New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

github.com/m1gwings/treedrawer

Package Overview
Dependencies
Alerts
File Explorer
Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

github.com/m1gwings/treedrawer

  • v0.3.3-beta
  • Source
  • Go
  • Socket score

Version published
Created
Source

treedrawer

treedrawer is a Go module for drawing trees on the terminal like the one below.

                                    ╭─╮                                    
                                    │9│                                    
                                    ╰┬╯                                    
                   ╭─────────────────┴─────────────┬─────────────┬───┬───╮ 
        ╭──────────┴─────────╮                    ╭┴╮           ╭┴╮ ╭┴╮ ╭┴╮
        │I can handle strings│                    │1│           │2│ │3│ │4│
        ╰──────────┬─────────╯                    ╰┬╯           ╰─╯ ╰─╯ ╰─╯
                   │                      ╭─────┬──┴─────╮                 
  ╭────────────────┴────────────────╮   ╭─┴─╮ ╭─┴╮  ╭────┴───╮             
  │with as many children as you want│   │124│ │13│  │a string│             
  ╰────────────────┬────────────────╯   ╰───╯ ╰──╯  ╰────────╯             
                   │                                                       
   ╭───────────────┴───────────────╮                                       
   │with as many layers as you want│                                       
   ╰───────────────┬───────────────╯                                       
                   │                                                       
 ╭─────────────────┴─────────────────╮                                     
 │actually I can handle everything...│                                     
 ╰─────────────────┬─────────────────╯                                     
                   │                                                       
╭──────────────────┴──────────────────╮                                    
│...that satisfies NodeValue interface│                                    
╰─────────────────────────────────────╯                                    

Import

import "github.com/m1gwings/treedrawer/tree"

Quick start

# Assume the following code is in example.go file
$ cat example.go
package main

import (
	"fmt"

	"github.com/m1gwings/treedrawer/tree"
)

func main() {
	// Creating a tree with 5 as the value of the root node
	t := tree.NewTree(tree.NodeInt64(5))

	// Adding children
	t.AddChild(tree.NodeString("adding a string"))
	t.AddChild(tree.NodeInt64(4))
	t.AddChild(tree.NodeInt64(3))

	// Drawing the tree
	fmt.Println(t)
}
$ go run example.go
           ╭─╮           
           │5│           
           ╰┬╯           
        ╭───┴──────┬───╮ 
╭───────┴───────╮ ╭┴╮ ╭┴╮
│adding a string│ │4│ │3│
╰───────────────╯ ╰─╯ ╰─╯

Usage

Building the tree

Creating the tree with 1 as the value of the root node

t := tree.NewTree(tree.NodeInt64(1))

Adding the first child to t with value 2

t.AddChild(tree.NodeInt64(2))

Adding more children

t.AddChild(tree.NodeInt64(3))
t.AddChild(tree.NodeInt64(4))
t.AddChild(tree.NodeInt64(5))

We've just built the tree below

      ╭─╮      
      │1│      
      ╰┬╯      
 ╭───┬─┴─┬───╮ 
╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮
│2│ │3│ │4│ │5│
╰─╯ ╰─╯ ╰─╯ ╰─╯

Navigating the tree

