├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── bench_test.go ├── cmd ├── demo-list │ ├── README.md │ └── demo.go ├── demo-progress │ ├── README.md │ └── demo.go ├── demo-table │ ├── README.md │ ├── demo-colors.png │ └── demo.go ├── profile-list │ └── profile.go ├── profile-progress │ └── profile.go └── profile-table │ └── profile.go ├── go.mod ├── go.sum ├── list ├── README.md ├── list.go ├── list_test.go ├── render.go ├── render_html.go ├── render_html_test.go ├── render_markdown.go ├── render_markdown_test.go ├── render_test.go ├── style.go ├── writer.go └── writer_test.go ├── profile.sh ├── progress ├── README.md ├── images │ └── demo.gif ├── indicator.go ├── indicator_test.go ├── progress.go ├── progress_test.go ├── render.go ├── render_test.go ├── style.go ├── tracker.go ├── tracker_sort.go ├── tracker_sort_test.go ├── tracker_test.go ├── units.go ├── units_test.go └── writer.go ├── table ├── README.md ├── config.go ├── config_test.go ├── images │ └── table-StyleColoredBright.png ├── pager.go ├── pager_options.go ├── pager_test.go ├── render.go ├── render_automerge_test.go ├── render_bidi_test.go ├── render_csv.go ├── render_csv_test.go ├── render_hint.go ├── render_html.go ├── render_html_test.go ├── render_init.go ├── render_markdown.go ├── render_markdown_test.go ├── render_paged_test.go ├── render_test.go ├── render_tsv.go ├── render_tsv_test.go ├── row.go ├── sort.go ├── sort_test.go ├── style.go ├── table.go ├── table_test.go ├── util.go ├── util_test.go ├── writer.go └── writer_test.go └── text ├── README.md ├── align.go ├── align_test.go ├── ansi.go ├── ansi_test.go ├── ansi_unix.go ├── ansi_windows.go ├── color.go ├── color_html.go ├── color_test.go ├── cursor.go ├── cursor_test.go ├── direction.go ├── direction_test.go ├── escape_seq_parser.go ├── escape_seq_parser_test.go ├── filter.go ├── filter_test.go ├── format.go ├── format_test.go ├── hyperlink.go ├── hyperlink_test.go ├── string.go ├── string_test.go ├── transformer.go ├── transformer_test.go ├── valign.go ├── valign_test.go ├── wrap.go └── wrap_test.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior. Code samples if possible. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your problem. 18 | 19 | **Software (please complete the following information):** 20 | - OS: [e.g. OSX] 21 | - GoLang Version [e.g. 1.10] 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | - 3 | - 4 | - 5 | 6 | Fixes #. 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | # Pushes and pulls to all branches 5 | push: 6 | pull_request: 7 | 8 | # Run on the first day of every month 9 | schedule: 10 | - cron: "0 0 1 * *" 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | jobs: 16 | # Build and test everything 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | # Checkout the code 21 | - name: Checkout Code 22 | uses: actions/checkout@v4 23 | 24 | # Set up the GoLang environment 25 | - name: Set up Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: 1.18 29 | 30 | # Download all the tools used in the steps that follow 31 | - name: Set up Tools 32 | run: | 33 | go install github.com/fzipp/gocyclo/cmd/gocyclo@v0.6.0 34 | go install github.com/mattn/goveralls@v0.0.12 35 | go install github.com/rinchsan/gosimports/cmd/gosimports@v0.3.8 36 | 37 | # Run all the unit-tests 38 | - name: Test 39 | run: | 40 | make test 41 | 42 | # Run some tests to ensure no race conditions exist 43 | - name: Test for Race Conditions 44 | run: make test-race 45 | 46 | # Run the benchmarks to manually ensure no performance degradation 47 | - name: Benchmark 48 | run: make bench 49 | 50 | # Upload all the unit-test coverage reports to Coveralls 51 | - name: Upload Coverage Report 52 | env: 53 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | run: goveralls -service=github -coverprofile=.coverprofile 55 | 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /demo* 3 | /profile/ 4 | .coverprofile 5 | *.swp 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jedib0t@outlook.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 jedib0t 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all profile test 2 | 3 | default: test 4 | 5 | all: test bench 6 | 7 | tools: 8 | go install github.com/fzipp/gocyclo/cmd/gocyclo@v0.5.1 9 | go install github.com/rinchsan/gosimports/cmd/gosimports@v0.3.8 10 | 11 | bench: 12 | go test -bench=. -benchmem 13 | 14 | cyclo: 15 | gocyclo -over 13 ./*/*.go 16 | 17 | demo-list: 18 | go run cmd/demo-list/demo.go 19 | 20 | demo-progress: 21 | go run cmd/demo-progress/demo.go 22 | 23 | demo-table: 24 | go run cmd/demo-table/demo.go 25 | 26 | fmt: 27 | go fmt ./... 28 | gosimports -w . 29 | 30 | profile: 31 | sh profile.sh 32 | 33 | test: fmt vet cyclo 34 | go test -cover -coverprofile=.coverprofile ./... 35 | 36 | test-race: 37 | go run -race ./cmd/demo-progress/demo.go 38 | 39 | vet: 40 | go vet ./... 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-pretty 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/jedib0t/go-pretty/v6.svg)](https://pkg.go.dev/github.com/jedib0t/go-pretty/v6) 4 | [![Build Status](https://github.com/jedib0t/go-pretty/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/jedib0t/go-pretty/actions?query=workflow%3ACI+event%3Apush+branch%3Amain) 5 | [![Coverage Status](https://coveralls.io/repos/github/jedib0t/go-pretty/badge.svg?branch=main)](https://coveralls.io/github/jedib0t/go-pretty?branch=main) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/jedib0t/go-pretty/v6)](https://goreportcard.com/report/github.com/jedib0t/go-pretty/v6) 7 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jedib0t_go-pretty&metric=alert_status)](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty) 8 | 9 | Utilities to prettify console output of tables, lists, progress-bars, text, etc. 10 | with a heavy emphasis on customization. 11 | 12 | ## Usage 13 | 14 | The current major version of this package is __v6__, and it follows the standard 15 | outlined [here](https://go.dev/doc/modules/version-numbers#major-version). 16 | 17 | Run `go get github.com/jedib0t/go-pretty/v6` to add this as a dependency to your 18 | project, and import the packages in your code using one or more of these: 19 | * `github.com/jedib0t/go-pretty/v6/list` 20 | * `github.com/jedib0t/go-pretty/v6/progress` 21 | * `github.com/jedib0t/go-pretty/v6/table` 22 | * `github.com/jedib0t/go-pretty/v6/text` 23 | 24 | ## Table 25 | 26 | Pretty-print tables in a terminal with colors, nested tables and more. 27 | 28 | ``` 29 | +-----+------------+-----------+--------+-----------------------------+ 30 | | # | FIRST NAME | LAST NAME | SALARY | | 31 | +-----+------------+-----------+--------+-----------------------------+ 32 | | 1 | Arya | Stark | 3000 | | 33 | | 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! | 34 | | 300 | Tyrion | Lannister | 5000 | | 35 | +-----+------------+-----------+--------+-----------------------------+ 36 | | | | TOTAL | 10000 | | 37 | +-----+------------+-----------+--------+-----------------------------+ 38 | ``` 39 | 40 | Execute `go run github.com/jedib0t/go-pretty/v6/cmd/demo-table@latest colors` to get: 41 | 42 | Tables with Colors within a Table in a Terminal 43 | 44 | More details can be found here: [table/](table) 45 | 46 | ## Progress 47 | 48 | Track the Progress of one or more Tasks like downloading multiple files in 49 | parallel. 50 | 51 | Progress Demo in a Terminal 52 | 53 | More details can be found here: [progress/](progress) 54 | 55 | ## List 56 | 57 | Pretty-print lists with multiple levels/indents into ASCII/Unicode strings. 58 | 59 | ``` 60 | ╭─ Game Of Thrones 61 | │ ├─ Winter 62 | │ ├─ Is 63 | │ ╰─ Coming 64 | │ ├─ This 65 | │ ├─ Is 66 | │ ╰─ Known 67 | ╰─ The Dark Tower 68 | ╰─ The Gunslinger 69 | ``` 70 | 71 | More details can be found here: [list/](list) 72 | 73 | ## Text 74 | 75 | Utility functions to manipulate text with or without ANSI escape sequences. Most 76 | of the functions available are used in one or more of the other packages here. 77 | 78 | - Align text [Horizontally](text/align.go) or [Vertically](text/valign.go) 79 | - [Colorize](text/color.go) text 80 | - Move [cursor](text/cursor.go) 81 | - [Format](text/format.go) text (convert case) 82 | - Manipulate [strings](text/string.go) (Pad, RepeatAndTrim, RuneCount, etc.) 83 | - [Transform](text/transformer.go) text (UnixTime to human-readable-time, pretty-JSON, etc.) 84 | - [Wrap](text/wrap.go) text 85 | 86 | GoDoc has examples for all the available functions. 87 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 6.x.x | :white_check_mark: | 8 | | < 6.0 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Please create a new issue on this repo describing the vulnerability, 13 | and (if known) how to resolve it. 14 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package gopretty 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | "time" 7 | 8 | "github.com/jedib0t/go-pretty/v6/list" 9 | "github.com/jedib0t/go-pretty/v6/progress" 10 | "github.com/jedib0t/go-pretty/v6/table" 11 | ) 12 | 13 | var ( 14 | listItem1 = "Game Of Thrones" 15 | listItems2 = []interface{}{"Winter", "Is", "Coming"} 16 | listItems3 = []interface{}{"This", "Is", "Known"} 17 | tableCaption = "table-caption" 18 | tableRowFooter = table.Row{"", "", "Total", 10000} 19 | tableRowHeader = table.Row{"#", "First Name", "Last Name", "Salary"} 20 | tableRows = []table.Row{ 21 | {1, "Arya", "Stark", 3000}, 22 | {20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"}, 23 | {300, "Tyrion", "Lannister", 5000}, 24 | } 25 | tracker1 = progress.Tracker{Message: "Calculating Total # 1", Total: 1000, Units: progress.UnitsDefault} 26 | tracker2 = progress.Tracker{Message: "Downloading File # 2", Total: 1000, Units: progress.UnitsBytes} 27 | tracker3 = progress.Tracker{Message: "Transferring Amount # 3", Total: 1000, Units: progress.UnitsCurrencyDollar} 28 | ) 29 | 30 | func BenchmarkList_Render(b *testing.B) { 31 | for i := 0; i < b.N; i++ { 32 | lw := list.NewWriter() 33 | lw.AppendItem(listItem1) 34 | lw.Indent() 35 | lw.AppendItems(listItems2) 36 | lw.Indent() 37 | lw.AppendItems(listItems3) 38 | lw.Render() 39 | } 40 | } 41 | 42 | func BenchmarkProgress_Render(b *testing.B) { 43 | trackSomething := func(pw progress.Writer, tracker *progress.Tracker) { 44 | tracker.Reset() 45 | pw.AppendTracker(tracker) 46 | time.Sleep(time.Millisecond * 100) 47 | tracker.Increment(tracker.Total / 2) 48 | time.Sleep(time.Millisecond * 100) 49 | tracker.Increment(tracker.Total / 2) 50 | } 51 | 52 | for i := 0; i < b.N; i++ { 53 | pw := progress.NewWriter() 54 | pw.SetAutoStop(true) 55 | pw.SetOutputWriter(io.Discard) 56 | go trackSomething(pw, &tracker1) 57 | go trackSomething(pw, &tracker2) 58 | go trackSomething(pw, &tracker3) 59 | time.Sleep(time.Millisecond * 50) 60 | pw.Render() 61 | } 62 | } 63 | 64 | func generateBenchmarkTable() table.Writer { 65 | tw := table.NewWriter() 66 | tw.AppendHeader(tableRowHeader) 67 | tw.AppendRows(tableRows) 68 | tw.AppendFooter(tableRowFooter) 69 | tw.SetCaption(tableCaption) 70 | return tw 71 | } 72 | 73 | func BenchmarkTable_Render(b *testing.B) { 74 | for i := 0; i < b.N; i++ { 75 | generateBenchmarkTable().Render() 76 | } 77 | } 78 | 79 | func BenchmarkTable_RenderCSV(b *testing.B) { 80 | for i := 0; i < b.N; i++ { 81 | generateBenchmarkTable().RenderCSV() 82 | } 83 | } 84 | 85 | func BenchmarkTable_RenderHTML(b *testing.B) { 86 | for i := 0; i < b.N; i++ { 87 | generateBenchmarkTable().RenderHTML() 88 | } 89 | } 90 | 91 | func BenchmarkTable_RenderMarkdown(b *testing.B) { 92 | for i := 0; i < b.N; i++ { 93 | generateBenchmarkTable().RenderMarkdown() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /cmd/demo-list/README.md: -------------------------------------------------------------------------------- 1 | Output of `go run cmd/demo-list/demo.go`: 2 | 3 | ``` 4 | A Simple List: 5 | -------------- 6 | * Game Of Thrones 7 | * The Dark Tower 8 | 9 | A Multi-level List: 10 | ------------------- 11 | * Game Of Thrones 12 | * Winter 13 | * Is 14 | * Coming 15 | * This 16 | * Is 17 | * Known 18 | * The Dark Tower 19 | * The Gunslinger 20 | 21 | A List using the Style 'StyleBulletCircle': 22 | ------------------------------------------- 23 | ● Game Of Thrones 24 | ● Winter 25 | ● Is 26 | ● Coming 27 | ● This 28 | ● Is 29 | ● Known 30 | ● The Dark Tower 31 | ● The Gunslinger 32 | 33 | A List using the Style 'StyleConnectedRounded': 34 | ----------------------------------------------- 35 | ╭─ Game Of Thrones 36 | │ ├─ Winter 37 | │ ├─ Is 38 | │ ╰─ Coming 39 | │ ├─ This 40 | │ ├─ Is 41 | │ ╰─ Known 42 | ╰─ The Dark Tower 43 | ╰─ The Gunslinger 44 | 45 | A List using the Style 'funkyStyle': 46 | ------------------------------------ 47 | t GAME OF THRONES 48 | |f WINTER 49 | |m IS 50 | |b COMING 51 | | f THIS 52 | | m IS 53 | | b KNOWN 54 | b THE DARK TOWER 55 | b THE GUNSLINGER 56 | 57 | A List in HTML format: 58 | ---------------------- 59 | [HTML] 76 | 77 | A List in Markdown format: 78 | -------------------------- 79 | [Markdown] * Game Of Thrones 80 | [Markdown] * Winter 81 | [Markdown] * Is 82 | [Markdown] * Coming 83 | [Markdown] * This 84 | [Markdown] * Is 85 | [Markdown] * Known 86 | [Markdown] * The Dark Tower 87 | [Markdown] * The Gunslinger 88 | 89 | ``` 90 | -------------------------------------------------------------------------------- /cmd/demo-list/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/jedib0t/go-pretty/v6/list" 8 | "github.com/jedib0t/go-pretty/v6/text" 9 | ) 10 | 11 | func demoPrint(title string, content string, prefix string) { 12 | fmt.Printf("%s:\n", title) 13 | fmt.Println(strings.Repeat("-", len(title)+1)) 14 | for _, line := range strings.Split(content, "\n") { 15 | fmt.Printf("%s%s\n", prefix, line) 16 | } 17 | fmt.Println() 18 | } 19 | 20 | func main() { 21 | //========================================================================== 22 | // Initialization 23 | //========================================================================== 24 | l := list.NewWriter() 25 | // you can also instantiate the object directly 26 | lTemp := list.List{} 27 | lTemp.Render() // just to avoid the compile error of not using the object 28 | //========================================================================== 29 | 30 | //========================================================================== 31 | // A List needs Items. 32 | //========================================================================== 33 | l.AppendItem("Game Of Thrones") 34 | l.AppendItem("The Dark Tower") 35 | demoPrint("A Simple List", l.Render(), "") 36 | //A Simple List: 37 | //-------------- 38 | //* Game Of Thrones 39 | //* The Dark Tower 40 | l.Reset() 41 | //========================================================================== 42 | 43 | //========================================================================== 44 | // I wanna Level Down! 45 | //========================================================================== 46 | l.AppendItem("Game Of Thrones") 47 | l.Indent() 48 | l.AppendItems([]interface{}{"Winter", "Is", "Coming"}) 49 | l.Indent() 50 | l.AppendItems([]interface{}{"This", "Is", "Known"}) 51 | l.UnIndent() 52 | l.UnIndent() 53 | l.AppendItem("The Dark Tower") 54 | l.Indent() 55 | l.AppendItem("The Gunslinger") 56 | demoPrint("A Multi-level List", l.Render(), "") 57 | //A Multi-level List: 58 | //------------------- 59 | //* Game Of Thrones 60 | // * Winter 61 | // * Is 62 | // * Coming 63 | // * This 64 | // * Is 65 | // * Known 66 | //* The Dark Tower 67 | // * The Gunslinger 68 | //========================================================================== 69 | 70 | //========================================================================== 71 | // I am Fancy! 72 | //========================================================================== 73 | l.SetStyle(list.StyleBulletCircle) 74 | demoPrint("A List using the Style 'StyleBulletCircle'", l.Render(), "") 75 | //A List using the Style 'StyleBulletCircle': 76 | //------------------------------------------- 77 | //● Game Of Thrones 78 | // ● Winter 79 | // ● Is 80 | // ● Coming 81 | // ● This 82 | // ● Is 83 | // ● Known 84 | //● The Dark Tower 85 | // ● The Gunslinger 86 | l.SetStyle(list.StyleConnectedRounded) 87 | demoPrint("A List using the Style 'StyleConnectedRounded'", l.Render(), "") 88 | //A List using the Style 'StyleConnectedRounded': 89 | //----------------------------------------------- 90 | //╭─ Game Of Thrones 91 | //├─┬─ Winter 92 | //│ ├─ Is 93 | //│ ├─ Coming 94 | //│ ╰─┬─ This 95 | //│ ├─ Is 96 | //│ ╰─ Known 97 | //├─ The Dark Tower 98 | //╰─── The Gunslinger 99 | //========================================================================== 100 | 101 | //========================================================================== 102 | // I want my own Style! 103 | //========================================================================== 104 | funkyStyle := list.Style{ 105 | CharItemSingle: "s", 106 | CharItemTop: "t", 107 | CharItemFirst: "f", 108 | CharItemMiddle: "m", 109 | CharItemVertical: "|", 110 | CharItemBottom: "b", 111 | CharNewline: "\n", 112 | Format: text.FormatUpper, 113 | LinePrefix: "", 114 | Name: "styleTest", 115 | } 116 | l.SetStyle(funkyStyle) 117 | demoPrint("A List using the Style 'funkyStyle'", l.Render(), "") 118 | //A List using the Style 'funkyStyle': 119 | //------------------------------------ 120 | //t GAME OF THRONES 121 | //|f WINTER 122 | //|m IS 123 | //|b COMING 124 | //| f THIS 125 | //| m IS 126 | //| b KNOWN 127 | //b THE DARK TOWER 128 | // b THE GUNSLINGER 129 | //========================================================================== 130 | 131 | //========================================================================== 132 | // I want to use it in a HTML file! 133 | //========================================================================== 134 | demoPrint("A List in HTML format", l.RenderHTML(), "[HTML] ") 135 | //A List in HTML format: 136 | //---------------------- 137 | //[HTML] 154 | //========================================================================== 155 | 156 | //========================================================================== 157 | // Can I get the list in Markdown format? 158 | //========================================================================== 159 | demoPrint("A List in Markdown format", l.RenderMarkdown(), "[Markdown] ") 160 | fmt.Println() 161 | //A List in Markdown format: 162 | //-------------------------- 163 | //[Markdown] * Game Of Thrones 164 | //[Markdown] * Winter 165 | //[Markdown] * Is 166 | //[Markdown] * Coming 167 | //[Markdown] * This 168 | //[Markdown] * Is 169 | //[Markdown] * Known 170 | //[Markdown] * The Dark Tower 171 | //[Markdown] * The Gunslinger 172 | //========================================================================== 173 | } 174 | -------------------------------------------------------------------------------- /cmd/demo-progress/README.md: -------------------------------------------------------------------------------- 1 | Output of `go run cmd/demo-list/demo.go`: 2 | 3 | Progress Demo in a Terminal -------------------------------------------------------------------------------- /cmd/demo-progress/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/jedib0t/go-pretty/v6/progress" 10 | "github.com/jedib0t/go-pretty/v6/text" 11 | ) 12 | 13 | var ( 14 | flagAutoStop = flag.Bool("auto-stop", false, "Auto-stop rendering?") 15 | flagHideETA = flag.Bool("hide-eta", false, "Hide the ETA?") 16 | flagHideETAOverall = flag.Bool("hide-eta-overall", false, "Hide the ETA in the overall tracker?") 17 | flagHideOverallTracker = flag.Bool("hide-overall", false, "Hide the Overall Tracker?") 18 | flagHidePercentage = flag.Bool("hide-percentage", false, "Hide the progress percent?") 19 | flagHideTime = flag.Bool("hide-time", false, "Hide the time taken?") 20 | flagHideValue = flag.Bool("hide-value", false, "Hide the tracker value?") 21 | flagNumTrackers = flag.Int("num-trackers", 13, "Number of Trackers") 22 | flagShowSpeed = flag.Bool("show-speed", false, "Show the tracker speed?") 23 | flagShowSpeedOverall = flag.Bool("show-speed-overall", false, "Show the overall tracker speed?") 24 | flagShowPinned = flag.Bool("show-pinned", false, "Show a pinned message?") 25 | flagRandomFail = flag.Bool("rnd-fail", false, "Introduce random failures in tracking") 26 | flagRandomDefer = flag.Bool("rnd-defer", false, "Introduce random deferred starts") 27 | flagRandomRemove = flag.Bool("rnd-remove", false, "Introduce random remove of trackers on completion") 28 | flagRandomLogs = flag.Bool("rnd-logs", false, "Output random logs in the middle of tracking") 29 | 30 | messageColors = []text.Color{ 31 | text.FgRed, 32 | text.FgGreen, 33 | text.FgYellow, 34 | text.FgBlue, 35 | text.FgMagenta, 36 | text.FgCyan, 37 | text.FgWhite, 38 | } 39 | rng = rand.New(rand.NewSource(time.Now().UnixNano())) 40 | timeStart = time.Now() 41 | ) 42 | 43 | func getMessage(idx int64, units *progress.Units) string { 44 | var message string 45 | switch units { 46 | case &progress.UnitsBytes: 47 | message = fmt.Sprintf("Downloading File #%3d", idx) 48 | case &progress.UnitsCurrencyDollar, &progress.UnitsCurrencyEuro, &progress.UnitsCurrencyPound: 49 | message = fmt.Sprintf("Transferring Amount #%3d", idx) 50 | default: 51 | message = fmt.Sprintf("Calculating Total #%3d", idx) 52 | } 53 | return message 54 | } 55 | 56 | func getUnits(idx int64) *progress.Units { 57 | var units *progress.Units 58 | switch { 59 | case idx%5 == 0: 60 | units = &progress.UnitsCurrencyPound 61 | case idx%4 == 0: 62 | units = &progress.UnitsCurrencyDollar 63 | case idx%3 == 0: 64 | units = &progress.UnitsBytes 65 | default: 66 | units = &progress.UnitsDefault 67 | } 68 | return units 69 | } 70 | 71 | func trackSomething(pw progress.Writer, idx int64, updateMessage bool) { 72 | total := idx * idx * idx * 250 73 | incrementPerCycle := idx * int64(*flagNumTrackers) * 250 74 | 75 | units := getUnits(idx) 76 | message := getMessage(idx, units) 77 | tracker := progress.Tracker{ 78 | DeferStart: *flagRandomDefer && rng.Float64() < 0.5, 79 | Message: message, 80 | RemoveOnCompletion: *flagRandomRemove && rng.Float64() < 0.25, 81 | Total: total, 82 | Units: *units, 83 | } 84 | if idx == int64(*flagNumTrackers) { 85 | tracker.Total = 0 86 | } 87 | 88 | pw.AppendTracker(&tracker) 89 | 90 | if tracker.DeferStart { 91 | time.Sleep(3 * time.Second) 92 | tracker.Start() 93 | } 94 | 95 | ticker := time.Tick(time.Millisecond * 500) 96 | updateTicker := time.Tick(time.Millisecond * 250) 97 | for !tracker.IsDone() { 98 | select { 99 | case <-ticker: 100 | tracker.Increment(incrementPerCycle) 101 | if idx == int64(*flagNumTrackers) && tracker.Value() >= total { 102 | tracker.MarkAsDone() 103 | } else if *flagRandomFail && rand.Float64() < 0.1 { 104 | tracker.MarkAsErrored() 105 | } 106 | pw.SetPinnedMessages( 107 | fmt.Sprintf(">> Current Time: %-32s", time.Now().Format(time.RFC3339)), 108 | fmt.Sprintf(">> Total Time: %-32s", time.Since(timeStart).Round(time.Millisecond)), 109 | ) 110 | case <-updateTicker: 111 | if updateMessage { 112 | rndIdx := rand.Intn(len(messageColors)) 113 | if rndIdx == len(messageColors) { 114 | rndIdx-- 115 | } 116 | tracker.UpdateMessage(messageColors[rndIdx].Sprint(message)) 117 | } 118 | } 119 | } 120 | } 121 | 122 | func main() { 123 | flag.Parse() 124 | fmt.Printf("Tracking Progress of %d trackers ...\n\n", *flagNumTrackers) 125 | 126 | // instantiate a Progress Writer and set up the options 127 | pw := progress.NewWriter() 128 | pw.SetAutoStop(*flagAutoStop) 129 | pw.SetMessageLength(24) 130 | pw.SetNumTrackersExpected(*flagNumTrackers) 131 | pw.SetSortBy(progress.SortByPercentDsc) 132 | pw.SetStyle(progress.StyleDefault) 133 | pw.SetTrackerLength(25) 134 | pw.SetTrackerPosition(progress.PositionRight) 135 | pw.SetUpdateFrequency(time.Millisecond * 100) 136 | pw.Style().Colors = progress.StyleColorsExample 137 | pw.Style().Options.PercentFormat = "%4.1f%%" 138 | pw.Style().Visibility.ETA = !*flagHideETA 139 | pw.Style().Visibility.ETAOverall = !*flagHideETAOverall 140 | pw.Style().Visibility.Percentage = !*flagHidePercentage 141 | pw.Style().Visibility.Speed = *flagShowSpeed 142 | pw.Style().Visibility.SpeedOverall = *flagShowSpeedOverall 143 | pw.Style().Visibility.Time = !*flagHideTime 144 | pw.Style().Visibility.TrackerOverall = !*flagHideOverallTracker 145 | pw.Style().Visibility.Value = !*flagHideValue 146 | pw.Style().Visibility.Pinned = *flagShowPinned 147 | 148 | // call Render() in async mode; yes we don't have any trackers at the moment 149 | go pw.Render() 150 | 151 | // add a bunch of trackers with random parameters to demo most of the 152 | // features available; do this in async too like a client might do (for ex. 153 | // when downloading a bunch of files in parallel) 154 | for idx := int64(1); idx <= int64(*flagNumTrackers); idx++ { 155 | go trackSomething(pw, idx, idx == int64(*flagNumTrackers)) 156 | 157 | // in auto-stop mode, the Render logic terminates the moment it detects 158 | // zero active trackers; but in a manual-stop mode, it keeps waiting and 159 | // is a good chance to demo trackers being added dynamically while other 160 | // trackers are active or done 161 | if !*flagAutoStop { 162 | time.Sleep(time.Millisecond * 100) 163 | } 164 | } 165 | 166 | // wait for one or more trackers to become active (just blind-wait for a 167 | // second) and then keep watching until Rendering is in progress 168 | time.Sleep(time.Second) 169 | messagesLogged := make(map[string]bool) 170 | for pw.IsRenderInProgress() { 171 | if *flagRandomLogs && pw.LengthDone()%3 == 0 { 172 | logMsg := text.Faint.Sprintf("[INFO] done with %d trackers", pw.LengthDone()) 173 | if !messagesLogged[logMsg] { 174 | pw.Log(logMsg) 175 | messagesLogged[logMsg] = true 176 | } 177 | } 178 | 179 | // for manual-stop mode, stop when there are no more active trackers 180 | if !*flagAutoStop && pw.LengthActive() == 0 { 181 | pw.Stop() 182 | } 183 | time.Sleep(time.Millisecond * 100) 184 | } 185 | 186 | fmt.Println("\nAll done!") 187 | } 188 | -------------------------------------------------------------------------------- /cmd/demo-table/demo-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedib0t/go-pretty/18e8a019b34e7e802e7f0c2cd78d2f63f7840689/cmd/demo-table/demo-colors.png -------------------------------------------------------------------------------- /cmd/profile-list/profile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/jedib0t/go-pretty/v6/list" 9 | "github.com/pkg/profile" 10 | ) 11 | 12 | var ( 13 | listItem1 = "Game Of Thrones" 14 | listItems2 = []interface{}{"Winter", "Is", "Coming"} 15 | listItems3 = []interface{}{"This", "Is", "Known"} 16 | profilers = []func(*profile.Profile){ 17 | profile.CPUProfile, 18 | profile.MemProfileRate(512), 19 | } 20 | ) 21 | 22 | func profileRender(profiler func(profile2 *profile.Profile), n int) { 23 | defer profile.Start(profiler, profile.ProfilePath("./")).Stop() 24 | 25 | for i := 0; i < n; i++ { 26 | lw := list.NewWriter() 27 | lw.AppendItem(listItem1) 28 | lw.Indent() 29 | lw.AppendItems(listItems2) 30 | lw.Indent() 31 | lw.AppendItems(listItems3) 32 | lw.Render() 33 | } 34 | } 35 | 36 | func main() { 37 | numRenders := 100000 38 | if len(os.Args) > 1 { 39 | var err error 40 | numRenders, err = strconv.Atoi(os.Args[2]) 41 | if err != nil { 42 | fmt.Printf("Invalid Argument: '%s'\n", os.Args[2]) 43 | os.Exit(1) 44 | } 45 | } 46 | 47 | for _, profiler := range profilers { 48 | profileRender(profiler, numRenders) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cmd/profile-progress/profile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/jedib0t/go-pretty/v6/progress" 11 | "github.com/pkg/profile" 12 | ) 13 | 14 | var ( 15 | tracker1 = progress.Tracker{Message: "Calculating Total # 1", Total: 1000, Units: progress.UnitsDefault} 16 | tracker2 = progress.Tracker{Message: "Downloading File # 2", Total: 1000, Units: progress.UnitsBytes} 17 | tracker3 = progress.Tracker{Message: "Transferring Amount # 3", Total: 1000, Units: progress.UnitsCurrencyDollar} 18 | profilers = []func(*profile.Profile){ 19 | profile.CPUProfile, 20 | profile.MemProfileRate(512), 21 | } 22 | ) 23 | 24 | func profileRender(profiler func(profile2 *profile.Profile), n int) { 25 | defer profile.Start(profiler, profile.ProfilePath("./")).Stop() 26 | 27 | trackSomething := func(pw progress.Writer, tracker *progress.Tracker) { 28 | tracker.Reset() 29 | pw.AppendTracker(tracker) 30 | time.Sleep(time.Millisecond * 100) 31 | tracker.Increment(tracker.Total / 2) 32 | time.Sleep(time.Millisecond * 100) 33 | tracker.Increment(tracker.Total / 2) 34 | } 35 | 36 | for i := 0; i < n; i++ { 37 | pw := progress.NewWriter() 38 | pw.SetAutoStop(true) 39 | pw.SetOutputWriter(io.Discard) 40 | go trackSomething(pw, &tracker1) 41 | go trackSomething(pw, &tracker2) 42 | go trackSomething(pw, &tracker3) 43 | time.Sleep(time.Millisecond * 50) 44 | pw.Render() 45 | } 46 | } 47 | 48 | func main() { 49 | numRenders := 5 50 | if len(os.Args) > 1 { 51 | var err error 52 | numRenders, err = strconv.Atoi(os.Args[2]) 53 | if err != nil { 54 | fmt.Printf("Invalid Argument: '%s'\n", os.Args[2]) 55 | os.Exit(1) 56 | } 57 | } 58 | 59 | for _, profiler := range profilers { 60 | profileRender(profiler, numRenders) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cmd/profile-table/profile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/jedib0t/go-pretty/v6/table" 9 | "github.com/pkg/profile" 10 | ) 11 | 12 | var ( 13 | profilers = []func(*profile.Profile){ 14 | profile.CPUProfile, 15 | profile.MemProfileRate(512), 16 | } 17 | tableCaption = "Profiling a Simple Table." 18 | tableRowFooter = table.Row{"", "", "Total", 10000} 19 | tableRowHeader = table.Row{"#", "First Name", "Last Name", "Salary"} 20 | tableRows = []table.Row{ 21 | {1, "Arya", "Stark", 3000}, 22 | {20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"}, 23 | {300, "Tyrion", "Lannister", 5000}, 24 | } 25 | ) 26 | 27 | func profileRender(profiler func(profile2 *profile.Profile), n int) { 28 | defer profile.Start(profiler, profile.ProfilePath(".")).Stop() 29 | 30 | for i := 0; i < n; i++ { 31 | tw := table.NewWriter() 32 | tw.AppendHeader(tableRowHeader) 33 | tw.AppendRows(tableRows) 34 | tw.AppendFooter(tableRowFooter) 35 | tw.SetCaption(tableCaption) 36 | tw.Render() 37 | tw.RenderCSV() 38 | tw.RenderHTML() 39 | tw.RenderMarkdown() 40 | } 41 | } 42 | 43 | func main() { 44 | numRenders := 100000 45 | if len(os.Args) > 1 { 46 | var err error 47 | numRenders, err = strconv.Atoi(os.Args[2]) 48 | if err != nil { 49 | fmt.Printf("Invalid Argument: '%s'\n", os.Args[2]) 50 | os.Exit(1) 51 | } 52 | } 53 | 54 | for _, profiler := range profilers { 55 | profileRender(profiler, numRenders) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jedib0t/go-pretty/v6 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/mattn/go-runewidth v0.0.16 7 | github.com/pkg/profile v1.7.0 8 | github.com/stretchr/testify v1.10.0 9 | golang.org/x/sys v0.30.0 10 | golang.org/x/term v0.29.0 11 | golang.org/x/text v0.22.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/felixge/fgprof v0.9.5 // indirect 17 | github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/rivo/uniseg v0.4.7 // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /list/README.md: -------------------------------------------------------------------------------- 1 | ## List 2 | [![Go Reference](https://pkg.go.dev/badge/github.com/jedib0t/go-pretty/v6/list.svg)](https://pkg.go.dev/github.com/jedib0t/go-pretty/v6/list) 3 | 4 | Pretty-print lists with multiple levels/indents into ASCII/Unicode strings. 5 | 6 | - Append Items one-by-one or as a group 7 | - Indent/UnIndent as you like 8 | - Support Items with Multiple-lines 9 | - Mirror output to an io.Writer object (like os.StdOut) 10 | - Completely customizable styles 11 | - Many ready-to-use styles: [style.go](style.go) 12 | - Render as: 13 | - (ASCII/Unicode) List 14 | - HTML List (with custom CSS Class) 15 | - Markdown List 16 | 17 | ``` 18 | ■ Game Of Thrones 19 | ■ Winter 20 | ■ Is 21 | ■ Coming 22 | ■ This 23 | ■ Is 24 | ■ Known 25 | ■ The Dark Tower 26 | ■ The Gunslinger 27 | ``` 28 | 29 | A demonstration of all the capabilities can be found here: 30 | [../cmd/demo-list](../cmd/demo-list) 31 | -------------------------------------------------------------------------------- /list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "unicode/utf8" 8 | 9 | "github.com/jedib0t/go-pretty/v6/text" 10 | ) 11 | 12 | const ( 13 | // DefaultHTMLCSSClass stores the css-class to use when none-provided via 14 | // SetHTMLCSSClass(cssClass string). 15 | DefaultHTMLCSSClass = "go-pretty-table" 16 | ) 17 | 18 | // listItem represents one line in the List 19 | type listItem struct { 20 | Level int 21 | Text string 22 | } 23 | 24 | // List helps print a 2-dimensional array in a human-readable pretty-List. 25 | type List struct { 26 | // approxSize stores the approximate output length/size 27 | approxSize int 28 | // htmlCSSClass stores the HTML CSS Class to use on the