Skip to content
This repository was archived by the owner on Dec 16, 2025. It is now read-only.
5 changes: 5 additions & 0 deletions claat/nodes/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ type HeaderNode struct {
func (hn *HeaderNode) Empty() bool {
return hn.Content.Empty()
}

// IsHeader returns true if t is one of header types.
func IsHeader(t NodeType) bool {
return t&(NodeHeader|NodeHeaderCheck|NodeHeaderFAQ) != 0
}
51 changes: 51 additions & 0 deletions claat/nodes/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package nodes

// NewImportNode creates a new Node of type NodeImport,
// with initialized ImportNode.Content.
func NewImportNode(url string) *ImportNode {
return &ImportNode{
node: node{typ: NodeImport},
Content: NewListNode(),
URL: url,
}
}

// ImportNode indicates a remote resource available at ImportNode.URL.
type ImportNode struct {
node
URL string
Content *ListNode
}

// Empty returns the result of in.Content.Empty method.
func (in *ImportNode) Empty() bool {
return in.Content.Empty()
}

// MutateBlock mutates both in's block marker and that of in.Content.
func (in *ImportNode) MutateBlock(v interface{}) {
in.node.MutateBlock(v)
in.Content.MutateBlock(v)
}

// ImportNodes extracts everything except NodeImport nodes, recursively.
func ImportNodes(nodes []Node) []*ImportNode {
var imps []*ImportNode
for _, n := range nodes {
switch n := n.(type) {
case *ImportNode:
imps = append(imps, n)
case *ListNode:
imps = append(imps, ImportNodes(n.Nodes)...)
case *InfoboxNode:
imps = append(imps, ImportNodes(n.Content.Nodes)...)
case *GridNode:
for _, r := range n.Rows {
for _, c := range r {
imps = append(imps, ImportNodes(c.Content.Nodes)...)
}
}
}
}
return imps
}
196 changes: 196 additions & 0 deletions claat/nodes/import_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package nodes

import (
"testing"

"github.com/google/go-cmp/cmp"
)

var cmpOptImport = cmp.AllowUnexported(ImportNode{}, node{}, ListNode{}, TextNode{})

func TestNewImportNode(t *testing.T) {
tests := []struct {
name string
inURL string
out *ImportNode
}{
{
name: "Empty",
out: &ImportNode{
node: node{typ: NodeImport},
Content: NewListNode(),
},
},
{
name: "HasURL",
inURL: "google.com",
out: &ImportNode{
node: node{typ: NodeImport},
URL: "google.com",
Content: NewListNode(),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
out := NewImportNode(tc.inURL)
if diff := cmp.Diff(tc.out, out, cmpOptImport); diff != "" {
t.Errorf("NewImportNode(%q) got diff (-want +got): %s", tc.inURL, diff)
return
}
})
}
}

func TestImportNodeEmpty(t *testing.T) {
a := NewImportNode("")
a.Content.Nodes = append(a.Content.Nodes, NewTextNode("a"))
b := NewImportNode("foobar")
b.Content.Nodes = append(b.Content.Nodes, NewTextNode("b"))
c := NewImportNode("foobar")
c.Content.Nodes = append(c.Content.Nodes, NewTextNode(""))

tests := []struct {
name string
inNode *ImportNode
out bool
}{
{
name: "EmptyNoURL",
inNode: NewImportNode(""),
out: true,
},
{
name: "EmptyWithURL",
inNode: NewImportNode("google.com"),
out: true,
},
{
name: "NonEmptyNoURL",
inNode: a,
},
{
name: "NonEmptyWithURL",
inNode: b,
},
{
name: "EmptyWithContent",
inNode: c,
out: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
out := tc.inNode.Empty()
if out != tc.out {
t.Errorf("ImportNode.Empty() = %t, want %t", out, tc.out)
return
}
})
}
}

