├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ ├── lint.yml │ └── coverage.yml ├── go.mod ├── .gitignore ├── terminal.go ├── terminal_test.go ├── examples ├── singlebar │ └── single.go └── multibar │ └── multi.go ├── go.sum ├── LICENSE ├── README.md ├── progressbar.go └── progressbar_test.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: muesli 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/muesli/goprogressbar 2 | 3 | go 1.12 4 | 5 | require golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 6 | -------------------------------------------------------------------------------- /.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 | *~ 27 | examples/multibar/multibar 28 | examples/singlebar/singlebar 29 | -------------------------------------------------------------------------------- /terminal.go: -------------------------------------------------------------------------------- 1 | /* 2 | * goprogressbar 3 | * Copyright (c) 2016-2017, Christian Muehlhaeuser 4 | * 5 | * For license see LICENSE 6 | */ 7 | 8 | package goprogressbar 9 | 10 | import "fmt" 11 | 12 | func clearCurrentLine() { 13 | fmt.Fprintf(Stdout, "\033[2K\r") 14 | } 15 | 16 | func moveCursorUp(lines uint) { 17 | fmt.Fprintf(Stdout, "\033[%dA", lines) 18 | } 19 | 20 | func moveCursorDown(lines uint) { 21 | fmt.Fprintf(Stdout, "\033[%dB", lines) 22 | } 23 | -------------------------------------------------------------------------------- /terminal_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * goprogressbar 3 | * Copyright (c) 2016-2017, Christian Muehlhaeuser 4 | * 5 | * For license see LICENSE 6 | */ 7 | 8 | package goprogressbar 9 | 10 | import ( 11 | "bytes" 12 | "testing" 13 | ) 14 | 15 | func TestCursorMovement(t *testing.T) { 16 | buf := &bytes.Buffer{} 17 | Stdout = buf 18 | 19 | moveCursorUp(5) 20 | if buf.String() != "\033[5A" { 21 | t.Errorf("Unexpected cursor up movement behaviour") 22 | } 23 | buf.Reset() 24 | 25 | moveCursorDown(5) 26 | if buf.String() != "\033[5B" { 27 | t.Errorf("Unexpected cursor down movement behaviour") 28 | } 29 | buf.Reset() 30 | } 31 | -------------------------------------------------------------------------------- /examples/singlebar/single.go: -------------------------------------------------------------------------------- 1 | /* 2 | * goprogressbar 3 | * Copyright (c) 2016-2017, Christian Muehlhaeuser 4 | * 5 | * For license see LICENSE 6 | */ 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "time" 13 | 14 | "github.com/muesli/goprogressbar" 15 | ) 16 | 17 | func main() { 18 | pb := &goprogressbar.ProgressBar{ 19 | Text: "Current Progress", 20 | Total: 1000, 21 | Current: 0, 22 | Width: 60, 23 | } 24 | 25 | for i := 1; i <= 1000; i++ { 26 | pb.PrependText = fmt.Sprintf("%d of %d", i, pb.Total) 27 | pb.Current = int64(i) 28 | 29 | time.Sleep(23 * time.Millisecond) 30 | pb.LazyPrint() 31 | } 32 | 33 | fmt.Println() 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | strategy: 7 | matrix: 8 | go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x] 9 | os: [ubuntu-latest, macos-latest, windows-latest] 10 | runs-on: ${{ matrix.os }} 11 | env: 12 | GO111MODULE: "on" 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | 19 | - name: Checkout code 20 | uses: actions/checkout@v1 21 | 22 | - name: Download Go modules 23 | run: go mod download 24 | 25 | - name: Build 26 | run: go build -v ./... 27 | 28 | - name: Test 29 | run: go test ./... 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 3 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 4 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 5 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 6 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 7 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 8 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 9 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | golangci: 8 | name: lint 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: golangci-lint 13 | uses: golangci/golangci-lint-action@v2 14 | with: 15 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 16 | version: v1.30 17 | # Optional: golangci-lint command line arguments. 18 | args: --issues-exit-code=0 19 | # Optional: working directory, useful for monorepos 20 | # working-directory: somedir 21 | # Optional: show only new issues if it's a pull request. The default value is `false`. 22 | only-new-issues: true 23 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | coverage: 6 | strategy: 7 | matrix: 8 | go-version: [1.15.x] 9 | os: [ubuntu-latest] 10 | runs-on: ${{ matrix.os }} 11 | env: 12 | GO111MODULE: "on" 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | 19 | - name: Checkout code 20 | uses: actions/checkout@v1 21 | 22 | - name: Coverage 23 | env: 24 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | run: | 26 | go test -race -covermode atomic -coverprofile=profile.cov ./... 27 | GO111MODULE=off go get github.com/mattn/goveralls 28 | $(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Christian Muehlhaeuser 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 | -------------------------------------------------------------------------------- /examples/multibar/multi.go: -------------------------------------------------------------------------------- 1 | /* 2 | * goprogressbar 3 | * Copyright (c) 2016-2017, Christian Muehlhaeuser 4 | * 5 | * For license see LICENSE 6 | */ 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "strconv" 13 | "time" 14 | 15 | "github.com/muesli/goprogressbar" 16 | ) 17 | 18 | func main() { 19 | mpb := goprogressbar.MultiProgressBar{} 20 | 21 | for i := 0; i < 10; i++ { 22 | pb := &goprogressbar.ProgressBar{ 23 | Text: "Progress " + strconv.FormatInt(int64(i+1), 10), 24 | Total: 100, 25 | Current: 0, 26 | Width: 60, 27 | PrependTextFunc: func(p *goprogressbar.ProgressBar) string { 28 | return fmt.Sprintf("%d of %d", p.Current, p.Total) 29 | }, 30 | } 31 | 32 | mpb.AddProgressBar(pb) 33 | } 34 | 35 | pb := &goprogressbar.ProgressBar{ 36 | Text: "Overall Progress", 37 | Total: 1000, 38 | Current: 0, 39 | Width: 60, 40 | } 41 | mpb.AddProgressBar(pb) 42 | 43 | // fill progress bars one after another 44 | for j := 0; j < 10; j++ { 45 | for i := 1; i <= 100; i++ { 46 | p := mpb.ProgressBars[j] 47 | p.Current = int64(i) 48 | pb.Current++ 49 | 50 | mpb.LazyPrint() 51 | time.Sleep(23 * time.Millisecond) 52 | } 53 | } 54 | 55 | fmt.Println() 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | goprogressbar 2 | ============= 3 | 4 | [![Latest Release](https://img.shields.io/github/release/muesli/goprogressbar.svg)](https://github.com/muesli/goprogressbar/releases) 5 | [![Build Status](https://github.com/muesli/goprogressbar/workflows/build/badge.svg)](https://github.com/muesli/goprogressbar/actions) 6 | [![Coverage Status](https://coveralls.io/repos/github/muesli/goprogressbar/badge.svg?branch=master)](https://coveralls.io/github/muesli/goprogressbar?branch=master) 7 | [![Go ReportCard](http://goreportcard.com/badge/muesli/goprogressbar)](http://goreportcard.com/report/muesli/goprogressbar) 8 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://pkg.go.dev/github.com/muesli/goprogressbar) 9 | 10 | Golang helper to print one or many progress bars on the console 11 | 12 | ## Installation 13 | 14 | Make sure you have a working Go environment (Go 1.9 or higher is required). 15 | See the [install instructions](http://golang.org/doc/install.html). 16 | 17 | To install goprogressbar, simply run: 18 | 19 | go get github.com/muesli/goprogressbar 20 | 21 | ## Example 22 | 23 | ```go 24 | package main 25 | 26 | import ( 27 | "fmt" 28 | "strconv" 29 | "time" 30 | 31 | "github.com/muesli/goprogressbar" 32 | ) 33 | 34 | func main() { 35 | mpb := goprogressbar.MultiProgressBar{} 36 | 37 | for i := 0; i < 10; i++ { 38 | pb := &goprogressbar.ProgressBar{ 39 | Text: "Progress " + strconv.FormatInt(int64(i+1), 10), 40 | Total: 100, 41 | Current: 0, 42 | Width: 60, 43 | } 44 | 45 | mpb.AddProgressBar(pb) 46 | } 47 | 48 | pb := &goprogressbar.ProgressBar{ 49 | Text: "Overall Progress", 50 | Total: 1000, 51 | Current: 0, 52 | Width: 60, 53 | } 54 | mpb.AddProgressBar(pb) 55 | 56 | // fill progress bars one after another 57 | for j := 0; j < 10; j++ { 58 | for i := 1; i <= 100; i++ { 59 | p := mpb.ProgressBars[j] 60 | p.Current = int64(i) 61 | p.RightAlignedText = fmt.Sprintf("%d of %d", i, p.Total) 62 | 63 | pb.Current++ 64 | 65 | mpb.LazyPrint() 66 | time.Sleep(23 * time.Millisecond) 67 | } 68 | } 69 | 70 | fmt.Println() 71 | } 72 | ``` 73 | 74 | ## What it looks like 75 | ``` 76 | Progress 1 100 of 100 [#################################################] 100.00% 77 | Progress 2 100 of 100 [#################################################] 100.00% 78 | Progress 3 89 of 100 [###########################################>-----] 89.00% 79 | Progress 4 [#>-----------------------------------------------] 0.00% 80 | Progress 5 [#>-----------------------------------------------] 0.00% 81 | Progress 6 [#>-----------------------------------------------] 0.00% 82 | Progress 7 [#>-----------------------------------------------] 0.00% 83 | Progress 8 [#>-----------------------------------------------] 0.00% 84 | Progress 9 [#>-----------------------------------------------] 0.00% 85 | Progress 10 [#>-----------------------------------------------] 0.00% 86 | Overall Progress [#############>-----------------------------------] 28.90% 87 | ``` 88 | -------------------------------------------------------------------------------- /progressbar.go: -------------------------------------------------------------------------------- 1 | /* 2 | * goprogressbar 3 | * Copyright (c) 2016-2017, Christian Muehlhaeuser 4 | * 5 | * For license see LICENSE 6 | */ 7 | 8 | package goprogressbar 9 | 10 | import ( 11 | "fmt" 12 | "io" 13 | "math" 14 | "os" 15 | "strings" 16 | "syscall" 17 | "time" 18 | "unicode/utf8" 19 | 20 | "golang.org/x/crypto/ssh/terminal" 21 | ) 22 | 23 | const fps = 25 24 | 25 | var ( 26 | // Stdout defines where output gets printed to 27 | Stdout io.Writer = os.Stdout 28 | // BarFormat defines the bar design 29 | BarFormat = "[#>-]" 30 | ) 31 | 32 | // ProgressBar is a helper for printing a progress bar 33 | type ProgressBar struct { 34 | // Text displayed on the very left 35 | Text string 36 | // Text prepending the bar 37 | PrependText string 38 | // Max value (100%) 39 | Total int64 40 | // Current progress value 41 | Current int64 42 | // Desired bar width 43 | Width uint 44 | 45 | // If a PrependTextFunc is set, the PrependText will be automatically 46 | // generated on every print 47 | PrependTextFunc func(p *ProgressBar) string 48 | 49 | lastPrintTime time.Time 50 | } 51 | 52 | // MultiProgressBar is a helper for printing multiple progress bars 53 | type MultiProgressBar struct { 54 | ProgressBars []*ProgressBar 55 | 56 | lastPrintTime time.Time 57 | } 58 | 59 | // percentage returns the percentage bound between 0.0 and 1.0 60 | func (p *ProgressBar) percentage() float64 { 61 | pct := float64(p.Current) / float64(p.Total) 62 | if p.Total == 0 { 63 | if p.Current == 0 { 64 | // When both Total and Current are 0, show a full progressbar 65 | pct = 1 66 | } else { 67 | pct = 0 68 | } 69 | } 70 | 71 | // percentage is bound between 0 and 1 72 | return math.Min(1, math.Max(0, pct)) 73 | } 74 | 75 | // UpdateRequired returns true when this progressbar wants an update regardless 76 | // of fps limitation 77 | func (p *ProgressBar) UpdateRequired() bool { 78 | return p.Current == 0 || p.Current == p.Total 79 | } 80 | 81 | // LazyPrint writes the progress bar to stdout if a significant update occurred 82 | func (p *ProgressBar) LazyPrint() { 83 | now := time.Now() 84 | if p.UpdateRequired() || now.Sub(p.lastPrintTime) > time.Second/fps { 85 | p.lastPrintTime = now 86 | p.Print() 87 | } 88 | } 89 | 90 | // Clear deletes everything on the current terminal line, hence removing a printed progressbar 91 | func (p *ProgressBar) Clear() { 92 | clearCurrentLine() 93 | } 94 | 95 | // Print writes the progress bar to stdout 96 | func (p *ProgressBar) Print() { 97 | if p.PrependTextFunc != nil { 98 | p.PrependText = p.PrependTextFunc(p) 99 | } 100 | pct := p.percentage() 101 | clearCurrentLine() 102 | 103 | pcts := fmt.Sprintf("%.2f%%", pct*100) 104 | for len(pcts) < 7 { 105 | pcts = " " + pcts 106 | } 107 | 108 | tiWidth, _, err := terminal.GetSize(int(syscall.Stdin)) 109 | if tiWidth <= 0 || err != nil { 110 | // we're not running inside a real terminal (e.g. CI) 111 | // we assume a width of 80 112 | tiWidth = 80 113 | } 114 | barWidth := uint(math.Min(float64(p.Width), float64(tiWidth)/2.0)) 115 | 116 | size := int(barWidth) - len(pcts) - 4 117 | fill := int(math.Max(2, math.Floor((float64(size)*pct)+.5))) 118 | if size < 16 { 119 | barWidth = 0 120 | } 121 | 122 | text := p.Text 123 | textLen := utf8.RuneCountInString(p.Text) 124 | maxTextWidth := tiWidth - 3 - int(barWidth) - utf8.RuneCountInString(p.PrependText) 125 | if maxTextWidth < 0 { 126 | maxTextWidth = 0 127 | } 128 | if textLen > maxTextWidth { 129 | if textLen-maxTextWidth+3 < textLen { 130 | text = "..." + string([]rune(p.Text)[textLen-maxTextWidth+3:]) 131 | } else { 132 | text = "" 133 | } 134 | } 135 | 136 | // Print text 137 | s := fmt.Sprintf("%s%s %s ", 138 | text, 139 | strings.Repeat(" ", maxTextWidth-utf8.RuneCountInString(text)), 140 | p.PrependText) 141 | fmt.Fprint(Stdout, s) 142 | 143 | if barWidth > 0 { 144 | progChar := BarFormat[2] 145 | if p.Current == p.Total { 146 | progChar = BarFormat[1] 147 | } 148 | 149 | // Print progress bar 150 | fmt.Fprintf(Stdout, "%c%s%c%s%c %s", 151 | BarFormat[0], 152 | strings.Repeat(string(BarFormat[1]), fill-1), 153 | progChar, 154 | strings.Repeat(string(BarFormat[3]), size-fill), 155 | BarFormat[4], 156 | pcts) 157 | } 158 | } 159 | 160 | // AddProgressBar adds another progress bar to the multi struct 161 | func (mp *MultiProgressBar) AddProgressBar(p *ProgressBar) { 162 | mp.ProgressBars = append(mp.ProgressBars, p) 163 | 164 | if len(mp.ProgressBars) > 1 { 165 | fmt.Println() 166 | } 167 | mp.Print() 168 | } 169 | 170 | // Print writes all progress bars to stdout 171 | func (mp *MultiProgressBar) Print() { 172 | moveCursorUp(uint(len(mp.ProgressBars))) 173 | 174 | for _, p := range mp.ProgressBars { 175 | moveCursorDown(1) 176 | p.Print() 177 | } 178 | } 179 | 180 | // LazyPrint writes all progress bars to stdout if a significant update occurred 181 | func (mp *MultiProgressBar) LazyPrint() { 182 | forced := false 183 | for _, p := range mp.ProgressBars { 184 | if p.UpdateRequired() { 185 | forced = true 186 | break 187 | } 188 | } 189 | 190 | now := time.Now() 191 | if !forced { 192 | forced = now.Sub(mp.lastPrintTime) > time.Second/fps 193 | } 194 | 195 | if forced { 196 | mp.lastPrintTime = now 197 | 198 | moveCursorUp(uint(len(mp.ProgressBars))) 199 | for _, p := range mp.ProgressBars { 200 | moveCursorDown(1) 201 | p.Print() 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /progressbar_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * goprogressbar 3 | * Copyright (c) 2016-2017, Christian Muehlhaeuser 4 | * 5 | * For license see LICENSE 6 | */ 7 | 8 | package goprogressbar 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "testing" 14 | ) 15 | 16 | func TestPercentageBound(t *testing.T) { 17 | p := ProgressBar{Current: -1, Total: 100} 18 | if p.percentage() != 0 { 19 | t.Errorf("percentage should be bound to 0, got: %f", p.percentage()) 20 | } 21 | 22 | p = ProgressBar{Current: 200, Total: 100} 23 | if p.percentage() != 1 { 24 | t.Errorf("percentage should be bound to 1, got: %f", p.percentage()) 25 | } 26 | } 27 | 28 | func TestPercentageSpecialValues(t *testing.T) { 29 | p := ProgressBar{Current: 0, Total: 0} 30 | if p.percentage() != 1 { 31 | t.Errorf("percentage should be 1 when both current and total are 0, got: %f", p.percentage()) 32 | } 33 | 34 | p = ProgressBar{Current: 100, Total: 0} 35 | if p.percentage() != 0 { 36 | t.Errorf("percentage should be 0 when current is greater than 0 but the total is unknown (0), got: %f", p.percentage()) 37 | } 38 | } 39 | 40 | func TestProgressBarOutput(t *testing.T) { 41 | buf := &bytes.Buffer{} 42 | Stdout = buf 43 | 44 | p := ProgressBar{Text: "Test", Current: 0, Total: 100, Width: 60} 45 | p.PrependText = fmt.Sprintf("%d of %d", p.Current, p.Total) 46 | p.Print() 47 | if buf.String() != "\033[2K\rTest 0 of 100 [#>---------------------------] 0.00%" { 48 | t.Errorf("Unexpected progressbar print behaviour") 49 | } 50 | buf.Reset() 51 | 52 | p.Current = 10 53 | p.PrependText = fmt.Sprintf("%d of %d", p.Current, p.Total) 54 | p.Print() 55 | if buf.String() != "\033[2K\rTest 10 of 100 [##>--------------------------] 10.00%" { 56 | t.Errorf("Unexpected progressbar print behaviour") 57 | } 58 | buf.Reset() 59 | 60 | p.Current = 100 61 | p.PrependText = fmt.Sprintf("%d of %d", p.Current, p.Total) 62 | p.Print() 63 | if buf.String() != "\033[2K\rTest 100 of 100 [#############################] 100.00%" { 64 | t.Errorf("Unexpected progressbar print behaviour") 65 | } 66 | buf.Reset() 67 | } 68 | 69 | func TestMultiProgressBarOutput(t *testing.T) { 70 | buf := &bytes.Buffer{} 71 | Stdout = buf 72 | 73 | p1 := ProgressBar{Text: "Test1", Current: 23, Total: 100, Width: 60} 74 | p1.PrependText = fmt.Sprintf("%d of %d", p1.Current, p1.Total) 75 | p2 := ProgressBar{Text: "Test2", Current: 69, Total: 100, Width: 60} 76 | p2.PrependText = fmt.Sprintf("%d of %d", p2.Current, p2.Total) 77 | 78 | mp := MultiProgressBar{} 79 | mp.AddProgressBar(&p1) 80 | mp.AddProgressBar(&p2) 81 | 82 | if buf.String() != "\033[1A\033[1B\033[2K\rTest1 23 of 100 [######>----------------------] 23.00%"+ 83 | "\033[2A\033[1B\033[2K\rTest1 23 of 100 [######>----------------------] 23.00%"+ 84 | "\033[1B\033[2K\rTest2 69 of 100 [###################>---------] 69.00%" { 85 | t.Errorf("Unexpected multi progressbar print behaviour") 86 | } 87 | buf.Reset() 88 | } 89 | 90 | func TestLazyPrint(t *testing.T) { 91 | buf := &bytes.Buffer{} 92 | Stdout = buf 93 | 94 | p := ProgressBar{Text: "Test", Current: 10, Total: 100, Width: 60} 95 | p.PrependText = fmt.Sprintf("%d of %d", p.Current, p.Total) 96 | 97 | // LazyPrint should buffer prints, so we call it twice and check it 98 | // only prints once 99 | p.LazyPrint() 100 | p.LazyPrint() 101 | 102 | if buf.String() != "\033[2K\rTest 10 of 100 [##>--------------------------] 10.00%" { 103 | t.Errorf("Unexpected progressbar print behaviour") 104 | } 105 | buf.Reset() 106 | } 107 | 108 | func TestMultiLazyPrint(t *testing.T) { 109 | buf := &bytes.Buffer{} 110 | Stdout = buf 111 | 112 | p1 := ProgressBar{Text: "Test1", Current: 23, Total: 100, Width: 60} 113 | p1.PrependText = fmt.Sprintf("%d of %d", p1.Current, p1.Total) 114 | p2 := ProgressBar{Text: "Test2", Current: 69, Total: 100, Width: 60} 115 | p2.PrependText = fmt.Sprintf("%d of %d", p2.Current, p2.Total) 116 | 117 | mp := MultiProgressBar{} 118 | mp.AddProgressBar(&p1) 119 | mp.AddProgressBar(&p2) 120 | buf.Reset() 121 | 122 | // LazyPrint should buffer prints, so we call it twice and check it 123 | // only prints once 124 | mp.LazyPrint() 125 | mp.LazyPrint() 126 | 127 | if buf.String() != "\033[2A\033[1B\033[2K\rTest1 23 of 100 [######>----------------------] 23.00%"+ 128 | "\033[1B\033[2K\rTest2 69 of 100 [###################>---------] 69.00%" { 129 | t.Errorf("Unexpected multi progressbar print behaviour") 130 | } 131 | buf.Reset() 132 | } 133 | 134 | func TestPrependFunc(t *testing.T) { 135 | buf := &bytes.Buffer{} 136 | Stdout = buf 137 | 138 | p := ProgressBar{Text: "Test", Current: 10, Total: 100, Width: 60} 139 | p.PrependTextFunc = func(p *ProgressBar) string { 140 | return fmt.Sprintf("%d of %d", p.Current, p.Total) 141 | } 142 | 143 | p.Print() 144 | 145 | if buf.String() != "\033[2K\rTest 10 of 100 [##>--------------------------] 10.00%" { 146 | t.Errorf("Unexpected progressbar print behaviour") 147 | } 148 | buf.Reset() 149 | } 150 | 151 | func TestTextElide(t *testing.T) { 152 | buf := &bytes.Buffer{} 153 | Stdout = buf 154 | 155 | p := ProgressBar{Text: "ThisIsAReallyLongLongStringHere", Current: 10, Total: 100, Width: 60} 156 | p.PrependText = fmt.Sprintf("%d of %d", p.Current, p.Total) 157 | 158 | p.Print() 159 | 160 | if buf.String() != "\033[2K\r...AReallyLongLongStringHere 10 of 100 [##>--------------------------] 10.00%" { 161 | t.Errorf("Unexpected progressbar print behaviour") 162 | } 163 | buf.Reset() 164 | } 165 | 166 | func TestFormat(t *testing.T) { 167 | buf := &bytes.Buffer{} 168 | Stdout = buf 169 | BarFormat = "(+-_)" 170 | 171 | p := ProgressBar{Text: "Test", Current: 10, Total: 100, Width: 60} 172 | p.PrependText = fmt.Sprintf("%d of %d", p.Current, p.Total) 173 | 174 | p.Print() 175 | 176 | if buf.String() != "\033[2K\rTest 10 of 100 (++-__________________________) 10.00%" { 177 | t.Errorf("Unexpected progressbar print behaviour") 178 | } 179 | buf.Reset() 180 | } 181 | --------------------------------------------------------------------------------