Navigating to first child of t (we're still working with the tree above)

// This method returns an error if the i-th child does not exist
// in this case i = 0
tFirstChild, err := t.Child(0)

Adding children to first child

tFirstChild.AddChild(tree.NodeInt64(6))
tFirstChild.AddChild(tree.NodeInt64(7))
tFirstChild.AddChild(tree.NodeInt64(8))

Going back to parent

// ok would be equal to false if tFirstChild were the root of the tree
tFirstChildParent, ok := tFirstChild.Parent()

_ := tFirstChildParent == t // true, we have gone back to the root of the tree

Navigating to third child of t

tThirdChild, err := t.Child(2)

Adding a string child to third child

tThirdChild.AddChild(tree.NodeString("I'm a string"))

Getting a pointer to the root of the tree

tRoot := tThirdChild.Root()

_ := tRoot == t // true

Now the tree looks like this

                ╭─╮                
                │1│                
                ╰┬╯                
     ╭───────┬───┴─────┬─────────╮ 
    ╭┴╮     ╭┴╮       ╭┴╮       ╭┴╮
    │2│     │3│       │4│       │5│
    ╰┬╯     ╰─╯       ╰┬╯       ╰─╯
 ╭───┼───╮             │           
╭┴╮ ╭┴╮ ╭┴╮     ╭──────┴─────╮     
│6│ │7│ │8│     │I'm a string│     
╰─╯ ╰─╯ ╰─╯     ╰────────────╯     

Getting and setting values from the tree

Getting the value of a node

v := t.Val()

Setting the value of a node

t.SetVal(tree.NodeInt64(3))

Drawing the tree

*tree.Tree implements the Stringer interface, just use package fmt to draw trees to terminal

fmt.Println(t)

Implementing NodeValue interface

The tree can handle every type that satisfies the NodeValue interface

// NodeValue is the interface that wraps the Draw method.
//
// The Draw method allows to convert data into its unicode canvas representation.
// With the Draw method you can control how your data is going to appear on the tree.
type NodeValue interface {
	Draw() *drawer.Drawer
}

The wrappers for built-in are defined inside the package treedrawer/tree like tree.NodeInt64 or tree.NodeString used above, so you don't need to worry about them.
Continue reading this section if you want to draw custom types instead.

  • Importing treedrawer/drawer

First of all we need access to the drawer.Drawer type. Just import the following

import "github.com/m1gwings/treedrawer/drawer"

drawer.Drawer under the hood is just a 2D slice of runes on which you can draw a rune specifying its coordinates or another entire drawer.Drawer specifying the coordinates of its upper-left corner.

  • Defining a custom type
type NodeAsterisk struct {
	Width, Height int
}

NodeAsterisk represents a rectangle of width NodeAsterisk.Width and height NodeAsterisk.Height.

  • Implementing NodeAsterisk.Draw() in order to satisfy NodeValue interface
func (nA NodeAsterisk) Draw() *drawer.Drawer {
	d, err := drawer.NewDrawer(nA.Width, nA.Height)
	if err != nil {
		log.Fatal(err)
	}
	for x := 0; x < nA.Width; x++ {
		for y := 0; y < nA.Height; y++ {
			err = d.DrawRune('*', x, y)
			if err != nil {
				log.Fatal(err)
			}
		}
	}
	return d
}

The method allocates a new drawer with width nA.Width and height nA.Height, then loops over each cell and fills it with an '*'.
You can implement this method to represent your data as you want.

  • Adding instances of NodeAsterisk to a tree
t := tree.NewTree(NodeAsterisk{3, 4})
t.AddChild(NodeAsterisk{1, 2})
t.AddChild(NodeAsterisk{3, 3})
  • Drawing the tree
fmt.Println(t)
  ╭───╮  
  │***│  
  │***│  
  │***│  
  │***│  
  ╰─┬─╯  
 ╭──┴─╮  
╭┴╮ ╭─┴─╮
│*│ │***│
│*│ │***│
╰─╯ │***│
    ╰───╯

Examples

You can find these examples inside the ./examples folder

HTML tree

# Assume the following code is in htmltree.go file
$ cat htmltree.go
package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/m1gwings/treedrawer/tree"
	"golang.org/x/net/html"
)

func main() {
	s := `<p>Links:</p><ul><li><a href="foo">Foo</a><li><a href="/bar/baz">BarBaz</a></ul>`
	doc, err := html.Parse(strings.NewReader(s))
	if err != nil {
		log.Fatal(err)
	}

	t := tree.NewTree(tree.NodeString(""))

	var f func(*html.Node, *tree.Tree)
	f = func(n *html.Node, t *tree.Tree) {
		t.SetVal(tree.NodeString(n.Data))
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			tChild := t.AddChild(tree.NodeString(""))
			f(c, tChild)
		}
	}
	// Starting from FirstChild because the DocumentRoot has an empty Val
	f(doc.FirstChild, t)

	fmt.Println(t)
}
$ go run htmltree.go
             ╭────╮              
             │html│              
             ╰──┬─╯              
   ╭────────────┴───╮            
╭──┴─╮           ╭──┴─╮          
│head│           │body│          
╰────╯           ╰──┬─╯          
            ╭───────┴────╮       
           ╭┴╮         ╭─┴╮      
           │p│         │ul│      
           ╰┬╯         ╰─┬╯      
            │       ╭────┴──╮    
        ╭───┴──╮  ╭─┴╮    ╭─┴╮   
        │Links:│  │li│    │li│   
        ╰──────╯  ╰─┬╯    ╰─┬╯   
                    │       │    
                   ╭┴╮     ╭┴╮   
                   │a│     │a│   
                   ╰┬╯     ╰┬╯   
                    │       │    
                  ╭─┴─╮ ╭───┴──╮ 
                  │Foo│ │BarBaz│ 
                  ╰───╯ ╰──────╯ 

File system tree

# Assume the following code is in filesystemtree.go file
$ cat filesystemtree.go
package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/m1gwings/treedrawer/tree"
	"golang.org/x/net/html"
)