func TestImportNodeMutateBlock(t *testing.T) {
n := NewImportNode("")
mValue := "foobar"

n.MutateBlock(mValue)

if n.node.block != mValue {
t.Errorf("ImportNode.node.block = %+v, want %q", n.node.block, mValue)
}
if n.Content.node.block != mValue {
t.Errorf("ImportNode.Content.node.block = %+v, want %q", n.Content.node.block, mValue)
}
}

func TestImportNodes(t *testing.T) {
a1 := NewImportNode("google.com")
a2 := NewImportNode("youtube.com")
a3 := NewImportNode("google.com/calendar")

b1 := NewGridNode(
[]*GridCell{
&GridCell{
Rowspan: 1,
Colspan: 1,
Content: NewListNode(NewTextNode("aaa"), NewTextNode("bbb")),
},
&GridCell{
Rowspan: 1,
Colspan: 1,
Content: NewListNode(a1, NewTextNode("ccc")),
},
},
[]*GridCell{
&GridCell{
Rowspan: 1,
Colspan: 1,
Content: NewListNode(NewTextNode("ddd"), a3),
},
&GridCell{
Rowspan: 1,
Colspan: 1,
Content: NewListNode(a2, NewTextNode("eee")),
},
},
)

c1 := NewInfoboxNode(InfoboxNegative, a1, NewTextNode("foobar"))
c2 := NewListNode(a2, NewButtonNode(false, false, false, NewTextNode("foobar")))
c3 := NewListNode(c1, c2, a3)

tests := []struct {
name string
inNodes []Node
out []*ImportNode
}{
{
name: "JustImport",
inNodes: []Node{a1},
out: []*ImportNode{a1},
},
{
name: "Multiple",
inNodes: []Node{a1, NewTextNode("foo"), a2, NewTextNode("bar"), a3},
out: []*ImportNode{a1, a2, a3},
},
{
name: "List",
inNodes: []Node{NewListNode(a1, a2, a3)},
out: []*ImportNode{a1, a2, a3},
},
{
name: "Infobox",
inNodes: []Node{NewInfoboxNode(InfoboxPositive, a1, a2, NewTextNode("foobar"), a3)},
out: []*ImportNode{a1, a2, a3},
},
{
name: "Grid",
inNodes: []Node{b1},
out: []*ImportNode{a1, a3, a2},
},
{
name: "Button",
inNodes: []Node{NewButtonNode(true, true, true, a3, a2, a1)},
},
{
name: "Text",
inNodes: []Node{NewTextNode("foobar")},
},
{
name: "NontrivialStructure",
inNodes: []Node{c3},
out: []*ImportNode{a1, a2, a3},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
out := ImportNodes(tc.inNodes)
if diff := cmp.Diff(tc.out, out, cmpOptImport); diff != "" {
t.Errorf("ImportNodes(%+v) got diff (-want +got): %s", tc.inNodes, diff)
return
}
})
}
}
42 changes: 42 additions & 0 deletions claat/nodes/itemslist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package nodes

// NewItemsListNode creates a new ItemsListNode of type NodeItemsList,
// which defaults to an unordered list.
// Provide a positive start to make this a numbered list.
// NodeItemsCheck and NodeItemsFAQ are always unnumbered.
func NewItemsListNode(typ string, start int) *ItemsListNode {
iln := ItemsListNode{
node: node{typ: NodeItemsList},
// TODO document this
ListType: typ,
Start: start,
}
iln.MutateBlock(true)
return &iln
}

// ItemsListNode containts sets of ListNode.
// Non-zero ListType indicates an ordered list.
type ItemsListNode struct {
node
ListType string
Start int
Items []*ListNode
}

// Empty returns true if every item has empty content.
func (il *ItemsListNode) Empty() bool {
for _, i := range il.Items {
if !i.Empty() {
return false
}
}
return true
}

// NewItem creates a new ListNode and adds it to il.Items.
func (il *ItemsListNode) NewItem(nodes ...Node) *ListNode {
n := NewListNode(nodes...)
il.Items = append(il.Items, n)
return n
}
Loading