├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── _config.yml ├── go.mod ├── gotree-logo.png ├── gotree.go └── gotree_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .idea/ 27 | GoTree.iml 28 | ### Linux template 29 | *~ 30 | 31 | # temporary files which can be created if a process still has a handle open of a deleted file 32 | .fuse_hidden* 33 | 34 | # KDE directory preferences 35 | .directory 36 | 37 | # Linux trash folder which might appear on any partition or disk 38 | .Trash-* 39 | ### Windows template 40 | # Windows image file caches 41 | Thumbs.db 42 | ehthumbs.db 43 | 44 | # Folder config file 45 | Desktop.ini 46 | 47 | # Recycle Bin used on file shares 48 | $RECYCLE.BIN/ 49 | 50 | # Windows Installer files 51 | *.cab 52 | *.msi 53 | *.msm 54 | *.msp 55 | 56 | # Windows shortcuts 57 | *.lnk 58 | ### JetBrains template 59 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 60 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 61 | 62 | # User-specific stuff: 63 | .idea/workspace.xml 64 | .idea/tasks.xml 65 | .idea/dictionaries 66 | .idea/vcs.xml 67 | .idea/jsLibraryMappings.xml 68 | 69 | # Sensitive or high-churn files: 70 | .idea/dataSources.ids 71 | .idea/dataSources.xml 72 | .idea/dataSources.local.xml 73 | .idea/sqlDataSources.xml 74 | .idea/dynamic.xml 75 | .idea/uiDesigner.xml 76 | 77 | # Gradle: 78 | .idea/gradle.xml 79 | .idea/libraries 80 | 81 | # Mongo Explorer plugin: 82 | .idea/mongoSettings.xml 83 | 84 | ## File-based project format: 85 | *.iws 86 | 87 | ## Plugin-specific files: 88 | 89 | # IntelliJ 90 | /out/ 91 | 92 | # mpeltonen/sbt-idea plugin 93 | .idea_modules/ 94 | 95 | # JIRA plugin 96 | atlassian-ide-plugin.xml 97 | 98 | # Crashlytics plugin (for Android Studio and IntelliJ) 99 | com_crashlytics_export_strings.xml 100 | crashlytics.properties 101 | crashlytics-build.properties 102 | fabric.properties 103 | ### Go template 104 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 105 | 106 | # Folders 107 | 108 | # Architecture specific extensions/prefixes 109 | 110 | 111 | 112 | ### OSX template 113 | *.DS_Store 114 | .AppleDouble 115 | .LSOverride 116 | 117 | # Icon must end with two \r 118 | Icon 119 | 120 | # Thumbnails 121 | ._* 122 | 123 | # Files that might appear in the root of a volume 124 | .DocumentRevisions-V100 125 | .fseventsd 126 | .Spotlight-V100 127 | .TemporaryItems 128 | .Trashes 129 | .VolumeIcon.icns 130 | .com.apple.timemachine.donotpresent 131 | 132 | # Directories potentially created on remote AFP share 133 | .AppleDB 134 | .AppleDesktop 135 | Network Trash Folder 136 | Temporary Items 137 | .apdisk 138 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go_import_path: github.com/disiqueira/gotree 3 | git: 4 | depth: 1 5 | env: 6 | - GO111MODULE=on 7 | - GO111MODULE=off 8 | go: [ 1.11.x, 1.12.x, 1.13.x ] 9 | os: [ linux, osx ] 10 | script: 11 | - go test -race -v ./... 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Diego Siqueira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![GoTree](https://rawgit.com/DiSiqueira/GoTree/master/gotree-logo.png) 2 | 3 | # GoTree ![Language Badge](https://img.shields.io/badge/Language-Go-blue.svg) ![Go Report](https://goreportcard.com/badge/github.com/DiSiqueira/GoTree) ![License Badge](https://img.shields.io/badge/License-MIT-blue.svg) ![Status Badge](https://img.shields.io/badge/Status-Beta-brightgreen.svg) [![GoDoc](https://godoc.org/github.com/DiSiqueira/GoTree?status.svg)](https://godoc.org/github.com/DiSiqueira/GoTree) [![Build Status](https://travis-ci.org/DiSiqueira/GoTree.svg?branch=master)](https://travis-ci.org/DiSiqueira/GoTree) 4 | 5 | Simple Go module to print tree structures in terminal. Heavily inspired by [The Tree Command for Linux][treecommand] 6 | 7 | The GoTree's goal is to be a simple tool providing a stupidly easy-to-use and fast way to print recursive structures. 8 | 9 | [treecommand]: http://mama.indstate.edu/users/ice/tree/ 10 | 11 | ## Project Status 12 | 13 | GoTree is on beta. Pull Requests [are welcome](https://github.com/DiSiqueira/GoTree#social-coding) 14 | 15 | ![](http://image.prntscr.com/image/2a0dbf0777454446b8083fb6a0dc51fe.png) 16 | 17 | ## Features 18 | 19 | - Very simple and fast code 20 | - Intuitive names 21 | - Easy to extend 22 | - Uses only native libs 23 | - STUPIDLY [EASY TO USE](https://github.com/DiSiqueira/GoTree#usage) 24 | 25 | ## Installation 26 | 27 | ### Go Get 28 | 29 | ```bash 30 | $ go get github.com/disiqueira/gotree 31 | ``` 32 | 33 | ## Usage 34 | 35 | ### Simple create, populate and print example 36 | 37 | ![](http://image.prntscr.com/image/dd2fe3737e6543f7b21941a6953598c2.png) 38 | 39 | ```golang 40 | package main 41 | 42 | import ( 43 | "fmt" 44 | 45 | "github.com/disiqueira/gotree" 46 | ) 47 | 48 | func main() { 49 | artist := gotree.New("Pantera") 50 | album := artist.Add("Far Beyond Driven") 51 | album.Add("5 minutes Alone") 52 | 53 | fmt.Println(artist.Print()) 54 | } 55 | ``` 56 | 57 | ## Contributing 58 | 59 | ### Bug Reports & Feature Requests 60 | 61 | Please use the [issue tracker](https://github.com/DiSiqueira/GoTree/issues) to report any bugs or file feature requests. 62 | 63 | ### Developing 64 | 65 | PRs are welcome. To begin developing, do this: 66 | 67 | ```bash 68 | $ git clone --recursive git@github.com:DiSiqueira/GoTree.git 69 | $ cd GoTree/ 70 | ``` 71 | 72 | ## Social Coding 73 | 74 | 1. Create an issue to discuss about your idea 75 | 2. [Fork it] (https://github.com/DiSiqueira/GoTree/fork) 76 | 3. Create your feature branch (`git checkout -b my-new-feature`) 77 | 4. Commit your changes (`git commit -am 'Add some feature'`) 78 | 5. Push to the branch (`git push origin my-new-feature`) 79 | 6. Create a new Pull Request 80 | 7. Profit! :white_check_mark: 81 | 82 | ## License 83 | 84 | The MIT License (MIT) 85 | 86 | Copyright (c) 2013-2018 Diego Siqueira 87 | 88 | Permission is hereby granted, free of charge, to any person obtaining a copy 89 | of this software and associated documentation files (the "Software"), to deal 90 | in the Software without restriction, including without limitation the rights 91 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 92 | copies of the Software, and to permit persons to whom the Software is 93 | furnished to do so, subject to the following conditions: 94 | 95 | The above copyright notice and this permission notice shall be included in 96 | all copies or substantial portions of the Software. 97 | 98 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 99 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 100 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 101 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 102 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 103 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 104 | THE SOFTWARE. 105 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/disiqueira/gotree/v3 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gotree-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d6o/GoTree/07d11ce3f54daaa88bd9a492a4010e8c500bcc69/gotree-logo.png -------------------------------------------------------------------------------- /gotree.go: -------------------------------------------------------------------------------- 1 | // Package gotree create and print tree. 2 | package gotree 3 | 4 | import ( 5 | "strings" 6 | ) 7 | 8 | const ( 9 | newLine = "\n" 10 | emptySpace = " " 11 | middleItem = "├── " 12 | continueItem = "│ " 13 | lastItem = "└── " 14 | ) 15 | 16 | type ( 17 | tree struct { 18 | text string 19 | items []Tree 20 | } 21 | 22 | // Tree is tree interface 23 | Tree interface { 24 | Add(text string) Tree 25 | AddTree(tree Tree) 26 | Items() []Tree 27 | Text() string 28 | Print() string 29 | } 30 | 31 | printer struct { 32 | } 33 | 34 | // Printer is printer interface 35 | Printer interface { 36 | Print(Tree) string 37 | } 38 | ) 39 | 40 | //New returns a new GoTree.Tree 41 | func New(text string) Tree { 42 | return &tree{ 43 | text: text, 44 | items: []Tree{}, 45 | } 46 | } 47 | 48 | //Add adds a node to the tree 49 | func (t *tree) Add(text string) Tree { 50 | n := New(text) 51 | t.items = append(t.items, n) 52 | return n 53 | } 54 | 55 | //AddTree adds a tree as an item 56 | func (t *tree) AddTree(tree Tree) { 57 | t.items = append(t.items, tree) 58 | } 59 | 60 | //Text returns the node's value 61 | func (t *tree) Text() string { 62 | return t.text 63 | } 64 | 65 | //Items returns all items in the tree 66 | func (t *tree) Items() []Tree { 67 | return t.items 68 | } 69 | 70 | //Print returns an visual representation of the tree 71 | func (t *tree) Print() string { 72 | return newPrinter().Print(t) 73 | } 74 | 75 | func newPrinter() Printer { 76 | return &printer{} 77 | } 78 | 79 | //Print prints a tree to a string 80 | func (p *printer) Print(t Tree) string { 81 | return t.Text() + newLine + p.printItems(t.Items(), []bool{}) 82 | } 83 | 84 | func (p *printer) printText(text string, spaces []bool, last bool) string { 85 | var result string 86 | for _, space := range spaces { 87 | if space { 88 | result += emptySpace 89 | } else { 90 | result += continueItem 91 | } 92 | } 93 | 94 | indicator := middleItem 95 | if last { 96 | indicator = lastItem 97 | } 98 | 99 | var out string 100 | lines := strings.Split(text, "\n") 101 | for i := range lines { 102 | text := lines[i] 103 | if i == 0 { 104 | out += result + indicator + text + newLine 105 | continue 106 | } 107 | if last { 108 | indicator = emptySpace 109 | } else { 110 | indicator = continueItem 111 | } 112 | out += result + indicator + text + newLine 113 | } 114 | 115 | return out 116 | } 117 | 118 | func (p *printer) printItems(t []Tree, spaces []bool) string { 119 | var result string 120 | for i, f := range t { 121 | last := i == len(t)-1 122 | result += p.printText(f.Text(), spaces, last) 123 | if len(f.Items()) > 0 { 124 | spacesChild := append(spaces, last) 125 | result += p.printItems(f.Items(), spacesChild) 126 | } 127 | } 128 | return result 129 | } 130 | -------------------------------------------------------------------------------- /gotree_test.go: -------------------------------------------------------------------------------- 1 | package gotree 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func ExampleTree() { 10 | artist := New("Pantera") 11 | album := artist.Add("Far Beyond Driven\nsee https://en.wikipedia.org/wiki/Pantera\n(1994)") 12 | five := album.Add("5 minutes Alone") 13 | five.Add("song by American\ngroove metal") 14 | album.Add("I’m Broken") 15 | album.Add("Good Friends and a Bottle of Pills") 16 | 17 | artist.Add("Power Metal\n(1988)") 18 | artist.Add("Cowboys from Hell\n(1990)") 19 | fmt.Println(artist.Print()) 20 | 21 | // Output: 22 | // Pantera 23 | // ├── Far Beyond Driven 24 | // │ see https://en.wikipedia.org/wiki/Pantera 25 | // │ (1994) 26 | // │ ├── 5 minutes Alone 27 | // │ │ └── song by American 28 | // │ │ groove metal 29 | // │ ├── I’m Broken 30 | // │ └── Good Friends and a Bottle of Pills 31 | // ├── Power Metal 32 | // │ (1988) 33 | // └── Cowboys from Hell 34 | // (1990) 35 | } 36 | 37 | func TestNew(t *testing.T) { 38 | type args struct { 39 | text string 40 | } 41 | tests := []struct { 42 | name string 43 | args args 44 | want Tree 45 | }{ 46 | { 47 | name: "Create new Tree", 48 | args: args{ 49 | text: "new tree", 50 | }, 51 | want: &tree{ 52 | text: "new tree", 53 | items: []Tree{}, 54 | }, 55 | }, 56 | } 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | if got := New(tt.args.text); !reflect.DeepEqual(got, tt.want) { 60 | t.Errorf("New() = %v, want %v", got, tt.want) 61 | } 62 | }) 63 | } 64 | } 65 | 66 | func Test_tree_Add(t *testing.T) { 67 | type fields struct { 68 | text string 69 | items []Tree 70 | } 71 | type args struct { 72 | text string 73 | } 74 | tests := []struct { 75 | name string 76 | fields fields 77 | args args 78 | want Tree 79 | parentCount int 80 | }{ 81 | { 82 | name: "Adding a new item into an empty tree", 83 | args: args{ 84 | text: "child item", 85 | }, 86 | fields: fields{ 87 | items: []Tree{}, 88 | }, 89 | want: &tree{ 90 | text: "child item", 91 | items: []Tree{}, 92 | }, 93 | parentCount: 1, 94 | }, 95 | { 96 | name: "Adding a new item into a full tree", 97 | args: args{ 98 | text: "fourth item", 99 | }, 100 | fields: fields{ 101 | items: []Tree{ 102 | New("test"), 103 | New("test2"), 104 | New("test3"), 105 | }, 106 | }, 107 | want: &tree{ 108 | text: "fourth item", 109 | items: []Tree{}, 110 | }, 111 | parentCount: 4, 112 | }, 113 | } 114 | for _, tt := range tests { 115 | t.Run(tt.name, func(t *testing.T) { 116 | tree := &tree{ 117 | text: tt.fields.text, 118 | items: tt.fields.items, 119 | } 120 | got := tree.Add(tt.args.text) 121 | if !reflect.DeepEqual(got, tt.want) { 122 | t.Errorf("tree.Add() = %v, want %v", got, tt.want) 123 | } 124 | if tt.parentCount != len(tree.Items()) { 125 | t.Errorf("tree total items = %v, want %v", got, tt.want) 126 | } 127 | }) 128 | } 129 | } 130 | 131 | func Test_tree_AddTree(t *testing.T) { 132 | type fields struct { 133 | text string 134 | items []Tree 135 | } 136 | type args struct { 137 | tree Tree 138 | } 139 | tests := []struct { 140 | name string 141 | fields fields 142 | args args 143 | itemCount int 144 | }{ 145 | { 146 | name: "Adding a new item into an empty tree", 147 | args: args{ 148 | tree: New("child item"), 149 | }, 150 | fields: fields{ 151 | items: []Tree{}, 152 | }, 153 | itemCount: 1, 154 | }, 155 | { 156 | name: "Adding a new item into a full tree", 157 | args: args{ 158 | tree: New("fourth item"), 159 | }, 160 | fields: fields{ 161 | items: []Tree{ 162 | New("test"), 163 | New("test2"), 164 | New("test3"), 165 | }, 166 | }, 167 | itemCount: 4, 168 | }, 169 | } 170 | for _, tt := range tests { 171 | t.Run(tt.name, func(t *testing.T) { 172 | tree := &tree{ 173 | text: tt.fields.text, 174 | items: tt.fields.items, 175 | } 176 | tree.AddTree(tt.args.tree) 177 | }) 178 | } 179 | } 180 | 181 | func Test_tree_Text(t *testing.T) { 182 | type fields struct { 183 | text string 184 | items []Tree 185 | } 186 | tests := []struct { 187 | name string 188 | fields fields 189 | want string 190 | }{ 191 | { 192 | name: "Return the correct value", 193 | fields: fields{ 194 | text: "item", 195 | }, 196 | want: "item", 197 | }, 198 | { 199 | name: "Return the correct value while empty", 200 | fields: fields{ 201 | text: "", 202 | }, 203 | want: "", 204 | }, 205 | } 206 | for _, tt := range tests { 207 | t.Run(tt.name, func(t *testing.T) { 208 | tree := &tree{ 209 | text: tt.fields.text, 210 | items: tt.fields.items, 211 | } 212 | if got := tree.Text(); got != tt.want { 213 | t.Errorf("tree.Text() = %v, want %v", got, tt.want) 214 | } 215 | }) 216 | } 217 | } 218 | 219 | func Test_tree_Items(t *testing.T) { 220 | type fields struct { 221 | text string 222 | items []Tree 223 | } 224 | tests := []struct { 225 | name string 226 | fields fields 227 | want []Tree 228 | }{ 229 | { 230 | name: "Return empty if there is no items under the tree", 231 | fields: fields{ 232 | text: "top level item", 233 | items: []Tree{}, 234 | }, 235 | want: []Tree{}, 236 | }, 237 | { 238 | name: "Return all items under the tree", 239 | fields: fields{ 240 | text: "top level item", 241 | items: []Tree{ 242 | New("first child"), 243 | New("second child"), 244 | }, 245 | }, 246 | want: []Tree{ 247 | New("first child"), 248 | New("second child"), 249 | }, 250 | }, 251 | } 252 | for _, tt := range tests { 253 | t.Run(tt.name, func(t *testing.T) { 254 | tree := &tree{ 255 | text: tt.fields.text, 256 | items: tt.fields.items, 257 | } 258 | if got := tree.Items(); !reflect.DeepEqual(got, tt.want) { 259 | t.Errorf("tree.Items() = %v, want %v", got, tt.want) 260 | } 261 | }) 262 | } 263 | } 264 | 265 | func Test_tree_Print(t *testing.T) { 266 | threeLevelTree := New("First Level") 267 | threeLevelTree.Add("Second level").Add("Third Level") 268 | 269 | complexTree := New("Daft Punk") 270 | ram := complexTree.Add("Random Access Memories") 271 | complexTree.Add("Humam After All") 272 | alive := complexTree.Add("Alive 2007") 273 | 274 | ram.Add("Give Life Back to Music") 275 | ram.Add("Giorgio by Moroder") 276 | ram.Add("Within") 277 | 278 | alive.Add("Touch It/Technologic") 279 | alive.Add("Face to Face/Too Long") 280 | 281 | type fields struct { 282 | tree Tree 283 | } 284 | tests := []struct { 285 | name string 286 | fields fields 287 | want string 288 | }{ 289 | { 290 | name: "Print a single item tree", 291 | fields: fields{ 292 | tree: New("single item"), 293 | }, 294 | want: `single item 295 | `, 296 | }, 297 | { 298 | name: "Print a three level tree", 299 | fields: fields{ 300 | tree: threeLevelTree, 301 | }, 302 | want: `First Level 303 | └── Second level 304 | └── Third Level 305 | `, 306 | }, 307 | { 308 | name: "Print a three level tree", 309 | fields: fields{ 310 | tree: complexTree, 311 | }, 312 | want: `Daft Punk 313 | ├── Random Access Memories 314 | │ ├── Give Life Back to Music 315 | │ ├── Giorgio by Moroder 316 | │ └── Within 317 | ├── Humam After All 318 | └── Alive 2007 319 | ├── Touch It/Technologic 320 | └── Face to Face/Too Long 321 | `, 322 | }, 323 | } 324 | 325 | for _, tt := range tests { 326 | t.Run(tt.name, func(t *testing.T) { 327 | if got := tt.fields.tree.Print(); got != tt.want { 328 | t.Errorf("tree.Print() = %#v, want %#v", got, tt.want) 329 | } 330 | }) 331 | } 332 | } 333 | --------------------------------------------------------------------------------