func main() {
	s := `<p>Links:</p><ul><li><a href="foo">Foo</a><li><a href="/bar/baz">BarBaz</a></ul>`
	doc, err := html.Parse(strings.NewReader(s))
	if err != nil {
		log.Fatal(err)
	}

	t := tree.NewTree(tree.NodeString(""))

	var f func(*html.Node, *tree.Tree)
	f = func(n *html.Node, t *tree.Tree) {
		t.SetVal(tree.NodeString(n.Data))
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			tChild := t.AddChild(tree.NodeString(""))
			f(c, tChild)
		}
	}
	// Starting from FirstChild because the DocumentRoot has an empty Val
	f(doc.FirstChild, t)

	fmt.Println(t)
}
$ go run filesystemtree.go
                                                                                ╭──────────╮                                                                                 
                                                                                │treedrawer│                                                                                 
                                                                                ╰─────┬────╯                                                                                 
     ╭──────────────────────┬─────────────────────┬───────────────────┬───────────────┴───┬───────────────────────────────────────────╮                                      
╭────┴────╮            ╭────┴───╮             ╭───┴───╮           ╭───┴──╮            ╭───┴──╮                                     ╭──┴─╮                                    
│README.md│            │examples│             │LICENSE│           │drawer│            │go.sum│                                     │tree│                                    
╰─────────╯            ╰────┬───╯             ╰───────╯           ╰───┬──╯            ╰──────╯                                     ╰──┬─╯                                    
                  ╭─────────┴──────╮                            ╭─────┴────────╮                         ╭─────────────────┬──────────┴─┬────────────┬───────────────╮       
            ╭─────┴─────╮ ╭────────┴────────╮           ╭───────┴──────╮  ╭────┴────╮           ╭────────┴───────╮  ╭──────┴─────╮  ╭───┴───╮ ╭──────┴──────╮ ╭──────┴─────╮ 
            │htmltree.go│ │filesystemtree.go│           │drawer_test.go│  │drawer.go│           │examples_test.go│  │stringify.go│  │tree.go│ │nodevalues.go│ │tree_test.go│ 
            ╰───────────╯ ╰─────────────────╯           ╰──────────────╯  ╰─────────╯           ╰────────────────╯  ╰────────────╯  ╰───────╯ ╰─────────────╯ ╰────────────╯ 

Benchmarks

You can find the code used for the benchmark inside ./tree/stringify_test.go
In order to profile the module we first create trees with l layers and c children for each node, except leaf nodes. Each node has a tree.NodeString("*") as value.
For example the tree below has 3 layers and 2 children for each node.

      ╭─╮      
      │*│      
      ╰┬╯      
   ╭───┴───╮   
  ╭┴╮     ╭┴╮  
  │*│     │*│  
  ╰┬╯     ╰┬╯  
 ╭─┴─╮   ╭─┴─╮ 
╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮
│*│ │*│ │*│ │*│
╰─╯ ╰─╯ ╰─╯ ╰─╯

In our benchmark function we print to /dev/null a tree with the specified l and c parameter.

NameIterationsTimeChildren per NodeLayersTotal of NodesMemoryAllocations
BenchmarkDrawing3L3C-1210000100063 ns/op3.00 children3.00 layers13.0 nodes135576 B/op722 allocs/op
BenchmarkDrawing100L1C-123823096956 ns/op1.00 children100 layers100 nodes3727628 B/op23297 allocs/op
BenchmarkDrawing6L3C-129119789317 ns/op3.00 children6.00 layers364 nodes366549320 B/op33606 allocs/op
BenchmarkDrawing1000L1C-127161607620 ns/op1.00 children1000 layers1000 nodes373048737 B/op2033004 allocs/op
BenchmarkDrawing10L2C-122733952840 ns/op2.00 children10.0 layers1023 nodes4375790984 B/op114909 allocs/op
BenchmarkDrawing11L2C-1213661883138 ns/op2.00 children11.0 layers2047 nodes20557038432 B/op249910 allocs/op
BenchmarkDrawing8L3C-1218550947886 ns/op3.00 children8.00 layers3280 nodes50574173168 B/op387828 allocs/op
BenchmarkDrawing12L2C-12113559015034 ns/op2.00 children12.0 layers4095 nodes96166288000 B/op538283 allocs/op
Generated using go version go1.15.1 linux/amd64

Known issues 🐛

  • Emojis are larger than normal characters
fmt.Println(tree.NewTree(tree.NodeString("emojis are buggy 🤪")))
╭──────────────────╮ 
│emojis are buggy 🤪│ 
╰──────────────────╯ 

FAQs

Package last updated on 06 Sep 2020

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc