Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tree): modify children of existing Tree #461

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
18 changes: 18 additions & 0 deletions tree/children.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package tree

import "slices"

// Children is the interface that wraps the basic methods of a tree model.
type Children interface {
// At returns the content item of the given index.
Expand All @@ -18,6 +20,22 @@ func (n NodeChildren) Append(child Node) NodeChildren {
return n
}

// Insert inserts a child to the list at the given index.
func (n NodeChildren) Insert(index int, child Node) NodeChildren {
if index < 0 || len(n) < index+1 {
return n
}
return slices.Insert(n, index, child)
}

// Replace swaps the child at the given index with the given child.
func (n NodeChildren) Replace(index int, child Node) NodeChildren {
if index < 0 || len(n) < index+1 {
return n
}
return slices.Replace(n, index, index+1, child)
}

// Remove removes a child from the list at the given index.
func (n NodeChildren) Remove(index int) NodeChildren {
if index < 0 || len(n) < index+1 {
Expand Down
176 changes: 160 additions & 16 deletions tree/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,60 +9,60 @@ import (

// Leaf Examples

func ExampleLeaf_SetHidden() {
func ExampleNewLeaf() {
tr := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child("Hello!"),
Child(
tree.NewLeaf("This should be hidden", true),
tree.NewLeaf(
tree.Root("I am groot").Child("leaves"), false),
),
"Quuux",
),
"Baz",
)

tr.Children().At(1).Children().At(2).SetHidden(true)
fmt.Println(tr.String())
// Output:
//
// ├── Foo
// ├── Bar
// │ ├── Qux
// │ └── Quux
// │ └── Hello!
// │ ├── Quux
// │ │ └── I am groot
// │ │ └── leaves
// │ └── Quuux
// └── Baz
//
}

func ExampleNewLeaf() {
func ExampleLeaf_SetHidden() {
tr := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child(
tree.NewLeaf("This should be hidden", true),
tree.NewLeaf(
tree.Root("I am groot").Child("leaves"), false),
),
Child("Hello!"),
"Quuux",
),
"Baz",
)

tr.Children().At(1).Children().At(2).SetHidden(true)
fmt.Println(tr.String())
// Output:
//
// ├── Foo
// ├── Bar
// │ ├── Qux
// │ ├── Quux
// │ │ └── I am groot
// │ │ └── leaves
// │ └── Quuux
// │ └── Quux
// │ └── Hello!
// └── Baz
//
}
Expand Down Expand Up @@ -96,8 +96,152 @@ func ExampleLeaf_SetValue() {
//╰── Milk
}

func ExampleLeaf_Insert() {
t := tree.
Root("⁜ Makeup").
Child(
"Glossier",
"Fenty Beauty",
tree.New().Child(
"Gloss Bomb Universal Lip Luminizer",
"Hot Cheeks Velour Blushlighter",
),
"Nyx",
"Mac",
"Milk",
).
Enumerator(tree.RoundedEnumerator)
// Adds a new Tree Node to a Leaf (Mac).
t.Replace(3, t.Children().At(3).Insert(0, "Glow Play Cushion Blush"))
fmt.Println(ansi.Strip(t.String()))
// Output:
//⁜ Makeup
//├── Glossier
//├── Fenty Beauty
//│ ├── Gloss Bomb Universal Lip Luminizer
//│ ╰── Hot Cheeks Velour Blushlighter
//├── Nyx
//├── Mac
//│ ╰── Glow Play Cushion Blush
//╰── Milk
}

func ExampleLeaf_Replace() {
t := tree.
Root("⁜ Makeup").
Child(
"Glossier",
"Fenty Beauty",
tree.New().Child(
"Gloss Bomb Universal Lip Luminizer",
"Hot Cheeks Velour Blushlighter",
),
"Nyx",
"Mac",
"Milk",
).
Enumerator(tree.RoundedEnumerator)
// Add Glow Play Cushion Blush to Mac Leaf.
t.Replace(3, t.Children().At(3).Replace(0, "Glow Play Cushion Blush"))
fmt.Println(ansi.Strip(t.String()))
// Output:
//⁜ Makeup
//├── Glossier
//├── Fenty Beauty
//│ ├── Gloss Bomb Universal Lip Luminizer
//│ ╰── Hot Cheeks Velour Blushlighter
//├── Nyx
//├── Mac
//│ ╰── Glow Play Cushion Blush
//╰── Milk
}

// Tree Examples

func ExampleTree_Insert() {
t := tree.
Root("⁜ Makeup").
Child(
"Glossier",
"Fenty Beauty",
tree.New().Child(
"Gloss Bomb Universal Lip Luminizer",
"Hot Cheeks Velour Blushlighter",
),
"Nyx",
"Mac",
"Milk",
).
Enumerator(tree.RoundedEnumerator)
// Adds a new Tree Node after Fenty Beauty.
t.Insert(2, tree.Root("Lancôme").Child("Juicy Tubes Lip Gloss", "Lash Idôle", "Teint Idôle Highlighter"))

// Adds a new Tree Node in Fenty Beauty
t.Replace(1, t.Children().At(1).Insert(0, "Blurring Skin Tint"))

// Adds a new Tree Node to a Leaf (Mac)
t.Replace(4, t.Children().At(4).Insert(0, "Glow Play Cushion Blush"))
fmt.Println(ansi.Strip(t.String()))
// Output:
//⁜ Makeup
//├── Glossier
//├── Fenty Beauty
//│ ├── Blurring Skin Tint
//│ ├── Gloss Bomb Universal Lip Luminizer
//│ ╰── Hot Cheeks Velour Blushlighter
//├── Lancôme
//│ ├── Juicy Tubes Lip Gloss
//│ ├── Lash Idôle
//│ ╰── Teint Idôle Highlighter
//├── Nyx
//├── Mac
//│ ╰── Glow Play Cushion Blush
//╰── Milk
//
}

func ExampleTree_Replace() {
t := tree.
Root("⁜ Makeup").
Child(
"Glossier",
"Fenty Beauty",
tree.New().Child(
"Gloss Bomb Universal Lip Luminizer",
"Hot Cheeks Velour Blushlighter",
),
"Nyx",
"Mac",
"Milk",
).
Enumerator(tree.RoundedEnumerator)
// Add a Tree as a Child of "Glossier". At this stage "Glossier" is a Leaf,
// so we re-assign the value of "Glossier" in the "Makeup" Tree to its new
// Tree value returned from Child().
t.Replace(0, t.Children().At(0).Child(
tree.Root("Apparel").Child("Pink Hoodie", "Baseball Cap"),
))

// Add a Leaf as a Child of "Glossier". At this stage "Glossier" is a Tree,
// so we don't need to use [Tree.Replace] on the parent tree.
t.Children().At(0).Child("Makeup")
fmt.Println(ansi.Strip(t.String()))
// Output:
// ⁜ Makeup
// ├── Glossier
// │ ├── Apparel
// │ │ ├── Pink Hoodie
// │ │ ╰── Baseball Cap
// │ ╰── Makeup
// ├── Fenty Beauty
// │ ├── Gloss Bomb Universal Lip Luminizer
// │ ╰── Hot Cheeks Velour Blushlighter
// ├── Nyx
// ├── Mac
// ╰── Milk
//
}

func ExampleTree_Hide() {
tr := tree.New().
Child(
Expand Down
12 changes: 12 additions & 0 deletions tree/testdata/TestInheritedStyles.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
⁜ Makeup
├── Glossier
│  ├── Apparel
│  │  ├── Pink Hoodie
│  │  ╰── Baseball Cap
│  ╰── Makeup
├── Fenty Beauty
│  ├── Gloss Bomb Universal Lip Luminizer
│  ╰── Hot Cheeks Velour Blushlighter
├── Nyx
├── Mac
╰── Milk
89 changes: 89 additions & 0 deletions tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ type Node interface {
Value() string
Children() Children
Hidden() bool
Child(...any) *Tree
SetChildren(...any) *Tree
Insert(int, any) *Tree
Replace(int, any) *Tree
SetHidden(bool)
SetValue(any)
}
Expand Down Expand Up @@ -66,6 +70,35 @@ func (s Leaf) Value() string {
return s.value
}

// Child adds a child to this Tree, converting a Leaf to a Tree in the process.
func (s *Leaf) Child(children ...any) *Tree {
t := &Tree{
value: s.value,
hidden: s.hidden,
children: NodeChildren(nil),
}
return t.Child(children)
}

// SetChildren turns the Leaf into a Tree with the given children.
func (s *Leaf) SetChildren(children ...any) *Tree {
return s.Child(children)
}

// Replace turns the Leaf into a Tree with the given child. Because of the type
// change, you'll need to reassign the result of this function to the value with
// [Tree.Replace].
func (s *Leaf) Replace(_ int, child any) *Tree {
return s.Child(child)
}

// Insert turns the Leaf into a Tree with the given child. Because of the type
// change, you'll need to reassign the result of this function to the value with
// [Tree.Replace].
func (s *Leaf) Insert(_ int, child any) *Tree {
return s.Child(child)
}

// SetValue sets the value of a Leaf node.
func (s *Leaf) SetValue(value any) {
switch item := value.(type) {
Expand Down Expand Up @@ -152,6 +185,62 @@ func (t *Tree) String() string {
return t.ensureRenderer().render(t, true, "")
}

// SetChildren overwrites a Tree's Children.
func (t *Tree) SetChildren(children ...any) *Tree {
t.children = NodeChildren(nil)
return t.Child(children)
}

// Replace swaps the child at the given index with the given child.
func (t *Tree) Replace(index int, child any) *Tree {
nodes := t.anyToNode(child)
t.children = t.children.(NodeChildren).Replace(index, nodes[0])
return t
}

// Insert child at the given index.
func (t *Tree) Insert(index int, child any) *Tree {
nodes := t.anyToNode(child)
t.children = t.children.(NodeChildren).Insert(index, nodes[0])
return t
}

func (t *Tree) anyToNode(children ...any) []Node {
var nodes []Node
for _, child := range children {
switch item := child.(type) {
case *Tree:
child, _ := child.(*Tree)
nodes = append(nodes, child)
case Children:
for i := 0; i < item.Length(); i++ {
nodes = append(nodes, item.At(i))
}
case Node:
nodes = append(nodes, item)
case fmt.Stringer:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be useful to support encoding.TextMarshaler as well

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have this in other type checks in tree, maybe something we can add down the line

s := Leaf{value: item.String()}
nodes = append(nodes, &s)
case string:
s := Leaf{value: item}
nodes = append(nodes, &s)
case []any:
return t.anyToNode(item...)
case []string:
ss := make([]any, 0, len(item))
for _, s := range item {
ss = append(ss, s)
}
return t.anyToNode(ss...)
case nil:
continue
default:
return t.anyToNode(fmt.Sprintf("%v", item))
}
}
return nodes
}

// Child adds a child to this Tree.
//
// If a Child Tree is passed without a root, it will be parented to it's sibling
Expand Down
Loading
Loading