From 87c9df09cac5fc5ee3c338a4d3a77af6258aacbe Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Sun, 18 Dec 2022 11:00:59 +0100 Subject: [PATCH 01/26] allow custom spinner by using []string as an input --- progressbar.go | 33 +++++++++++++++++++-------------- progressbar_test.go | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/progressbar.go b/progressbar.go index 4252fa4..7e96486 100644 --- a/progressbar.go +++ b/progressbar.go @@ -93,8 +93,8 @@ type config struct { // clear bar once finished clearOnFinish bool - // spinnerType should be a number between 0-75 - spinnerType int + // spinner is a slice of string to represent the animation + spinner []string // fullWidth specifies whether to measure and set the bar to a specific width fullWidth bool @@ -132,9 +132,9 @@ func OptionSetWidth(s int) Option { } // OptionSpinnerType sets the type of spinner used for indeterminate bars -func OptionSpinnerType(spinnerType int) Option { +func OptionSpinnerType(spinner []string) Option { return func(p *ProgressBar) { - p.config.spinnerType = spinnerType + p.config.spinner = spinner } } @@ -296,7 +296,7 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { throttleDuration: 0 * time.Nanosecond, elapsedTime: true, predictTime: true, - spinnerType: 9, + spinner: GetPresetSpinner(9), invisible: false, }, } @@ -305,10 +305,6 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { o(&b) } - if b.config.spinnerType < 0 || b.config.spinnerType > 75 { - panic("invalid spinner type, must be between 0 and 75") - } - // ignoreLength if max bytes not known if b.config.max == -1 { b.config.ignoreLength = true @@ -359,7 +355,7 @@ func DefaultBytes(maxBytes int64, description ...string) *ProgressBar { OptionOnCompletion(func() { fmt.Fprint(os.Stderr, "\n") }), - OptionSpinnerType(14), + OptionSpinnerType(GetPresetSpinner(14)), OptionFullWidth(), OptionSetRenderBlankState(true), ) @@ -382,7 +378,7 @@ func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar { OptionSetWidth(10), OptionThrottle(65*time.Millisecond), OptionShowCount(), - OptionSpinnerType(14), + OptionSpinnerType(GetPresetSpinner(14)), OptionFullWidth(), ) } @@ -405,7 +401,7 @@ func Default(max int64, description ...string) *ProgressBar { OptionOnCompletion(func() { fmt.Fprint(os.Stderr, "\n") }), - OptionSpinnerType(14), + OptionSpinnerType(GetPresetSpinner(14)), OptionFullWidth(), OptionSetRenderBlankState(true), ) @@ -428,7 +424,7 @@ func DefaultSilent(max int64, description ...string) *ProgressBar { OptionThrottle(65*time.Millisecond), OptionShowCount(), OptionShowIts(), - OptionSpinnerType(14), + OptionSpinnerType(GetPresetSpinner(14)), OptionFullWidth(), ) } @@ -853,7 +849,7 @@ func renderProgressBar(c config, s *state) (int, error) { str := "" if c.ignoreLength { - spinner := spinners[c.spinnerType][int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType])))))] + spinner := c.spinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(c.spinner)))))] if c.elapsedTime { if c.showDescriptionAtLineEnd { str = fmt.Sprintf("\r%s %s [%s] %s ", @@ -1073,3 +1069,12 @@ var termWidth = func() (width int, err error) { } return 0, err } + +// GetPresetSpinner returns an preset spinner. The spinnerType is an integer +// between 0 and 75. +func GetPresetSpinner(spinnerType int) []string { + if spinnerType < 0 || spinnerType > 75 { + panic("invalid spinner type, must be between 0 and 75") + } + return spinners[spinnerType] +} diff --git a/progressbar_test.go b/progressbar_test.go index c818b3d..892c3e6 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -196,7 +196,7 @@ func TestSpinnerType(t *testing.T) { OptionSetDescription("indeterminate spinner"), OptionShowIts(), OptionShowCount(), - OptionSpinnerType(9), + OptionSpinnerType(GetPresetSpinner(9)), ) bar.Reset() for i := 0; i < 10; i++ { From c4c1ddcb97bbe19c94cb1fd6a0df20cb2ef53ef0 Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Wed, 4 Jan 2023 16:47:12 +0100 Subject: [PATCH 02/26] Revert "allow custom spinner by using []string as an input" This reverts commit 87c9df09cac5fc5ee3c338a4d3a77af6258aacbe. --- progressbar.go | 33 ++++++++++++++------------------- progressbar_test.go | 2 +- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/progressbar.go b/progressbar.go index 7e96486..4252fa4 100644 --- a/progressbar.go +++ b/progressbar.go @@ -93,8 +93,8 @@ type config struct { // clear bar once finished clearOnFinish bool - // spinner is a slice of string to represent the animation - spinner []string + // spinnerType should be a number between 0-75 + spinnerType int // fullWidth specifies whether to measure and set the bar to a specific width fullWidth bool @@ -132,9 +132,9 @@ func OptionSetWidth(s int) Option { } // OptionSpinnerType sets the type of spinner used for indeterminate bars -func OptionSpinnerType(spinner []string) Option { +func OptionSpinnerType(spinnerType int) Option { return func(p *ProgressBar) { - p.config.spinner = spinner + p.config.spinnerType = spinnerType } } @@ -296,7 +296,7 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { throttleDuration: 0 * time.Nanosecond, elapsedTime: true, predictTime: true, - spinner: GetPresetSpinner(9), + spinnerType: 9, invisible: false, }, } @@ -305,6 +305,10 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { o(&b) } + if b.config.spinnerType < 0 || b.config.spinnerType > 75 { + panic("invalid spinner type, must be between 0 and 75") + } + // ignoreLength if max bytes not known if b.config.max == -1 { b.config.ignoreLength = true @@ -355,7 +359,7 @@ func DefaultBytes(maxBytes int64, description ...string) *ProgressBar { OptionOnCompletion(func() { fmt.Fprint(os.Stderr, "\n") }), - OptionSpinnerType(GetPresetSpinner(14)), + OptionSpinnerType(14), OptionFullWidth(), OptionSetRenderBlankState(true), ) @@ -378,7 +382,7 @@ func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar { OptionSetWidth(10), OptionThrottle(65*time.Millisecond), OptionShowCount(), - OptionSpinnerType(GetPresetSpinner(14)), + OptionSpinnerType(14), OptionFullWidth(), ) } @@ -401,7 +405,7 @@ func Default(max int64, description ...string) *ProgressBar { OptionOnCompletion(func() { fmt.Fprint(os.Stderr, "\n") }), - OptionSpinnerType(GetPresetSpinner(14)), + OptionSpinnerType(14), OptionFullWidth(), OptionSetRenderBlankState(true), ) @@ -424,7 +428,7 @@ func DefaultSilent(max int64, description ...string) *ProgressBar { OptionThrottle(65*time.Millisecond), OptionShowCount(), OptionShowIts(), - OptionSpinnerType(GetPresetSpinner(14)), + OptionSpinnerType(14), OptionFullWidth(), ) } @@ -849,7 +853,7 @@ func renderProgressBar(c config, s *state) (int, error) { str := "" if c.ignoreLength { - spinner := c.spinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(c.spinner)))))] + spinner := spinners[c.spinnerType][int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType])))))] if c.elapsedTime { if c.showDescriptionAtLineEnd { str = fmt.Sprintf("\r%s %s [%s] %s ", @@ -1069,12 +1073,3 @@ var termWidth = func() (width int, err error) { } return 0, err } - -// GetPresetSpinner returns an preset spinner. The spinnerType is an integer -// between 0 and 75. -func GetPresetSpinner(spinnerType int) []string { - if spinnerType < 0 || spinnerType > 75 { - panic("invalid spinner type, must be between 0 and 75") - } - return spinners[spinnerType] -} diff --git a/progressbar_test.go b/progressbar_test.go index 892c3e6..c818b3d 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -196,7 +196,7 @@ func TestSpinnerType(t *testing.T) { OptionSetDescription("indeterminate spinner"), OptionShowIts(), OptionShowCount(), - OptionSpinnerType(GetPresetSpinner(9)), + OptionSpinnerType(9), ) bar.Reset() for i := 0; i < 10; i++ { From 281ed597faf1a8ea956ea198db2b86a9990278cb Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Wed, 4 Jan 2023 16:56:17 +0100 Subject: [PATCH 03/26] removes redundant new lines --- examples/customization/main.go | 2 +- examples/pacman/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/customization/main.go b/examples/customization/main.go index acb8b26..5b262eb 100644 --- a/examples/customization/main.go +++ b/examples/customization/main.go @@ -38,5 +38,5 @@ func main() { // got notified that progress bar is complete. <-doneCh - fmt.Println("\n ======= progress bar completed ==========\n") + fmt.Println("\n ======= progress bar completed ==========") } diff --git a/examples/pacman/main.go b/examples/pacman/main.go index 7aece4c..0f9aa6b 100644 --- a/examples/pacman/main.go +++ b/examples/pacman/main.go @@ -37,5 +37,5 @@ func main() { // got notified that progress bar is complete. <-doneCh - fmt.Println("\n ======= progress bar completed ==========\n") + fmt.Println("\n ======= progress bar completed ==========") } From dbbc98d4be4e64dc4db2609eabfda75837791314 Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Wed, 4 Jan 2023 17:33:03 +0100 Subject: [PATCH 04/26] check for error --- examples/download/main.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/download/main.go b/examples/download/main.go index 58cc4c5..e84ae9a 100644 --- a/examples/download/main.go +++ b/examples/download/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "io" "net/http" "os" @@ -11,7 +12,7 @@ import ( func main() { req, _ := http.NewRequest("GET", "https://dl.google.com/go/go1.14.2.src.tar.gz", nil) resp, _ := http.DefaultClient.Do(req) - defer resp.Body.Close() + defer check(resp.Body.Close) f, _ := os.OpenFile("go1.14.2.src.tar.gz", os.O_CREATE|os.O_WRONLY, 0644) defer f.Close() @@ -22,3 +23,10 @@ func main() { ) io.Copy(io.MultiWriter(f, bar), resp.Body) } + +// check checks the returned error of a function. +func check(f func() error) { + if err := f(); err != nil { + fmt.Fprintf(os.Stderr, "received error: %v\n", err) + } +} From 24a5250337624d3504299f2218583b94d97d9332 Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Wed, 4 Jan 2023 17:33:23 +0100 Subject: [PATCH 05/26] update tests --- progressbar_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/progressbar_test.go b/progressbar_test.go index c818b3d..ff073a7 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -201,13 +201,56 @@ func TestSpinnerType(t *testing.T) { bar.Reset() for i := 0; i < 10; i++ { time.Sleep(120 * time.Millisecond) - bar.Add(1) + err := bar.Add(1) + if err != nil { + t.Errorf("Successfully tested one spinner option can be used.") + } } if false { t.Errorf("error") } } +func TestSpinnerCustom(t *testing.T) { + bar := NewOptions(-1, + OptionSetWidth(10), + OptionSetDescription("indeterminate spinner"), + OptionShowIts(), + OptionShowCount(), + OptionSpinnerCustom([]string{"🐰", "🐰", "đŸ„•", "đŸ„•"}), + ) + bar.Reset() + for i := 0; i < 10; i++ { + time.Sleep(120 * time.Millisecond) + err := bar.Add(1) + if err != nil { + t.Errorf("Successfully tested one spinner option can be used.") + } + } + if false { + t.Errorf("error") + } +} + +func TestSpinnerTypeAndCustom(t *testing.T) { + bar := NewOptions(-1, + OptionSetWidth(10), + OptionSetDescription("indeterminate spinner"), + OptionShowIts(), + OptionShowCount(), + OptionSpinnerCustom([]string{"🐰", "🐰", "đŸ„•", "đŸ„•"}), + OptionSpinnerType(9), + ) + bar.Reset() + for i := 0; i < 10; i++ { + time.Sleep(120 * time.Millisecond) + err := bar.Add(1) + if err == nil { + t.Errorf("Successfully tested both spinner options cannot be used together.") + } + } +} + func Test_IsFinished(t *testing.T) { isCalled := false bar := NewOptions(72, OptionOnCompletion(func() { From 63fa2459139dec17d9a8e3cdcbc3b52bbfd5e1cb Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Wed, 4 Jan 2023 17:33:54 +0100 Subject: [PATCH 06/26] adds function for custom spinner, makes sure only one spinner option is used at a time --- progressbar.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/progressbar.go b/progressbar.go index 4252fa4..35a090e 100644 --- a/progressbar.go +++ b/progressbar.go @@ -96,6 +96,12 @@ type config struct { // spinnerType should be a number between 0-75 spinnerType int + // spinnerTypeOptionUsed remembers if the spinnerType was changed manually + spinnerTypeOptionUsed bool + + // spinner represents the spinner as a slice of string + spinner []string + // fullWidth specifies whether to measure and set the bar to a specific width fullWidth bool @@ -134,10 +140,19 @@ func OptionSetWidth(s int) Option { // OptionSpinnerType sets the type of spinner used for indeterminate bars func OptionSpinnerType(spinnerType int) Option { return func(p *ProgressBar) { + p.config.spinnerTypeOptionUsed = true p.config.spinnerType = spinnerType } } +// OptionSpinnerCustom sets the spinner used for indeterminate bars to the passed +// slice of string +func OptionSpinnerCustom(spinner []string) Option { + return func(p *ProgressBar) { + p.config.spinner = spinner + } +} + // OptionSetTheme sets the elements the bar is constructed of func OptionSetTheme(t Theme) Option { return func(p *ProgressBar) { @@ -509,6 +524,11 @@ func (p *ProgressBar) Add64(num int64) error { return nil } + // error out since OptionSpinnerCustom will always override a manually set spinnerType + if p.config.spinnerTypeOptionUsed && len(p.config.spinner) > 0 { + return errors.New("OptionSpinnerType and OptionSpinnerCustom cannot be used together") + } + if p.config.max == 0 { return errors.New("max must be greater than 0") } @@ -854,6 +874,9 @@ func renderProgressBar(c config, s *state) (int, error) { if c.ignoreLength { spinner := spinners[c.spinnerType][int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType])))))] + if len(c.spinner) > 0 { + spinner = c.spinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType])))))] + } if c.elapsedTime { if c.showDescriptionAtLineEnd { str = fmt.Sprintf("\r%s %s [%s] %s ", From c1741c23b6278b608894054c492927024718fe53 Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Wed, 4 Jan 2023 18:25:34 +0100 Subject: [PATCH 07/26] small fix --- progressbar.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/progressbar.go b/progressbar.go index 35a090e..9a85bb5 100644 --- a/progressbar.go +++ b/progressbar.go @@ -873,9 +873,10 @@ func renderProgressBar(c config, s *state) (int, error) { str := "" if c.ignoreLength { - spinner := spinners[c.spinnerType][int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType])))))] + index := int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType]))))) + spinner := spinners[c.spinnerType][index] if len(c.spinner) > 0 { - spinner = c.spinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType])))))] + spinner = c.spinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(c.spinner)))))] } if c.elapsedTime { if c.showDescriptionAtLineEnd { From 86b3f6ef9824e67c10175643d233a01385cc86b6 Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Wed, 4 Jan 2023 18:34:02 +0100 Subject: [PATCH 08/26] refactoring --- progressbar.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/progressbar.go b/progressbar.go index 9a85bb5..6768cdf 100644 --- a/progressbar.go +++ b/progressbar.go @@ -873,8 +873,11 @@ func renderProgressBar(c config, s *state) (int, error) { str := "" if c.ignoreLength { - index := int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType]))))) - spinner := spinners[c.spinnerType][index] + selectedSpinner := spinners[c.spinnerType] + if len(c.spinner) > 0 { + selectedSpinner = c.spinner + } + spinner := selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(selectedSpinner)))))] if len(c.spinner) > 0 { spinner = c.spinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(c.spinner)))))] } From a38ef252f56179f5f7c6167c0028cfffbb61a52a Mon Sep 17 00:00:00 2001 From: Nils Beckmann Date: Wed, 4 Jan 2023 18:34:39 +0100 Subject: [PATCH 09/26] remove obsolete code --- progressbar.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/progressbar.go b/progressbar.go index 6768cdf..1c37d30 100644 --- a/progressbar.go +++ b/progressbar.go @@ -878,9 +878,6 @@ func renderProgressBar(c config, s *state) (int, error) { selectedSpinner = c.spinner } spinner := selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(selectedSpinner)))))] - if len(c.spinner) > 0 { - spinner = c.spinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(c.spinner)))))] - } if c.elapsedTime { if c.showDescriptionAtLineEnd { str = fmt.Sprintf("\r%s %s [%s] %s ", From 17ae25cdf22bc402ef34618a2ddddbfdb832ab81 Mon Sep 17 00:00:00 2001 From: Zack Scholl Date: Wed, 4 Jan 2023 21:17:39 -0800 Subject: [PATCH 10/26] update deps --- go.mod | 4 ++-- go.sum | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 834fc4c..f41707c 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/schollz/progressbar/v3 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/rivo/uniseg v0.4.3 // indirect github.com/stretchr/testify v1.3.0 - golang.org/x/term v0.3.0 + golang.org/x/term v0.4.0 ) go 1.13 diff --git a/go.sum b/go.sum index 78e8036..b18a3dc 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= @@ -18,7 +18,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= From abcd9f50b7b64548a6e3f4a8f4f20a4bb6e731c4 Mon Sep 17 00:00:00 2001 From: Keming Date: Thu, 2 Feb 2023 21:46:49 +0800 Subject: [PATCH 11/26] fix data race in `Describe` Signed-off-by: Keming --- progressbar.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/progressbar.go b/progressbar.go index 1c37d30..70a6e4e 100644 --- a/progressbar.go +++ b/progressbar.go @@ -580,6 +580,8 @@ func (p *ProgressBar) Clear() error { // Describe will change the description shown before the progress, which // can be changed on the fly (as for a slow running process). func (p *ProgressBar) Describe(description string) { + p.lock.Lock() + defer p.lock.Unlock() p.config.description = description if p.config.invisible { return From 44ece3a4d7682ae9e61090f995974f5b36a995a9 Mon Sep 17 00:00:00 2001 From: Tal <83217276+talwat@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:02:10 +0100 Subject: [PATCH 12/26] remove duplicate call of term.GetSize --- progressbar.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/progressbar.go b/progressbar.go index 1c37d30..314654a 100644 --- a/progressbar.go +++ b/progressbar.go @@ -1091,9 +1091,6 @@ var termWidth = func() (width int, err error) { if err == nil { return width, nil } - width, _, err = term.GetSize(int(os.Stderr.Fd())) - if err == nil { - return width, nil - } + return 0, err } From e9fb7a4b7029b6d54ea2d9d5053249733fc16f7d Mon Sep 17 00:00:00 2001 From: DarthPesilane Date: Tue, 14 Mar 2023 15:53:57 +0800 Subject: [PATCH 13/26] fix: remove github.com/rivo/uniseg from go.mod to fix go version --- go.mod | 1 - go.sum | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f41707c..83165f6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db - github.com/rivo/uniseg v0.4.3 // indirect github.com/stretchr/testify v1.3.0 golang.org/x/term v0.4.0 ) diff --git a/go.sum b/go.sum index b18a3dc..8dddbe2 100644 --- a/go.sum +++ b/go.sum @@ -11,9 +11,8 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= -github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= From 3d122971646c65a9eff62c04531ec131ffb7ce5a Mon Sep 17 00:00:00 2001 From: Zack Scholl Date: Tue, 14 Mar 2023 04:34:14 -0700 Subject: [PATCH 14/26] update deps --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 83165f6..8349bc1 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/mattn/go-runewidth v0.0.14 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/stretchr/testify v1.3.0 - golang.org/x/term v0.4.0 + golang.org/x/term v0.6.0 ) go 1.13 diff --git a/go.sum b/go.sum index 8dddbe2..2edf9eb 100644 --- a/go.sum +++ b/go.sum @@ -17,7 +17,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= From 0a568bd83b3360763ae6c9cca5f826fc237577a2 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Thu, 28 Sep 2023 08:35:34 +0300 Subject: [PATCH 15/26] Remove redundancies --- progressbar.go | 31 +++++-------------------------- progressbar_test.go | 45 +-------------------------------------------- 2 files changed, 6 insertions(+), 70 deletions(-) diff --git a/progressbar.go b/progressbar.go index cd91ef2..c9b3f4f 100644 --- a/progressbar.go +++ b/progressbar.go @@ -95,12 +95,6 @@ type config struct { // spinnerType should be a number between 0-75 spinnerType int - // spinnerTypeOptionUsed remembers if the spinnerType was changed manually - spinnerTypeOptionUsed bool - - // spinner represents the spinner as a slice of string - spinner []string - // fullWidth specifies whether to measure and set the bar to a specific width fullWidth bool @@ -142,19 +136,10 @@ func OptionSetWidth(s int) Option { // OptionSpinnerType sets the type of spinner used for indeterminate bars func OptionSpinnerType(spinnerType int) Option { return func(p *ProgressBar) { - p.config.spinnerTypeOptionUsed = true p.config.spinnerType = spinnerType } } -// OptionSpinnerCustom sets the spinner used for indeterminate bars to the passed -// slice of string -func OptionSpinnerCustom(spinner []string) Option { - return func(p *ProgressBar) { - p.config.spinner = spinner - } -} - // OptionSetTheme sets the elements the bar is constructed of func OptionSetTheme(t Theme) Option { return func(p *ProgressBar) { @@ -517,11 +502,6 @@ func (p *ProgressBar) Add64(num int64) error { return nil } - // error out since OptionSpinnerCustom will always override a manually set spinnerType - if p.config.spinnerTypeOptionUsed && len(p.config.spinner) > 0 { - return errors.New("OptionSpinnerType and OptionSpinnerCustom cannot be used together") - } - if p.config.max == 0 { return errors.New("max must be greater than 0") } @@ -856,11 +836,7 @@ func renderProgressBar(c config, s *state) (int, error) { str := "" if c.ignoreLength { - selectedSpinner := spinners[c.spinnerType] - if len(c.spinner) > 0 { - selectedSpinner = c.spinner - } - spinner := selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(selectedSpinner)))))] + spinner := spinners[c.spinnerType][int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType])))))] if c.elapsedTime { str = fmt.Sprintf("\r%s %s %s [%s] ", spinner, @@ -1047,6 +1023,9 @@ var termWidth = func() (width int, err error) { if err == nil { return width, nil } - + width, _, err = term.GetSize(int(os.Stderr.Fd())) + if err == nil { + return width, nil + } return 0, err } diff --git a/progressbar_test.go b/progressbar_test.go index 70f8fe3..d1be36c 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -187,56 +187,13 @@ func TestSpinnerType(t *testing.T) { bar.Reset() for i := 0; i < 10; i++ { time.Sleep(120 * time.Millisecond) - err := bar.Add(1) - if err != nil { - t.Errorf("Successfully tested one spinner option can be used.") - } - } - if false { - t.Errorf("error") - } -} - -func TestSpinnerCustom(t *testing.T) { - bar := NewOptions(-1, - OptionSetWidth(10), - OptionSetDescription("indeterminate spinner"), - OptionShowIts(), - OptionShowCount(), - OptionSpinnerCustom([]string{"🐰", "🐰", "đŸ„•", "đŸ„•"}), - ) - bar.Reset() - for i := 0; i < 10; i++ { - time.Sleep(120 * time.Millisecond) - err := bar.Add(1) - if err != nil { - t.Errorf("Successfully tested one spinner option can be used.") - } + bar.Add(1) } if false { t.Errorf("error") } } -func TestSpinnerTypeAndCustom(t *testing.T) { - bar := NewOptions(-1, - OptionSetWidth(10), - OptionSetDescription("indeterminate spinner"), - OptionShowIts(), - OptionShowCount(), - OptionSpinnerCustom([]string{"🐰", "🐰", "đŸ„•", "đŸ„•"}), - OptionSpinnerType(9), - ) - bar.Reset() - for i := 0; i < 10; i++ { - time.Sleep(120 * time.Millisecond) - err := bar.Add(1) - if err == nil { - t.Errorf("Successfully tested both spinner options cannot be used together.") - } - } -} - func Test_IsFinished(t *testing.T) { isCalled := false bar := NewOptions(72, OptionOnCompletion(func() { From a2f6c682d792d6d855ce775826cfcb0a75e31d12 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Mon, 25 Sep 2023 08:25:37 +0300 Subject: [PATCH 16/26] Update CI and deps --- .github/workflows/ci.yml | 35 +++++++++++++++++------------------ go.mod | 17 ++++++++++++----- go.sum | 25 ++++++++++++++----------- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbe85cb..cca9ff4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,26 +1,25 @@ -name: CI +name: build on: push: - branches: [main] + branches: [patch] pull_request: - branches: [main] + branches: [patch] jobs: audit: - name: Audit - runs-on: ubuntu-latest + strategy: + matrix: + go: [1.19.x, 1.20.x, 1.21.x] + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '1.18' - - - name: Checkout repo - uses: actions/checkout@v3 - - - name: Run go vet - run: go vet . - - - name: Run go test - run: go test -v -cover -vet=off . + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + - run: go vet ./... + - run: go test -vet=off ./... + - uses: dominikh/staticcheck-action@v1.3.0 + with: + install-go: false diff --git a/go.mod b/go.mod index 328f7e8..06ec710 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,18 @@ module github.com/schollz/progressbar/v3 +go 1.19 + require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/mattn/go-runewidth v0.0.14 + github.com/mattn/go-runewidth v0.0.15 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db - github.com/stretchr/testify v1.3.0 - golang.org/x/term v0.6.0 + github.com/stretchr/testify v1.8.4 + golang.org/x/term v0.12.0 ) -go 1.13 +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + golang.org/x/sys v0.12.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index e143080..56f3ee3 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,21 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 83456a82484295715fadabbcb9646301d9a4a8c6 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Mon, 25 Sep 2023 08:26:09 +0300 Subject: [PATCH 17/26] Immediate minor fixes --- progressbar.go | 31 ++++++++++++++----------------- progressbar_test.go | 38 +++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/progressbar.go b/progressbar.go index c9b3f4f..0b0c6c0 100644 --- a/progressbar.go +++ b/progressbar.go @@ -194,6 +194,9 @@ func OptionEnableColorCodes(colorCodes bool) Option { func OptionSetElapsedTime(elapsedTime bool) Option { return func(p *ProgressBar) { p.config.elapsedTime = elapsedTime + if !elapsedTime { + p.config.predictTime = false + } } } @@ -670,7 +673,7 @@ func (p *ProgressBar) State() State { if p.state.currentNum > 0 { s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * (float64(p.config.max) - float64(p.state.currentNum)) } - s.KBsPerSecond = float64(p.state.currentBytes) / 1024.0 / s.SecondsSince + s.KBsPerSecond = float64(p.state.currentBytes) / 1024 / s.SecondsSince return s } @@ -767,7 +770,7 @@ func renderProgressBar(c config, s *state) (int, error) { } else if averageRate*60 > 1 { sb.WriteString(fmt.Sprintf("%0.0f %s/min", 60*averageRate, c.iterationString)) } else { - sb.WriteString(fmt.Sprintf("%0.0f %s/hr", 3600*averageRate, c.iterationString)) + sb.WriteString(fmt.Sprintf("%0.0f %s/h", 3600*averageRate, c.iterationString)) } } if sb.Len() > 0 { @@ -779,7 +782,7 @@ func renderProgressBar(c config, s *state) (int, error) { // show time prediction in "current/total" seconds format switch { case c.predictTime: - rightBracNum := (time.Duration((1/averageRate)*(float64(c.max)-float64(s.currentNum))) * time.Second) + rightBracNum := time.Duration((1/averageRate)*(float64(c.max)-float64(s.currentNum))) * time.Second if rightBracNum.Seconds() < 0 { rightBracNum = 0 * time.Second } @@ -806,7 +809,7 @@ func renderProgressBar(c config, s *state) (int, error) { } c.width = width - getStringWidth(c, c.description, true) - 10 - amend - sb.Len() - len(leftBrac) - len(rightBrac) - s.currentSaucerSize = int(float64(s.currentPercent) / 100.0 * float64(c.width)) + s.currentSaucerSize = int(float64(s.currentPercent) / 100 * float64(c.width)) } if s.currentSaucerSize > 0 { if c.ignoreLength { @@ -917,10 +920,7 @@ func clearProgressBar(c config, s state) error { // fill the empty content // to overwrite the progress bar and jump // back to the beginning of the line - str := fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth)) - return writeString(c, str) - // the following does not show correctly if the previous line is longer than subsequent line - // return writeString(c, "\r") + return writeString(c, fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth))) } func writeString(c config, str string) error { @@ -995,21 +995,18 @@ func average(xs []float64) float64 { return total / float64(len(xs)) } +var sizes = []string{" B", " kB", " MB", " GB", " TB", " PB", " EB"} + func humanizeBytes(s float64) (string, string) { - sizes := []string{" B", " kB", " MB", " GB", " TB", " PB", " EB"} - base := 1024.0 if s < 10 { return fmt.Sprintf("%2.0f", s), sizes[0] } - e := math.Floor(logn(float64(s), base)) - suffix := sizes[int(e)] - val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 - f := "%.0f" + e := math.Floor(logn(s, 1024)) + val, suffix := math.Floor(s/math.Pow(1024, e)*10+0.5)/10, sizes[int(e)] if val < 10 { - f = "%.1f" + return fmt.Sprintf("%.1f", val), suffix } - - return fmt.Sprintf(f, val), suffix + return fmt.Sprintf("%.0f", val), suffix } func logn(n, b float64) float64 { diff --git a/progressbar_test.go b/progressbar_test.go index d1be36c..07809da 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -282,7 +282,7 @@ func TestBarFastBytes(t *testing.T) { buf := strings.Builder{} bar := NewOptions64(1e8, OptionShowBytes(true), OptionShowCount(), OptionSetWidth(10), OptionSetWriter(&buf)) time.Sleep(time.Millisecond) - bar.Add(1e7) + bar.Add(2e7) if !strings.Contains(buf.String(), " GB/s)") { t.Errorf("wrong string: %s", buf.String()) } @@ -388,6 +388,22 @@ func TestOptionSetPredictTime(t *testing.T) { } } +func TestOptionSetElapsedTime(t *testing.T) { + buf := strings.Builder{} + bar := NewOptions( + 10, + OptionSetElapsedTime(false), + OptionSetWidth(10), + OptionSetWriter(&buf), + ) + _ = bar.Add(2) + result := strings.TrimSpace(buf.String()) + expect := "20% |██ |" + if result != expect { + t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar) + } +} + func TestOptionSetElapsedTime_spinner(t *testing.T) { buf := strings.Builder{} bar := NewOptions(-1, @@ -666,70 +682,70 @@ func TestOptionFullWidth(t *testing.T) { "\r \r" + "\rProgress: 100% |█████████████████████████████████████████████████████| ", }, - { // 4 + { // 3 []Option{OptionSetPredictTime(false)}, "" + "\r 10% |██████ | " + "\r \r" + "\r 100% |████████████████████████████████████████████████████████████████| ", }, - { // 5 + { // 4 []Option{OptionSetPredictTime(false), OptionShowElapsedTimeOnFinish()}, "" + "\r 10% |██████ | " + "\r \r" + "\r 100% |████████████████████████████████████████████████████████████████| [2s] ", }, - { // 6 + { // 5 []Option{OptionSetPredictTime(false), OptionSetElapsedTime(false)}, "" + "\r 10% |██████ | " + "\r \r" + "\r 100% |█████████████████████████████████████████████████████████████████████| ", }, - { // 7 + { // 6 []Option{OptionShowIts()}, "" + "\r 10% |█████ | (10 it/s) [1s:9s]" + "\r \r" + "\r 100% |█████████████████████████████████████████████████████| (50 it/s)", }, - { // 8 + { // 7 []Option{OptionShowCount()}, "" + "\r 10% |█████ | (10/100) [1s:9s]" + "\r \r" + "\r 100% |█████████████████████████████████████████████████████| (100/100)", }, - { // 9 + { // 8 []Option{OptionShowIts(), OptionShowCount(), OptionShowElapsedTimeOnFinish()}, "" + "\r 10% |████ | (10/100, 10 it/s) [1s:9s]" + "\r \r" + "\r 100% |████████████████████████████████████████████| (100/100, 50 it/s) [2s]", }, - { // 10 + { // 9 []Option{OptionSetDescription("Progress:"), OptionShowIts(), OptionShowCount()}, "" + "\rProgress: 10% |███ | (10/100, 10 it/s) [1s:9s]" + "\r \r" + "\rProgress: 100% |███████████████████████████████████| (100/100, 50 it/s)", }, - { // 12 + { // 10 []Option{OptionShowIts(), OptionShowCount(), OptionSetPredictTime(false)}, "" + "\r 10% |████ | (10/100, 10 it/s) " + "\r \r" + "\r 100% |██████████████████████████████████████████████| (100/100, 50 it/s) ", }, - { // 13 + { // 11 []Option{OptionShowIts(), OptionShowCount(), OptionSetPredictTime(false), OptionShowElapsedTimeOnFinish()}, "" + "\r 10% |████ | (10/100, 10 it/s) " + "\r \r" + "\r 100% |██████████████████████████████████████████████| (100/100, 50 it/s) [2s] ", }, - { // 14 + { // 12 []Option{OptionShowIts(), OptionShowCount(), OptionSetPredictTime(false), OptionSetElapsedTime(false)}, "" + "\r 10% |█████ | (10/100, 10 it/s) " + From f1c087843152e8dc13f26872c244309b2e6f222d Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Mon, 25 Sep 2023 20:27:16 +0300 Subject: [PATCH 18/26] Use base 1000 --- progressbar.go | 6 +++--- progressbar_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/progressbar.go b/progressbar.go index 0b0c6c0..e40ffb1 100644 --- a/progressbar.go +++ b/progressbar.go @@ -673,7 +673,7 @@ func (p *ProgressBar) State() State { if p.state.currentNum > 0 { s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * (float64(p.config.max) - float64(p.state.currentNum)) } - s.KBsPerSecond = float64(p.state.currentBytes) / 1024 / s.SecondsSince + s.KBsPerSecond = float64(p.state.currentBytes) / 1000 / s.SecondsSince return s } @@ -1001,8 +1001,8 @@ func humanizeBytes(s float64) (string, string) { if s < 10 { return fmt.Sprintf("%2.0f", s), sizes[0] } - e := math.Floor(logn(s, 1024)) - val, suffix := math.Floor(s/math.Pow(1024, e)*10+0.5)/10, sizes[int(e)] + e := math.Floor(logn(s, 1000)) + val, suffix := math.Floor(s/math.Pow(1000, e)*10+0.5)/10, sizes[int(e)] if val < 10 { return fmt.Sprintf("%.1f", val), suffix } diff --git a/progressbar_test.go b/progressbar_test.go index 07809da..7aa1d3d 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -266,14 +266,14 @@ func TestBarSmallBytes(t *testing.T) { time.Sleep(100 * time.Millisecond) bar.Add(1000) } - if !strings.Contains(buf.String(), "8.8 kB/95 MB") { + if !strings.Contains(buf.String(), "9.0 kB/100 MB") { t.Errorf("wrong string: %s", buf.String()) } for i := 1; i < 10; i++ { time.Sleep(10 * time.Millisecond) bar.Add(1000000) } - if !strings.Contains(buf.String(), "8.6/95 MB") { + if !strings.Contains(buf.String(), "9.0/100 MB") { t.Errorf("wrong string: %s", buf.String()) } } From 7e98fcd4ba7d9f23abfb8c4fab4e22c284449020 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Wed, 27 Sep 2023 23:28:56 +0300 Subject: [PATCH 19/26] Improve rendering and tidy up tests --- progressbar.go | 131 ++++++++++----------- progressbar_test.go | 276 ++++++++++++++++++-------------------------- 2 files changed, 169 insertions(+), 238 deletions(-) diff --git a/progressbar.go b/progressbar.go index e40ffb1..1a3e54f 100644 --- a/progressbar.go +++ b/progressbar.go @@ -79,8 +79,6 @@ type config struct { // always enabled if predictTime is true. elapsedTime bool - showElapsedTimeOnFinish bool - // whether the progress bar should attempt to predict the finishing // time of the progress based on the start time and the average // number of seconds between increments. @@ -121,6 +119,7 @@ var defaultTheme = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEn var spinners = map[int][]string{ 9: {"|", "/", "-", "\\"}, 14: {"⠋", "⠙", "â č", "â ž", "â Œ", "â Ž", "â Š", "â §", "⠇", "⠏"}, + 59: {". ", ".. ", "...", " ..", " .", " "}, } // Option is the type all options need to adhere to @@ -221,13 +220,6 @@ func OptionShowIts() Option { } } -// OptionShowElapsedOnFinish will keep the display of elapsed time on finish -func OptionShowElapsedTimeOnFinish() Option { - return func(p *ProgressBar) { - p.config.showElapsedTimeOnFinish = true - } -} - // OptionSetItsString sets what's displayed for iterations a second. The default is "it" which would display: "it/s" func OptionSetItsString(iterationString string) Option { return func(p *ProgressBar) { @@ -301,8 +293,8 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { o(&b) } - if b.config.spinnerType != 9 && b.config.spinnerType != 14 { - panic("invalid spinner type, must be 9 or 14") + if b.config.spinnerType != 9 && b.config.spinnerType != 14 && b.config.spinnerType != 59 { + panic("invalid spinner type, must be 9 or 14 or 59") } // ignoreLength if max bytes not known @@ -741,8 +733,10 @@ func renderProgressBar(c config, s *state) (int, error) { if c.showBytes { currentHumanize, currentSuffix := humanizeBytes(s.currentBytes) sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix)) + } else if !s.finished { + sb.WriteString(fmt.Sprintf("%.0f/%s", s.currentBytes, "?")) } else { - sb.WriteString(fmt.Sprintf("%.0f/%s", s.currentBytes, "-")) + sb.WriteString(fmt.Sprintf("%.0f/%.0f", s.currentBytes, s.currentBytes)) } } } @@ -801,16 +795,17 @@ func renderProgressBar(c config, s *state) (int, error) { amend := 1 // an extra space at eol switch { case leftBrac != "" && rightBrac != "": - amend = 4 // space, square brackets and colon + amend += 4 // space, square brackets and colon case leftBrac != "" && rightBrac == "": - amend = 4 // space and square brackets and another space + amend += 3 // space and square brackets case leftBrac == "" && rightBrac != "": - amend = 3 // space and square brackets + amend += 3 // space and square brackets } c.width = width - getStringWidth(c, c.description, true) - 10 - amend - sb.Len() - len(leftBrac) - len(rightBrac) s.currentSaucerSize = int(float64(s.currentPercent) / 100 * float64(c.width)) } + if s.currentSaucerSize > 0 { if c.ignoreLength { saucer = strings.Repeat(c.theme.SaucerPadding, s.currentSaucerSize-1) @@ -839,71 +834,56 @@ func renderProgressBar(c config, s *state) (int, error) { str := "" if c.ignoreLength { - spinner := spinners[c.spinnerType][int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(spinners[c.spinnerType])))))] - if c.elapsedTime { - str = fmt.Sprintf("\r%s %s %s [%s] ", - spinner, - c.description, - sb.String(), - leftBrac) + if !s.finished { + dt, st := time.Since(s.startTime).Milliseconds()/100, c.spinnerType + str = "\r" + + spinners[st][int(math.Round(math.Mod(float64(dt), float64(len(spinners[st])))))] + + sp(" ", c.description != "") + + c.description + + sp(" ", sb.Len() > 0) + + sb.String() + + sp(" [", c.elapsedTime) + + sp(leftBrac, c.elapsedTime) + + sp("]", c.elapsedTime) + " " } else { - str = fmt.Sprintf("\r%s %s %s ", - spinner, - c.description, - sb.String()) - } - } else if rightBrac == "" { - str = fmt.Sprintf("%4d%% %s%s%s%s%s %s", - s.currentPercent, - c.theme.BarStart, - saucer, - saucerHead, - strings.Repeat(c.theme.SaucerPadding, repeatAmount), - c.theme.BarEnd, - sb.String()) - - if s.currentPercent == 100 && c.showElapsedTimeOnFinish { - str = fmt.Sprintf("%s [%s]", str, leftBrac) + str = "\r 100%%" + + sp(" ", c.description != "") + + c.description + + sp(" ", sb.Len() > 0) + + sb.String() + + sp(" [", c.elapsedTime) + + sp(leftBrac, c.elapsedTime) + + sp("]", c.elapsedTime) + " " } - - str = fmt.Sprintf("\r%s%s ", c.description, str) + } else if rightBrac == "" || s.finished { + str = "\r" + + c.description + + fmt.Sprintf("%4d%% ", s.currentPercent) + + c.theme.BarStart + + saucer + + saucerHead + + strings.Repeat(c.theme.SaucerPadding, repeatAmount) + + c.theme.BarEnd + " " + + sb.String() + + sp(" [", c.elapsedTime || c.predictTime) + + sp(leftBrac, c.elapsedTime || c.predictTime) + + sp("]", c.elapsedTime || c.predictTime) + " " } else { - if s.currentPercent == 100 { - str = fmt.Sprintf("%4d%% %s%s%s%s%s %s", - s.currentPercent, - c.theme.BarStart, - saucer, - saucerHead, - strings.Repeat(c.theme.SaucerPadding, repeatAmount), - c.theme.BarEnd, - sb.String()) - - if c.showElapsedTimeOnFinish { - str = fmt.Sprintf("%s [%s]", str, leftBrac) - } - - str = fmt.Sprintf("\r%s%s", c.description, str) - } else { - str = fmt.Sprintf("%4d%% %s%s%s%s%s %s [%s:%s]", - s.currentPercent, - c.theme.BarStart, - saucer, - saucerHead, - strings.Repeat(c.theme.SaucerPadding, repeatAmount), - c.theme.BarEnd, - sb.String(), - leftBrac, - rightBrac) - - str = fmt.Sprintf("\r%s%s", c.description, str) - } + str = "\r" + + c.description + + fmt.Sprintf("%4d%% ", s.currentPercent) + + c.theme.BarStart + + saucer + + saucerHead + + strings.Repeat(c.theme.SaucerPadding, repeatAmount) + + c.theme.BarEnd + " " + + sb.String() + + " [" + leftBrac + ":" + rightBrac + "] " } - if c.colorCodes { // convert any color codes in the progress bar into the respective ANSI codes str = colorstring.Color(str) } - s.rendered = str return getStringWidth(c, str, false), writeString(c, str) @@ -1013,6 +993,13 @@ func logn(n, b float64) float64 { return math.Log(n) / math.Log(b) } +func sp(s string, p bool) string { + if p { + return s + } + return "" +} + // termWidth function returns the visible width of the current terminal // and can be redefined for testing var termWidth = func() (width int, err error) { diff --git a/progressbar_test.go b/progressbar_test.go index 7aa1d3d..2aa43e1 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -24,10 +24,7 @@ func TestMain(m *testing.M) { } func BenchmarkRender(b *testing.B) { - bar := NewOptions64(100000000, - OptionSetWriter(os.Stderr), - OptionShowIts(), - ) + bar := NewOptions64(1e8, OptionSetWriter(os.Stderr), OptionShowIts()) for i := 0; i < b.N; i++ { bar.Add(1) } @@ -36,6 +33,7 @@ func BenchmarkRender(b *testing.B) { func ExampleProgressBar() { bar := New(100) bar.Add(10) + // Output: // 10% |████ | [0s:0s] } @@ -43,6 +41,7 @@ func ExampleProgressBar() { func ExampleProgressBar_Set() { bar := New(100) bar.Set(10) + // Output: // 10% |████ | [0s:0s] } @@ -50,59 +49,60 @@ func ExampleProgressBar_Set() { func ExampleProgressBar_Set64() { bar := New(100) bar.Set64(10) + // Output: // 10% |████ | [0s:0s] } func ExampleProgressBar_basic() { bar := NewOptions(100, OptionSetWidth(10)) - bar.Reset() time.Sleep(1 * time.Second) bar.Add(10) + // Output: // 10% |█ | [1s:9s] } func ExampleProgressBar_invisible() { bar := NewOptions(100, OptionSetWidth(10), OptionSetRenderBlankState(true), OptionSetVisibility(false)) - bar.Reset() fmt.Println("hello, world") time.Sleep(1 * time.Second) bar.Add(10) + // Output: // hello, world } func ExampleOptionThrottle() { bar := NewOptions(100, OptionSetWidth(10), OptionThrottle(100*time.Millisecond)) - bar.Reset() bar.Add(5) time.Sleep(150 * time.Millisecond) bar.Add(5) bar.Add(10) + // Output: // 10% |█ | [0s:1s] } func ExampleOptionClearOnFinish() { bar := NewOptions(100, OptionSetWidth(10), OptionClearOnFinish()) - bar.Reset() bar.Finish() fmt.Println("Finished") + // Output: // Finished } func ExampleProgressBar_Finish() { - bar := NewOptions(100, OptionSetWidth(10)) + bar := NewOptions(100, OptionSetWidth(10), OptionSetElapsedTime(false)) bar.Finish() + // Output: // 100% |██████████| } func Example_xOutOfY() { bar := NewOptions(100, OptionSetPredictTime(true)) - for i := 0; i < 100; i++ { bar.Add(1) time.Sleep(1 * time.Millisecond) @@ -111,34 +111,28 @@ func Example_xOutOfY() { func ExampleOptionShowIts_count() { bar := NewOptions(100, OptionSetWidth(10), OptionShowIts(), OptionShowCount()) - bar.Reset() time.Sleep(1 * time.Second) bar.Add(10) + // Output: // 10% |█ | (10/100, 10 it/s) [1s:9s] } func ExampleOptionShowIts() { - bar := NewOptions(100, OptionSetWidth(10), OptionShowIts(), OptionSetPredictTime(false)) - bar.Reset() + bar := NewOptions(100, OptionSetWidth(10), OptionShowIts(), OptionSetElapsedTime(false)) time.Sleep(1 * time.Second) bar.Add(10) + // Output: // 10% |█ | (10 it/s) } func ExampleOptionShowCount_minuscule() { - bar := NewOptions(10000, OptionSetWidth(10), OptionShowCount(), OptionSetPredictTime(false)) + bar := NewOptions(10000, OptionSetWidth(10), OptionShowCount(), OptionSetElapsedTime(false)) bar.Add(1) - // Output: - // 0% | | (1/10000) -} -func ExampleOptionSetPredictTime() { - bar := NewOptions(100, OptionSetWidth(10), OptionSetPredictTime(false)) - _ = bar.Add(10) // Output: - // 10% |█ | + // 0% | | (1/10000) } func ExampleDefault() { @@ -147,69 +141,71 @@ func ExampleDefault() { bar.Add(1) time.Sleep(10 * time.Millisecond) } + // Output: // } func ExampleProgressBar_ChangeMax() { - bar := NewOptions(100, OptionSetWidth(10), OptionSetPredictTime(false)) + bar := NewOptions(100, OptionSetWidth(10), OptionSetElapsedTime(false)) bar.ChangeMax(50) bar.Add(50) + // Output: // 100% |██████████| } +func ExampleOptionSetDescription_spinner() { + /* + Spinner with description and iteration count + */ + bar := NewOptions(-1, OptionSetDescription("Spinning"), OptionShowCount()) + time.Sleep(1 * time.Second) + bar.Add(5) + + // Output: + // - Spinning (5/?) [1s] +} + func ExampleOptionShowIts_spinner() { /* - Spinner test with iteration count and iteration rate + Spinner with iteration count and iteration rate */ - bar := NewOptions(-1, - OptionSetWidth(10), - OptionShowIts(), - OptionShowCount(), - ) - bar.Reset() + bar := NewOptions(-1, OptionSetWidth(10), OptionShowIts(), OptionShowCount()) time.Sleep(1 * time.Second) bar.Add(5) // Output: - // - (5/-, 5 it/s) [1s] + // - (5/?, 5 it/s) [1s] } -func TestSpinnerType(t *testing.T) { +func ExampleOptionSpinnerType() { bar := NewOptions(-1, OptionSetWidth(10), - OptionSetDescription("indeterminate spinner"), - OptionShowIts(), + OptionSetDescription("Tracing"), OptionShowCount(), - OptionSpinnerType(9), - ) - bar.Reset() - for i := 0; i < 10; i++ { - time.Sleep(120 * time.Millisecond) - bar.Add(1) - } - if false { - t.Errorf("error") - } + OptionSpinnerType(59)) + time.Sleep(120 * time.Millisecond) + bar.Add(11) + + // Output: + // .. Tracing (11/?) [0s] } func Test_IsFinished(t *testing.T) { isCalled := false - bar := NewOptions(72, OptionOnCompletion(func() { - isCalled = true - })) + bar := NewOptions(72, OptionOnCompletion(func() { isCalled = true })) // Test1: If bar is not fully completed. bar.Add(5) if bar.IsFinished() || isCalled { - t.Errorf("Successfully tested bar is not yet finished.") + t.Errorf("bar finished but it shouldn't") } // Test2: Bar fully completed. bar.Add(67) if !bar.IsFinished() || !isCalled { - t.Errorf("Successfully tested bar is finished.") + t.Errorf("bar not finished but it should") } // Test3: If increases maximum bytes error should be thrown and @@ -217,33 +213,27 @@ func Test_IsFinished(t *testing.T) { bar.Reset() err := bar.Add(73) if err == nil || bar.IsFinished() { - t.Errorf("Successfully got error when bytes increases max bytes, bar finished: %v", bar.IsFinished()) + t.Errorf("no error when bytes increases over max bytes or bar finished: %v", bar.IsFinished()) } } func ExampleOptionShowBytes_spinner() { /* - Spinner test with iterations and count + Spinner with iterations and count */ - bar := NewOptions(-1, - OptionSetWidth(10), - OptionShowBytes(true), - ) - - bar.Reset() + bar := NewOptions(-1, OptionSetWidth(10), OptionShowBytes(true)) time.Sleep(1 * time.Second) // since 10 is the width and we don't know the max bytes // it will do a infinite scrolling. bar.Add(11) // Output: - // - (11 B/s) [1s] + // - (11 B/s) [1s] } func TestBarSlowAdd(t *testing.T) { buf := strings.Builder{} bar := NewOptions(100, OptionSetWidth(10), OptionShowIts(), OptionSetWriter(&buf)) - bar.Reset() time.Sleep(3 * time.Second) bar.Add(1) if !strings.Contains(buf.String(), "1%") { @@ -255,13 +245,15 @@ func TestBarSlowAdd(t *testing.T) { if !strings.Contains(buf.String(), "[3s:") { t.Errorf("wrong string: %s", buf.String()) } - // Output: - // 1% | | (20 it/min) [3s:4m57s] } func TestBarSmallBytes(t *testing.T) { buf := strings.Builder{} - bar := NewOptions64(100000000, OptionShowBytes(true), OptionShowCount(), OptionSetWidth(10), OptionSetWriter(&buf)) + bar := NewOptions64(100000000, + OptionShowBytes(true), + OptionShowCount(), + OptionSetWidth(10), + OptionSetWriter(&buf)) for i := 1; i < 10; i++ { time.Sleep(100 * time.Millisecond) bar.Add(1000) @@ -280,7 +272,11 @@ func TestBarSmallBytes(t *testing.T) { func TestBarFastBytes(t *testing.T) { buf := strings.Builder{} - bar := NewOptions64(1e8, OptionShowBytes(true), OptionShowCount(), OptionSetWidth(10), OptionSetWriter(&buf)) + bar := NewOptions64(1e8, + OptionShowBytes(true), + OptionShowCount(), + OptionSetWidth(10), + OptionSetWriter(&buf)) time.Sleep(time.Millisecond) bar.Add(2e7) if !strings.Contains(buf.String(), " GB/s)") { @@ -301,7 +297,6 @@ func TestBar(t *testing.T) { func TestState(t *testing.T) { bar := NewOptions(100, OptionSetWidth(10)) - bar.Reset() time.Sleep(1 * time.Second) bar.Add(10) s := bar.State() @@ -312,41 +307,37 @@ func TestState(t *testing.T) { func ExampleOptionSetRenderBlankState() { NewOptions(10, OptionSetWidth(10), OptionSetRenderBlankState(true)) + // Output: // 0% | | [0s:0s] } func TestBasicSets(t *testing.T) { - b := NewOptions( - 999, - OptionSetWidth(888), + b := NewOptions(333, + OptionSetWidth(222), OptionSetRenderBlankState(true), - OptionSetWriter(io.Discard), // suppressing output for this test - ) + OptionSetWriter(io.Discard)) // suppressing output for this test tc := b.config - if tc.max != 999 { - t.Errorf("Expected %s to be %d, instead I got %d\n%+v", "max", 999, tc.max, b) + if tc.max != 333 { + t.Errorf("Expected %s to be %d, instead I got %d\n%+v", "max", 333, tc.max, b) } - - if tc.width != 888 { - t.Errorf("Expected %s to be %d, instead I got %d\n%+v", "width", 999, tc.max, b) + if tc.width != 222 { + t.Errorf("Expected %s to be %d, instead I got %d\n%+v", "width", 222, tc.max, b) } - if !tc.renderWithBlankState { - t.Errorf("Expected %s to be %t, instead I got %t\n%+v", "renderWithBlankState", true, tc.renderWithBlankState, b) + t.Errorf("Expected %s to be %t, instead I got %t\n%+v", + "renderWithBlankState", true, tc.renderWithBlankState, b) } } func TestOptionSetTheme(t *testing.T) { buf := strings.Builder{} - bar := NewOptions( - 10, + bar := NewOptions(10, OptionSetTheme(Theme{Saucer: "#", SaucerPadding: "-", BarStart: ">", BarEnd: "<"}), OptionSetWidth(10), - OptionSetWriter(&buf), - ) + OptionSetWriter(&buf)) bar.Add(5) result := strings.TrimSpace(buf.String()) expect := "50% >#####-----< [0s:0s]" @@ -360,16 +351,14 @@ func TestOptionSetTheme(t *testing.T) { // time in seconds is specified. func TestOptionSetPredictTime(t *testing.T) { buf := strings.Builder{} - bar := NewOptions( - 10, + bar := NewOptions(10, OptionSetPredictTime(false), OptionSetWidth(10), - OptionSetWriter(&buf), - ) + OptionSetWriter(&buf)) - _ = bar.Add(2) + bar.Add(2) result := strings.TrimSpace(buf.String()) - expect := "20% |██ |" + expect := "20% |██ | [0s]" if result != expect { t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar) @@ -379,7 +368,7 @@ func TestOptionSetPredictTime(t *testing.T) { bar.config.predictTime = true buf.Reset() - _ = bar.Add(7) + bar.Add(7) result = strings.TrimSpace(buf.String()) expect = "70% |███████ | [0s:0s]" @@ -390,13 +379,11 @@ func TestOptionSetPredictTime(t *testing.T) { func TestOptionSetElapsedTime(t *testing.T) { buf := strings.Builder{} - bar := NewOptions( - 10, + bar := NewOptions(10, OptionSetElapsedTime(false), OptionSetWidth(10), - OptionSetWriter(&buf), - ) - _ = bar.Add(2) + OptionSetWriter(&buf)) + bar.Add(2) result := strings.TrimSpace(buf.String()) expect := "20% |██ |" if result != expect { @@ -411,41 +398,18 @@ func TestOptionSetElapsedTime_spinner(t *testing.T) { OptionSetWriter(&buf), OptionShowIts(), OptionShowCount(), - OptionSetElapsedTime(false), - ) - bar.Reset() + OptionSetElapsedTime(false)) time.Sleep(1 * time.Second) bar.Add(5) result := strings.TrimSpace(buf.String()) - expect := "- (5/-, 5 it/s)" - if result != expect { - t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar) - } -} - -func TestShowElapsedTimeOnFinish(t *testing.T) { - buf := strings.Builder{} - bar := NewOptions(10, - OptionShowElapsedTimeOnFinish(), - OptionSetWidth(10), - OptionSetWriter(&buf), - ) - bar.Reset() - time.Sleep(3 * time.Second) - bar.Add(10) - result := strings.TrimSpace(buf.String()) - expect := "100% |██████████| [3s]" + expect := "- (5/?, 5 it/s)" if result != expect { t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar) } } func TestSpinnerState(t *testing.T) { - bar := NewOptions( - -1, - OptionSetWidth(100), - ) - bar.Reset() + bar := NewOptions(-1, OptionSetWidth(100)) time.Sleep(1 * time.Second) bar.Add(10) @@ -596,10 +560,7 @@ func TestReaderToFileUnknownLength(t *testing.T) { func TestConcurrency(t *testing.T) { buf := strings.Builder{} - bar := NewOptions( - 1000, - OptionSetWriter(&buf), - ) + bar := NewOptions(1000, OptionSetWriter(&buf)) var wg sync.WaitGroup for i := 0; i < 900; i++ { wg.Add(1) @@ -645,9 +606,9 @@ func TestProgressBar_Describe(t *testing.T) { bar.Add(10) result := buf.String() expect := "" + - "\rperforming axial adjustments 0% | | [0s:0s]" + - "\r \r" + - "\rperforming axial adjustments 10% |█ | [0s:0s]" + "\rperforming axial adjustments 0% | | [0s:0s] " + + "\r \r" + + "\rperforming axial adjustments 10% |█ | [0s:0s] " if result != expect { t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar) } @@ -655,7 +616,11 @@ func TestProgressBar_Describe(t *testing.T) { func TestRenderBlankStateWithThrottle(t *testing.T) { buf := strings.Builder{} - bar := NewOptions(100, OptionSetWidth(10), OptionSetRenderBlankState(true), OptionThrottle(time.Millisecond), OptionSetWriter(&buf)) + bar := NewOptions(100, + OptionSetWidth(10), + OptionSetRenderBlankState(true), + OptionThrottle(time.Millisecond), + OptionSetWriter(&buf)) result := strings.TrimSpace(buf.String()) expect := "0% | | [0s:0s]" if result != expect { @@ -671,81 +636,60 @@ func TestOptionFullWidth(t *testing.T) { { // 1 []Option{}, "" + - "\r 10% |██████ | [1s:9s]" + + "\r 10% |██████ | [1s:9s] " + "\r \r" + - "\r 100% |██████████████████████████████████████████████████████████████| ", + "\r 100% |█████████████████████████████████████████████████████████████| [2s] ", }, { // 2 []Option{OptionSetDescription("Progress:")}, "" + - "\rProgress: 10% |█████ | [1s:9s]" + + "\rProgress: 10% |█████ | [1s:9s] " + "\r \r" + - "\rProgress: 100% |█████████████████████████████████████████████████████| ", + "\rProgress: 100% |████████████████████████████████████████████████████| [2s] ", }, { // 3 []Option{OptionSetPredictTime(false)}, "" + - "\r 10% |██████ | " + - "\r \r" + - "\r 100% |████████████████████████████████████████████████████████████████| ", - }, - { // 4 - []Option{OptionSetPredictTime(false), OptionShowElapsedTimeOnFinish()}, - "" + - "\r 10% |██████ | " + - "\r \r" + + "\r 10% |██████ | [1s] " + + "\r \r" + "\r 100% |████████████████████████████████████████████████████████████████| [2s] ", }, - { // 5 + { // 4 []Option{OptionSetPredictTime(false), OptionSetElapsedTime(false)}, "" + "\r 10% |██████ | " + "\r \r" + "\r 100% |█████████████████████████████████████████████████████████████████████| ", }, - { // 6 + { // 5 []Option{OptionShowIts()}, "" + - "\r 10% |█████ | (10 it/s) [1s:9s]" + + "\r 10% |█████ | (10 it/s) [1s:9s] " + "\r \r" + - "\r 100% |█████████████████████████████████████████████████████| (50 it/s)", + "\r 100% |████████████████████████████████████████████████████| (50 it/s) [2s] ", }, - { // 7 + { // 6 []Option{OptionShowCount()}, "" + - "\r 10% |█████ | (10/100) [1s:9s]" + - "\r \r" + - "\r 100% |█████████████████████████████████████████████████████| (100/100)", - }, - { // 8 - []Option{OptionShowIts(), OptionShowCount(), OptionShowElapsedTimeOnFinish()}, - "" + - "\r 10% |████ | (10/100, 10 it/s) [1s:9s]" + + "\r 10% |█████ | (10/100) [1s:9s] " + "\r \r" + - "\r 100% |████████████████████████████████████████████| (100/100, 50 it/s) [2s]", + "\r 100% |████████████████████████████████████████████████████| (100/100) [2s] ", }, - { // 9 + { // 7 []Option{OptionSetDescription("Progress:"), OptionShowIts(), OptionShowCount()}, "" + - "\rProgress: 10% |███ | (10/100, 10 it/s) [1s:9s]" + + "\rProgress: 10% |███ | (10/100, 10 it/s) [1s:9s] " + "\r \r" + - "\rProgress: 100% |███████████████████████████████████| (100/100, 50 it/s)", + "\rProgress: 100% |██████████████████████████████████| (100/100, 50 it/s) [2s] ", }, - { // 10 + { // 8 []Option{OptionShowIts(), OptionShowCount(), OptionSetPredictTime(false)}, "" + - "\r 10% |████ | (10/100, 10 it/s) " + - "\r \r" + - "\r 100% |██████████████████████████████████████████████| (100/100, 50 it/s) ", - }, - { // 11 - []Option{OptionShowIts(), OptionShowCount(), OptionSetPredictTime(false), OptionShowElapsedTimeOnFinish()}, - "" + - "\r 10% |████ | (10/100, 10 it/s) " + - "\r \r" + + "\r 10% |████ | (10/100, 10 it/s) [1s] " + + "\r \r" + "\r 100% |██████████████████████████████████████████████| (100/100, 50 it/s) [2s] ", }, - { // 12 + { // 9 []Option{OptionShowIts(), OptionShowCount(), OptionSetPredictTime(false), OptionSetElapsedTime(false)}, "" + "\r 10% |█████ | (10/100, 10 it/s) " + From bd19a32368c2b9139be6b4b492028bcb3c7e4348 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Wed, 27 Sep 2023 23:30:09 +0300 Subject: [PATCH 20/26] Add OptionTotalRate --- progressbar.go | 71 +++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/progressbar.go b/progressbar.go index 1a3e54f..65d2f59 100644 --- a/progressbar.go +++ b/progressbar.go @@ -71,10 +71,14 @@ type config struct { // show rate of change in kB/sec or MB/sec showBytes bool + // show the iterations per second showIterationsPerSecond bool showIterationsCount bool + // always display total rate + totalRate bool + // whether the progress bar should show elapsed time. // always enabled if predictTime is true. elapsedTime bool @@ -227,6 +231,13 @@ func OptionSetItsString(iterationString string) Option { } } +// OptionTotalRate selects between total or recent average rate for rate display +func OptionTotalRate(val bool) Option { + return func(p *ProgressBar) { + p.config.totalRate = val + } +} + // OptionThrottle will wait the specified duration before updating again. The default // duration is 0 seconds. func OptionThrottle(duration time.Duration) Option { @@ -511,15 +522,18 @@ func (p *ProgressBar) Add64(num int64) error { p.state.currentBytes += float64(num) - // reset the countdown timer every second to take rolling average - p.state.counterNumSinceLast += num - if time.Since(p.state.counterTime).Seconds() > 0.5 { - p.state.counterLastTenRates = append(p.state.counterLastTenRates, float64(p.state.counterNumSinceLast)/time.Since(p.state.counterTime).Seconds()) - if len(p.state.counterLastTenRates) > 10 { - p.state.counterLastTenRates = p.state.counterLastTenRates[1:] + if !p.config.totalRate { + // reset the countdown timer approx every second to take rolling average + p.state.counterNumSinceLast += num + if t := time.Since(p.state.counterTime).Seconds(); t > 0.5 { + rate := float64(p.state.counterNumSinceLast) / t + p.state.counterLastTenRates = append(p.state.counterLastTenRates, rate) + if len(p.state.counterLastTenRates) > 10 { + p.state.counterLastTenRates = p.state.counterLastTenRates[1:] + } + p.state.counterTime = time.Now() + p.state.counterNumSinceLast = 0 } - p.state.counterTime = time.Now() - p.state.counterNumSinceLast = 0 } percent := float64(p.state.currentNum) / float64(p.config.max) @@ -698,17 +712,6 @@ func getStringWidth(c config, str string, colorize bool) int { func renderProgressBar(c config, s *state) (int, error) { var sb strings.Builder - averageRate := average(s.counterLastTenRates) - if len(s.counterLastTenRates) == 0 || s.finished { - // if no average samples, or if finished, - // then average rate should be the total rate - if t := time.Since(s.startTime).Seconds(); t > 0 { - averageRate = s.currentBytes / t - } else { - averageRate = 0 - } - } - // show iteration count in "current/total" iterations format if c.showIterationsCount { if sb.Len() == 0 { @@ -741,30 +744,40 @@ func renderProgressBar(c config, s *state) (int, error) { } } - // show rolling average rate - if c.showBytes && averageRate > 0 && !math.IsInf(averageRate, 1) { + rate := 0.0 + if len(s.counterLastTenRates) > 0 && !s.finished && !c.totalRate { + // display recent rolling average rate + rate = average(s.counterLastTenRates) + } else if t := time.Since(s.startTime).Seconds(); t > 0 { + // if no average samples, or if finished, or total rate option is set + // then display total rate + rate = s.currentBytes / t + } + + // format rate as units of bytes per second + if c.showBytes && rate > 0 && !math.IsInf(rate, 1) { if sb.Len() == 0 { sb.WriteString("(") } else { sb.WriteString(", ") } - currentHumanize, currentSuffix := humanizeBytes(averageRate) + currentHumanize, currentSuffix := humanizeBytes(rate) sb.WriteString(fmt.Sprintf("%s%s/s", currentHumanize, currentSuffix)) } - // show iterations rate + // format rate as iterations per second/minute/hour if c.showIterationsPerSecond { if sb.Len() == 0 { sb.WriteString("(") } else { sb.WriteString(", ") } - if averageRate > 1 { - sb.WriteString(fmt.Sprintf("%0.0f %s/s", averageRate, c.iterationString)) - } else if averageRate*60 > 1 { - sb.WriteString(fmt.Sprintf("%0.0f %s/min", 60*averageRate, c.iterationString)) + if rate > 1 { + sb.WriteString(fmt.Sprintf("%0.0f %s/s", rate, c.iterationString)) + } else if 60*rate > 1 { + sb.WriteString(fmt.Sprintf("%0.0f %s/min", 60*rate, c.iterationString)) } else { - sb.WriteString(fmt.Sprintf("%0.0f %s/h", 3600*averageRate, c.iterationString)) + sb.WriteString(fmt.Sprintf("%0.0f %s/h", 3600*rate, c.iterationString)) } } if sb.Len() > 0 { @@ -776,7 +789,7 @@ func renderProgressBar(c config, s *state) (int, error) { // show time prediction in "current/total" seconds format switch { case c.predictTime: - rightBracNum := time.Duration((1/averageRate)*(float64(c.max)-float64(s.currentNum))) * time.Second + rightBracNum := time.Duration((1/rate)*(float64(c.max)-float64(s.currentNum))) * time.Second if rightBracNum.Seconds() < 0 { rightBracNum = 0 * time.Second } From cc927c4da77513d3e81f23a2d95bc8917cc6cb8a Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Thu, 28 Sep 2023 22:31:49 +0300 Subject: [PATCH 21/26] Add Stop + fix some style --- progressbar.go | 58 +++++++++++++++++++-------------------------- progressbar_test.go | 31 ++++++++++++++---------- 2 files changed, 43 insertions(+), 46 deletions(-) diff --git a/progressbar.go b/progressbar.go index 65d2f59..149d1d0 100644 --- a/progressbar.go +++ b/progressbar.go @@ -49,7 +49,7 @@ type state struct { maxLineWidth int currentBytes float64 finished bool - exit bool // Progress bar exit halfway + stopped bool rendered string } @@ -91,7 +91,7 @@ type config struct { // minimum time to wait in between updates throttleDuration time.Duration - // clear bar once finished + // clear bar once finished or stopped clearOnFinish bool // spinnerType should be a number between 0-75 @@ -246,14 +246,14 @@ func OptionThrottle(duration time.Duration) Option { } } -// OptionClearOnFinish will clear the bar once its finished +// OptionClearOnFinish will clear the bar once it's finished or stopped func OptionClearOnFinish() Option { return func(p *ProgressBar) { p.config.clearOnFinish = true } } -// OptionOnCompletion will invoke cmpl function once its finished +// OptionOnCompletion will invoke cmpl function once it's finished or stopped func OptionOnCompletion(cmpl func()) Option { return func(p *ProgressBar) { p.config.onCompletion = cmpl @@ -319,6 +319,8 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { if b.config.renderWithBlankState { b.RenderBlank() + } else { + b.state.lastShown = b.state.startTime } return &b @@ -328,7 +330,6 @@ func getBasicState() state { now := time.Now() return state{ startTime: now, - lastShown: now, counterTime: now, } } @@ -414,7 +415,6 @@ func Default(max int64, description ...string) *ProgressBar { // String() can be used to get the output instead. func DefaultSilent(max int64, description ...string) *ProgressBar { // Mostly the same bar as Default - desc := "" if len(description) > 0 { desc = description[0] @@ -443,9 +443,7 @@ func (p *ProgressBar) RenderBlank() error { if p.config.invisible { return nil } - if p.state.currentNum == 0 { - p.state.lastShown = time.Time{} - } + p.state.lastShown = time.Time{} // render regardless of throttling return p.render() } @@ -453,29 +451,29 @@ func (p *ProgressBar) RenderBlank() error { // to calculate current time and the time left. func (p *ProgressBar) Reset() { p.lock.Lock() - defer p.lock.Unlock() - p.state = getBasicState() + p.lock.Unlock() } // Finish will fill the bar to full func (p *ProgressBar) Finish() error { p.lock.Lock() p.state.currentNum = p.config.max + p.state.lastShown = time.Time{} // re-render regardless of throttling p.lock.Unlock() return p.Add(0) } -// Exit will exit the bar to keep current state -func (p *ProgressBar) Exit() error { - p.lock.Lock() - defer p.lock.Unlock() - - p.state.exit = true - if p.config.onCompletion != nil { - p.config.onCompletion() +// Stop stops the progress bar at current state. +func (p *ProgressBar) Stop() error { + if p.config.invisible { + return nil } - return nil + p.lock.Lock() + p.state.stopped = true + p.state.lastShown = time.Time{} // render regardless of throttling + p.lock.Unlock() + return p.Add(0) } // Add will add the specified amount to the progressbar @@ -504,10 +502,6 @@ func (p *ProgressBar) Add64(num int64) error { p.lock.Lock() defer p.lock.Unlock() - if p.state.exit { - return nil - } - if p.config.max == 0 { return errors.New("max must be greater than 0") } @@ -547,7 +541,7 @@ func (p *ProgressBar) Add64(num int64) error { } // always update if show bytes/second or its/second - if updateBar || p.config.showIterationsPerSecond || p.config.showIterationsCount { + if updateBar || p.config.showIterationsPerSecond || p.config.showIterationsCount || num == 0 { return p.render() } @@ -564,10 +558,12 @@ func (p *ProgressBar) Clear() error { func (p *ProgressBar) Describe(description string) { p.lock.Lock() defer p.lock.Unlock() + p.config.description = description if p.config.invisible { return } + p.state.lastShown = time.Time{} // re-render regardless of throttling p.render() } @@ -608,7 +604,7 @@ func (p *ProgressBar) ChangeMax64(newMax int64) { p.Add(0) // re-render } -// IsFinished returns true if progress bar is completed +// IsFinished returns true if progress bar is finished func (p *ProgressBar) IsFinished() bool { return p.state.finished } @@ -633,7 +629,7 @@ func (p *ProgressBar) render() error { } // check if the progress bar is finished - if !p.state.finished && p.state.currentNum >= p.config.max { + if !p.state.finished && (p.state.currentNum >= p.config.max || p.state.stopped) { p.state.finished = true if !p.config.clearOnFinish { renderProgressBar(p.config, &p.state) @@ -694,7 +690,7 @@ func getStringWidth(c config, str string, colorize bool) int { // the width of the string, if printed to the console // does not include the carriage return character - cleanString := strings.Replace(str, "\r", "", -1) + cleanString := strings.ReplaceAll(str, "\r", "") if c.colorCodes { // the ANSI codes for the colors do not take up space in the console output, @@ -702,11 +698,7 @@ func getStringWidth(c config, str string, colorize bool) int { cleanString = ansiRegex.ReplaceAllString(cleanString, "") } - // get the amount of runes in the string instead of the - // character count of the string, as some runes span multiple characters. - // see https://stackoverflow.com/a/12668840/2733724 - stringWidth := runewidth.StringWidth(cleanString) - return stringWidth + return runewidth.StringWidth(cleanString) } func renderProgressBar(c config, s *state) (int, error) { diff --git a/progressbar_test.go b/progressbar_test.go index 2aa43e1..3ceba7f 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -147,9 +147,9 @@ func ExampleDefault() { } func ExampleProgressBar_ChangeMax() { - bar := NewOptions(100, OptionSetWidth(10), OptionSetElapsedTime(false)) - bar.ChangeMax(50) + bar := NewOptions(100, OptionSetWidth(10), OptionSetElapsedTime(false), OptionThrottle(time.Second)) bar.Add(50) + bar.ChangeMax(50) // Output: // 100% |██████████| @@ -171,7 +171,7 @@ func ExampleOptionShowIts_spinner() { /* Spinner with iteration count and iteration rate */ - bar := NewOptions(-1, OptionSetWidth(10), OptionShowIts(), OptionShowCount()) + bar := NewOptions(-1, OptionShowIts(), OptionShowCount()) time.Sleep(1 * time.Second) bar.Add(5) @@ -180,11 +180,7 @@ func ExampleOptionShowIts_spinner() { } func ExampleOptionSpinnerType() { - bar := NewOptions(-1, - OptionSetWidth(10), - OptionSetDescription("Tracing"), - OptionShowCount(), - OptionSpinnerType(59)) + bar := NewOptions(-1, OptionSetDescription("Tracing"), OptionShowCount(), OptionSpinnerType(59)) time.Sleep(120 * time.Millisecond) bar.Add(11) @@ -192,7 +188,7 @@ func ExampleOptionSpinnerType() { // .. Tracing (11/?) [0s] } -func Test_IsFinished(t *testing.T) { +func TestIsFinished(t *testing.T) { isCalled := false bar := NewOptions(72, OptionOnCompletion(func() { isCalled = true })) @@ -217,11 +213,21 @@ func Test_IsFinished(t *testing.T) { } } +func TestStop(t *testing.T) { + isCalled := false + bar := NewOptions(72, OptionOnCompletion(func() { isCalled = true })) + bar.Add(44) + bar.Stop() + if !bar.IsFinished() || !isCalled { + t.Errorf("bar not finished but it should") + } +} + func ExampleOptionShowBytes_spinner() { /* Spinner with iterations and count */ - bar := NewOptions(-1, OptionSetWidth(10), OptionShowBytes(true)) + bar := NewOptions(-1, OptionShowBytes(true)) time.Sleep(1 * time.Second) // since 10 is the width and we don't know the max bytes // it will do a infinite scrolling. @@ -394,11 +400,10 @@ func TestOptionSetElapsedTime(t *testing.T) { func TestOptionSetElapsedTime_spinner(t *testing.T) { buf := strings.Builder{} bar := NewOptions(-1, - OptionSetWidth(10), - OptionSetWriter(&buf), + OptionSetElapsedTime(false), OptionShowIts(), OptionShowCount(), - OptionSetElapsedTime(false)) + OptionSetWriter(&buf)) time.Sleep(1 * time.Second) bar.Add(5) result := strings.TrimSpace(buf.String()) From 7770177a470299b4d8cc46f5425bb6479d2c80e0 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Sat, 30 Sep 2023 11:55:46 +0300 Subject: [PATCH 22/26] Do tricky widths only when needed --- progressbar.go | 42 ++++++++++++++++++++++++++++++++++++++++-- progressbar_test.go | 13 +++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/progressbar.go b/progressbar.go index 149d1d0..bc03dca 100644 --- a/progressbar.go +++ b/progressbar.go @@ -10,6 +10,7 @@ import ( "strings" "sync" "time" + "unicode/utf8" "github.com/mattn/go-runewidth" "github.com/mitchellh/colorstring" @@ -94,7 +95,7 @@ type config struct { // clear bar once finished or stopped clearOnFinish bool - // spinnerType should be a number between 0-75 + // spinnerType should be a key from the spinners map spinnerType int // fullWidth specifies whether to measure and set the bar to a specific width @@ -107,6 +108,9 @@ type config struct { // whether the render function should make use of ANSI codes to reduce console I/O useANSICodes bool + + // whether the getStringWidth function should be more rigorous + trickyWidths bool } // Theme defines the elements of the bar @@ -140,6 +144,7 @@ func OptionSetWidth(s int) Option { func OptionSpinnerType(spinnerType int) Option { return func(p *ProgressBar) { p.config.spinnerType = spinnerType + p.checkTrickyWidths() } } @@ -147,6 +152,7 @@ func OptionSpinnerType(spinnerType int) Option { func OptionSetTheme(t Theme) Option { return func(p *ProgressBar) { p.config.theme = t + p.checkTrickyWidths() } } @@ -182,6 +188,7 @@ func OptionSetRenderBlankState(r bool) Option { func OptionSetDescription(description string) Option { return func(p *ProgressBar) { p.config.description = description + p.checkTrickyWidths() } } @@ -228,6 +235,7 @@ func OptionShowIts() Option { func OptionSetItsString(iterationString string) Option { return func(p *ProgressBar) { p.config.iterationString = iterationString + p.checkTrickyWidths() } } @@ -317,6 +325,8 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { b.config.maxHumanized, b.config.maxHumanizedSuffix = humanizeBytes(float64(b.config.max)) + b.checkTrickyWidths() + if b.config.renderWithBlankState { b.RenderBlank() } else { @@ -560,6 +570,7 @@ func (p *ProgressBar) Describe(description string) { defer p.lock.Unlock() p.config.description = description + p.checkTrickyWidths() if p.config.invisible { return } @@ -664,6 +675,30 @@ func (p *ProgressBar) render() error { return nil } +// checkTrickyWidths checks if any progress bar element's width in screen characters +// is different from the number of runes in it, and updates the relevant config variable. +func (p *ProgressBar) checkTrickyWidths() { + var parts = []string{ + p.config.description, + p.config.iterationString, + p.config.theme.Saucer, + p.config.theme.SaucerHead, + p.config.theme.SaucerPadding, + p.config.theme.BarStart, + p.config.theme.BarEnd, + } + if p.config.ignoreLength { + parts = append(parts, spinners[p.config.spinnerType]...) + } + for _, s := range parts { + if runewidth.StringWidth(s) != utf8.RuneCountInString(s) { + p.config.trickyWidths = true + return + } + } + p.config.trickyWidths = false +} + // State returns the current state func (p *ProgressBar) State() State { p.lock.Lock() @@ -698,7 +733,10 @@ func getStringWidth(c config, str string, colorize bool) int { cleanString = ansiRegex.ReplaceAllString(cleanString, "") } - return runewidth.StringWidth(cleanString) + if c.trickyWidths { + return runewidth.StringWidth(cleanString) + } + return utf8.RuneCountInString(cleanString) } func renderProgressBar(c config, s *state) (int, error) { diff --git a/progressbar_test.go b/progressbar_test.go index 3ceba7f..144cdda 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -23,8 +23,17 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func BenchmarkRender(b *testing.B) { - bar := NewOptions64(1e8, OptionSetWriter(os.Stderr), OptionShowIts()) +func BenchmarkRenderSimple(b *testing.B) { + bar := NewOptions64(1e8, OptionSetWriter(io.Discard), OptionShowIts(), + OptionSetDescription("ÂŁ")) + for i := 0; i < b.N; i++ { + bar.Add(1) + } +} + +func BenchmarkRenderTricky(b *testing.B) { + bar := NewOptions64(1e8, OptionSetWriter(io.Discard), OptionShowIts(), + OptionSetDescription("èż™æ˜Żäž€äžȘă€ăźæ”‹èŻ•")) for i := 0; i < b.N; i++ { bar.Add(1) } From 3daf02729815f7367f1e2c22cab1a51f09c39bf5 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Sun, 1 Oct 2023 00:37:34 +0300 Subject: [PATCH 23/26] Improve Set/Max + fix some style --- progressbar.go | 215 ++++++++++++++++++++++++-------------------- progressbar_test.go | 4 +- 2 files changed, 122 insertions(+), 97 deletions(-) diff --git a/progressbar.go b/progressbar.go index bc03dca..7663083 100644 --- a/progressbar.go +++ b/progressbar.go @@ -17,15 +17,14 @@ import ( "golang.org/x/term" ) -// ProgressBar is a thread-safe, simple -// progress bar +// ProgressBar is a thread-safe, simple progress bar. type ProgressBar struct { state state config config lock sync.Mutex } -// State is the basic properties of the bar +// State is the basic properties of the bar. type State struct { CurrentPercent float64 CurrentBytes float64 @@ -113,7 +112,7 @@ type config struct { trickyWidths bool } -// Theme defines the elements of the bar +// Theme defines the elements of the bar. type Theme struct { Saucer string SaucerHead string @@ -130,17 +129,17 @@ var spinners = map[int][]string{ 59: {". ", ".. ", "...", " ..", " .", " "}, } -// Option is the type all options need to adhere to +// Option is the type all options need to adhere to. type Option func(p *ProgressBar) -// OptionSetWidth sets the width of the bar +// OptionSetWidth sets the width of the bar. func OptionSetWidth(s int) Option { return func(p *ProgressBar) { p.config.width = s } } -// OptionSpinnerType sets the type of spinner used for indeterminate bars +// OptionSpinnerType sets the type of spinner used for indeterminate bars. func OptionSpinnerType(spinnerType int) Option { return func(p *ProgressBar) { p.config.spinnerType = spinnerType @@ -148,7 +147,7 @@ func OptionSpinnerType(spinnerType int) Option { } } -// OptionSetTheme sets the elements the bar is constructed of +// OptionSetTheme sets the elements the bar is constructed of. func OptionSetTheme(t Theme) Option { return func(p *ProgressBar) { p.config.theme = t @@ -156,35 +155,35 @@ func OptionSetTheme(t Theme) Option { } } -// OptionSetVisibility sets the visibility +// OptionSetVisibility sets the visibility. func OptionSetVisibility(visibility bool) Option { return func(p *ProgressBar) { p.config.invisible = !visibility } } -// OptionFullWidth sets the bar to be full width +// OptionFullWidth sets the bar to be full width. func OptionFullWidth() Option { return func(p *ProgressBar) { p.config.fullWidth = true } } -// OptionSetWriter sets the output writer (defaults to os.StdOut) +// OptionSetWriter sets the output writer (defaults to os.Stdout). func OptionSetWriter(w io.Writer) Option { return func(p *ProgressBar) { p.config.writer = w } } -// OptionSetRenderBlankState sets whether or not to render a 0% bar on construction +// OptionSetRenderBlankState sets whether or not to render a 0% bar on construction. func OptionSetRenderBlankState(r bool) Option { return func(p *ProgressBar) { p.config.renderWithBlankState = r } } -// OptionSetDescription sets the description of the bar to render in front of it +// OptionSetDescription sets the description of the bar to render in front of it. func OptionSetDescription(description string) Option { return func(p *ProgressBar) { p.config.description = description @@ -192,8 +191,7 @@ func OptionSetDescription(description string) Option { } } -// OptionEnableColorCodes enables or disables support for color codes -// using mitchellh/colorstring +// OptionEnableColorCodes enables or disables support for color codes. func OptionEnableColorCodes(colorCodes bool) Option { return func(p *ProgressBar) { p.config.colorCodes = colorCodes @@ -217,21 +215,22 @@ func OptionSetPredictTime(predictTime bool) Option { } } -// OptionShowCount will also print current count out of total +// OptionShowCount will also print current count out of total. func OptionShowCount() Option { return func(p *ProgressBar) { p.config.showIterationsCount = true } } -// OptionShowIts will also print the iterations/second +// OptionShowIts will also print the iterations/second. func OptionShowIts() Option { return func(p *ProgressBar) { p.config.showIterationsPerSecond = true } } -// OptionSetItsString sets what's displayed for iterations a second. The default is "it" which would display: "it/s" +// OptionSetItsString sets what's displayed for iterations a second. +// The default is "it" which would display: "it/s". func OptionSetItsString(iterationString string) Option { return func(p *ProgressBar) { p.config.iterationString = iterationString @@ -239,7 +238,7 @@ func OptionSetItsString(iterationString string) Option { } } -// OptionTotalRate selects between total or recent average rate for rate display +// OptionTotalRate selects between total or recent average rate for rate display. func OptionTotalRate(val bool) Option { return func(p *ProgressBar) { p.config.totalRate = val @@ -254,29 +253,28 @@ func OptionThrottle(duration time.Duration) Option { } } -// OptionClearOnFinish will clear the bar once it's finished or stopped +// OptionClearOnFinish will clear the bar once it's finished or stopped. func OptionClearOnFinish() Option { return func(p *ProgressBar) { p.config.clearOnFinish = true } } -// OptionOnCompletion will invoke cmpl function once it's finished or stopped +// OptionOnCompletion will invoke cmpl function once it's finished or stopped. func OptionOnCompletion(cmpl func()) Option { return func(p *ProgressBar) { p.config.onCompletion = cmpl } } -// OptionShowBytes will update the progress bar -// configuration settings to display/hide kBytes/Sec +// OptionShowBytes will update the progress bar configuration settings to display/hide kBytes/Sec. func OptionShowBytes(val bool) Option { return func(p *ProgressBar) { p.config.showBytes = val } } -// OptionUseANSICodes will use more optimized terminal i/o. +// OptionUseANSICodes will use more optimized terminal I/O. // // Only useful in environments with support for ANSI escape sequences. func OptionUseANSICodes(val bool) Option { @@ -285,12 +283,12 @@ func OptionUseANSICodes(val bool) Option { } } -// NewOptions constructs a new instance of ProgressBar, with any options you specify +// NewOptions constructs a new instance of ProgressBar, with any options you specify. func NewOptions(max int, options ...Option) *ProgressBar { return NewOptions64(int64(max), options...) } -// NewOptions64 constructs a new instance of ProgressBar, with any options you specify +// NewOptions64 constructs a new instance of ProgressBar, with any options you specify. func NewOptions64(max int64, options ...Option) *ProgressBar { b := ProgressBar{ state: getBasicState(), @@ -344,14 +342,12 @@ func getBasicState() state { } } -// New returns a new ProgressBar -// with the specified maximum +// New returns a new ProgressBar with the specified maximum. func New(max int) *ProgressBar { return NewOptions(max) } -// DefaultBytes provides a progressbar to measure byte -// throughput with recommended defaults. +// DefaultBytes provides a progressbar to measure byte throughput with recommended defaults. // Set maxBytes to -1 to use as a spinner. func DefaultBytes(maxBytes int64, description ...string) *ProgressBar { desc := "" @@ -378,8 +374,6 @@ func DefaultBytes(maxBytes int64, description ...string) *ProgressBar { // DefaultBytesSilent is the same as DefaultBytes, but does not output anywhere. // String() can be used to get the output instead. func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar { - // Mostly the same bar as DefaultBytes - desc := "" if len(description) > 0 { desc = description[0] @@ -424,7 +418,6 @@ func Default(max int64, description ...string) *ProgressBar { // DefaultSilent is the same as Default, but does not output anywhere. // String() can be used to get the output instead. func DefaultSilent(max int64, description ...string) *ProgressBar { - // Mostly the same bar as Default desc := "" if len(description) > 0 { desc = description[0] @@ -448,7 +441,7 @@ func (p *ProgressBar) String() string { return p.state.rendered } -// RenderBlank renders the current bar state, you can use this to render a 0% state +// RenderBlank renders the current bar state, it can be used to render a 0% state on start. func (p *ProgressBar) RenderBlank() error { if p.config.invisible { return nil @@ -457,78 +450,86 @@ func (p *ProgressBar) RenderBlank() error { return p.render() } -// Reset will reset the clock that is used -// to calculate current time and the time left. +// Reset will reset the clock that is used to calculate current time and the time left. func (p *ProgressBar) Reset() { p.lock.Lock() p.state = getBasicState() p.lock.Unlock() } -// Finish will fill the bar to full +// Finish will fill the bar to full. func (p *ProgressBar) Finish() error { p.lock.Lock() + defer p.lock.Unlock() + p.state.currentNum = p.config.max p.state.lastShown = time.Time{} // re-render regardless of throttling - p.lock.Unlock() - return p.Add(0) + return p.add(0) } // Stop stops the progress bar at current state. func (p *ProgressBar) Stop() error { - if p.config.invisible { - return nil - } p.lock.Lock() + defer p.lock.Unlock() + p.state.stopped = true - p.state.lastShown = time.Time{} // render regardless of throttling - p.lock.Unlock() - return p.Add(0) + p.state.lastShown = time.Time{} // re-render regardless of throttling + return p.add(0) } -// Add will add the specified amount to the progressbar -func (p *ProgressBar) Add(num int) error { - return p.Add64(int64(num)) +// Add adds the specified amount to the progressbar value. +func (p *ProgressBar) Add(delta int) error { + p.lock.Lock() + defer p.lock.Unlock() + + return p.add(int64(delta)) } -// Set will set the bar to a current number -func (p *ProgressBar) Set(num int) error { - return p.Set64(int64(num)) +// Add64 adds the specified amount to the progressbar value. +func (p *ProgressBar) Add64(delta int64) error { + p.lock.Lock() + defer p.lock.Unlock() + + return p.add(delta) } -// Set64 will set the bar to a current number -func (p *ProgressBar) Set64(num int64) error { +// Set sets the progressbar bar value. +func (p *ProgressBar) Set(value int) error { p.lock.Lock() - toAdd := num - int64(p.state.currentBytes) - p.lock.Unlock() - return p.Add64(toAdd) + defer p.lock.Unlock() + + return p.add(int64(value) - int64(p.state.currentBytes)) } -// Add64 will add the specified amount to the progressbar -func (p *ProgressBar) Add64(num int64) error { - if p.config.invisible { - return nil - } +// Set64 sets the progressbar bar value. +func (p *ProgressBar) Set64(value int64) error { p.lock.Lock() defer p.lock.Unlock() + return p.add(value - int64(p.state.currentBytes)) +} + +func (p *ProgressBar) add(delta int64) error { + if p.config.invisible { + return nil + } if p.config.max == 0 { return errors.New("max must be greater than 0") } if p.state.currentNum < p.config.max { if p.config.ignoreLength { - p.state.currentNum = (p.state.currentNum + num) % p.config.max + p.state.currentNum = (p.state.currentNum + delta) % p.config.max } else { - p.state.currentNum += num + p.state.currentNum += delta } } - p.state.currentBytes += float64(num) + p.state.currentBytes += float64(delta) if !p.config.totalRate { // reset the countdown timer approx every second to take rolling average - p.state.counterNumSinceLast += num + p.state.counterNumSinceLast += delta if t := time.Since(p.state.counterTime).Seconds(); t > 0.5 { rate := float64(p.state.counterNumSinceLast) / t p.state.counterLastTenRates = append(p.state.counterLastTenRates, rate) @@ -551,14 +552,14 @@ func (p *ProgressBar) Add64(num int64) error { } // always update if show bytes/second or its/second - if updateBar || p.config.showIterationsPerSecond || p.config.showIterationsCount || num == 0 { + if updateBar || p.config.showIterationsPerSecond || p.config.showIterationsCount || delta == 0 { return p.render() } return nil } -// Clear erases the progress bar from the current line +// Clear erases the progress bar from the current line. func (p *ProgressBar) Clear() error { return clearProgressBar(p.config, p.state) } @@ -578,44 +579,68 @@ func (p *ProgressBar) Describe(description string) { p.render() } -// New64 returns a new ProgressBar -// with the specified maximum +// New64 returns a new ProgressBar with the specified maximum value. func New64(max int64) *ProgressBar { return NewOptions64(max) } -// GetMax returns the max of a bar -func (p *ProgressBar) GetMax() int { +// Max returns the maximum value of the progress bar. +func (p *ProgressBar) Max() int { + p.lock.Lock() + defer p.lock.Unlock() + return int(p.config.max) } -// GetMax64 returns the current max -func (p *ProgressBar) GetMax64() int64 { +// Max64 returns the current maximum value of the progress bar. +func (p *ProgressBar) Max64() int64 { + p.lock.Lock() + defer p.lock.Unlock() + return p.config.max } -// ChangeMax takes in a int -// and changes the max value -// of the progress bar -func (p *ProgressBar) ChangeMax(newMax int) { - p.ChangeMax64(int64(newMax)) +// AddMax adds the specified amount to the maximum value of the progress bar. +func (p *ProgressBar) AddMax(delta int) error { + p.lock.Lock() + defer p.lock.Unlock() + + return p.setMax(p.config.max + int64(delta)) +} + +// AddMax64 adds the specified amount to the maximum value of the progress bar. +func (p *ProgressBar) AddMax64(delta int64) error { + p.lock.Lock() + defer p.lock.Unlock() + + return p.setMax(p.config.max + delta) } -// ChangeMax64 is basically -// the same as ChangeMax, -// but takes in a int64 -// to avoid casting -func (p *ProgressBar) ChangeMax64(newMax int64) { - p.config.max = newMax +// SetMax sets the maximum value of the progress bar. +func (p *ProgressBar) SetMax(max int) error { + p.lock.Lock() + defer p.lock.Unlock() + + return p.setMax(int64(max)) +} +// SetMax64 sets the maximum value of the progress bar. +func (p *ProgressBar) SetMax64(max int64) error { + p.lock.Lock() + defer p.lock.Unlock() + + return p.setMax(max) +} + +func (p *ProgressBar) setMax(max int64) error { + p.config.max = max if p.config.showBytes { p.config.maxHumanized, p.config.maxHumanizedSuffix = humanizeBytes(float64(p.config.max)) } - - p.Add(0) // re-render + return p.add(0) // re-render } -// IsFinished returns true if progress bar is finished +// IsFinished returns true if progress bar is finished. func (p *ProgressBar) IsFinished() bool { return p.state.finished } @@ -699,7 +724,7 @@ func (p *ProgressBar) checkTrickyWidths() { p.config.trickyWidths = false } -// State returns the current state +// State returns the current state. func (p *ProgressBar) State() State { p.lock.Lock() defer p.lock.Unlock() @@ -714,7 +739,7 @@ func (p *ProgressBar) State() State { return s } -// regex matching ansi escape codes +// regex matching ansi escape codes. var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`) func getStringWidth(c config, str string, colorize bool) int { @@ -961,7 +986,7 @@ func writeString(c config, str string) error { return nil } -// Reader is the progressbar io.Reader struct +// Reader is the progressbar io.Reader struct. type Reader struct { io.Reader bar *ProgressBar @@ -975,14 +1000,14 @@ func NewReader(r io.Reader, bar *ProgressBar) Reader { } } -// Read will read the data and add the number of bytes to the progressbar +// Read will read the data and add the number of bytes to the progressbar. func (r *Reader) Read(p []byte) (n int, err error) { n, err = r.Reader.Read(p) r.bar.Add(n) return } -// Close the reader when it implements io.Closer +// Close the reader when it implements io.Closer. func (r *Reader) Close() (err error) { if closer, ok := r.Reader.(io.Closer); ok { return closer.Close() @@ -991,14 +1016,14 @@ func (r *Reader) Close() (err error) { return } -// Write implement io.Writer +// Write implement io.Writer. func (p *ProgressBar) Write(b []byte) (n int, err error) { n = len(b) p.Add(n) return } -// Read implement io.Reader +// Read implement io.Reader. func (p *ProgressBar) Read(b []byte) (n int, err error) { n = len(b) p.Add(n) @@ -1044,7 +1069,7 @@ func sp(s string, p bool) string { } // termWidth function returns the visible width of the current terminal -// and can be redefined for testing +// and can be redefined for testing. var termWidth = func() (width int, err error) { width, _, err = term.GetSize(int(os.Stdout.Fd())) if err == nil { diff --git a/progressbar_test.go b/progressbar_test.go index 144cdda..67bb2a3 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -155,10 +155,10 @@ func ExampleDefault() { // } -func ExampleProgressBar_ChangeMax() { +func ExampleProgressBar_SetMax() { bar := NewOptions(100, OptionSetWidth(10), OptionSetElapsedTime(false), OptionThrottle(time.Second)) bar.Add(50) - bar.ChangeMax(50) + bar.SetMax(50) // Output: // 100% |██████████| From e8546035cc06f222221eca5a05e4348a8fc01188 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Sun, 1 Oct 2023 16:30:58 +0300 Subject: [PATCH 24/26] Major renaming for usability and style --- .github/workflows/ci.yml | 4 +- progressbar.go | 228 ++++++++++++++++++--------------------- progressbar_test.go | 120 ++++++++++----------- 3 files changed, 169 insertions(+), 183 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cca9ff4..dfb0cf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: build on: push: - branches: [patch] + branches: [sideline, patch] pull_request: - branches: [patch] + branches: [sideline, patch] jobs: audit: diff --git a/progressbar.go b/progressbar.go index 7663083..fb6b4e7 100644 --- a/progressbar.go +++ b/progressbar.go @@ -83,9 +83,9 @@ type config struct { // always enabled if predictTime is true. elapsedTime bool - // whether the progress bar should attempt to predict the finishing + // whether the progress bar should attempt to estimate the finishing // time of the progress based on the start time and the average - // number of seconds between increments. + // number of seconds between increments. predictTime bool // minimum time to wait in between updates @@ -132,10 +132,10 @@ var spinners = map[int][]string{ // Option is the type all options need to adhere to. type Option func(p *ProgressBar) -// OptionSetWidth sets the width of the bar. -func OptionSetWidth(s int) Option { +// OptionWidth sets the width of the bar. +func OptionWidth(width int) Option { return func(p *ProgressBar) { - p.config.width = s + p.config.width = width } } @@ -147,18 +147,18 @@ func OptionSpinnerType(spinnerType int) Option { } } -// OptionSetTheme sets the elements the bar is constructed of. -func OptionSetTheme(t Theme) Option { +// OptionTheme sets the appearance of the elements of the progress bar. +func OptionTheme(theme Theme) Option { return func(p *ProgressBar) { - p.config.theme = t + p.config.theme = theme p.checkTrickyWidths() } } -// OptionSetVisibility sets the visibility. -func OptionSetVisibility(visibility bool) Option { +// OptionVisibility sets the visibility. +func OptionVisibility(visible bool) Option { return func(p *ProgressBar) { - p.config.invisible = !visibility + p.config.invisible = !visible } } @@ -169,126 +169,126 @@ func OptionFullWidth() Option { } } -// OptionSetWriter sets the output writer (defaults to os.Stdout). -func OptionSetWriter(w io.Writer) Option { +// OptionWriter sets the output writer (defaults to os.Stdout). +func OptionWriter(w io.Writer) Option { return func(p *ProgressBar) { p.config.writer = w } } -// OptionSetRenderBlankState sets whether or not to render a 0% bar on construction. -func OptionSetRenderBlankState(r bool) Option { +// OptionRenderBlankState sets whether or not to render a 0% bar on construction. +func OptionRenderBlankState(enable bool) Option { return func(p *ProgressBar) { - p.config.renderWithBlankState = r + p.config.renderWithBlankState = enable } } -// OptionSetDescription sets the description of the bar to render in front of it. -func OptionSetDescription(description string) Option { +// OptionDescription sets the description label of the progress bar. +func OptionDescription(s string) Option { return func(p *ProgressBar) { - p.config.description = description + p.config.description = s p.checkTrickyWidths() } } -// OptionEnableColorCodes enables or disables support for color codes. -func OptionEnableColorCodes(colorCodes bool) Option { +// OptionColorCodes enables or disables support for color codes. +func OptionColorCodes(enable bool) Option { return func(p *ProgressBar) { - p.config.colorCodes = colorCodes + p.config.colorCodes = enable } } -// OptionSetElapsedTime will enable elapsed time. Always enabled if OptionSetPredictTime is true. -func OptionSetElapsedTime(elapsedTime bool) Option { +// OptionElapsed enables elapsed time display. Always enabled if OptionEstimate is true. +func OptionElapsed(show bool) Option { return func(p *ProgressBar) { - p.config.elapsedTime = elapsedTime - if !elapsedTime { + p.config.elapsedTime = show + if !show { p.config.predictTime = false } } } -// OptionSetPredictTime will also attempt to predict the time remaining. -func OptionSetPredictTime(predictTime bool) Option { +// OptionEstimate enables display of remaining time estimate in addition to elapsed time. +func OptionEstimate(show bool) Option { return func(p *ProgressBar) { - p.config.predictTime = predictTime + p.config.predictTime = show } } -// OptionShowCount will also print current count out of total. +// OptionShowCount enables display of current count out of total. func OptionShowCount() Option { return func(p *ProgressBar) { p.config.showIterationsCount = true } } -// OptionShowIts will also print the iterations/second. +// OptionShowIts enables printing the iterations/second. func OptionShowIts() Option { return func(p *ProgressBar) { p.config.showIterationsPerSecond = true } } -// OptionSetItsString sets what's displayed for iterations a second. +// OptionItsString sets what's displayed for iterations a second. // The default is "it" which would display: "it/s". -func OptionSetItsString(iterationString string) Option { +func OptionItsString(its string) Option { return func(p *ProgressBar) { - p.config.iterationString = iterationString + p.config.iterationString = its p.checkTrickyWidths() } } // OptionTotalRate selects between total or recent average rate for rate display. -func OptionTotalRate(val bool) Option { +func OptionTotalRate() Option { return func(p *ProgressBar) { - p.config.totalRate = val + p.config.totalRate = true } } -// OptionThrottle will wait the specified duration before updating again. The default -// duration is 0 seconds. +// OptionThrottle enables waiting for the specified duration before updating again. +// Default is 0 seconds. func OptionThrottle(duration time.Duration) Option { return func(p *ProgressBar) { p.config.throttleDuration = duration } } -// OptionClearOnFinish will clear the bar once it's finished or stopped. +// OptionClearOnFinish enables clearing the bar once it's finished or stopped. func OptionClearOnFinish() Option { return func(p *ProgressBar) { p.config.clearOnFinish = true } } -// OptionOnCompletion will invoke cmpl function once it's finished or stopped. +// OptionOnCompletion provides a function that will be invoked once the bar is finished or stopped. func OptionOnCompletion(cmpl func()) Option { return func(p *ProgressBar) { p.config.onCompletion = cmpl } } -// OptionShowBytes will update the progress bar configuration settings to display/hide kBytes/Sec. -func OptionShowBytes(val bool) Option { +// OptionShowBytes enables display of kBytes/Sec. +func OptionShowBytes(enable bool) Option { return func(p *ProgressBar) { - p.config.showBytes = val + p.config.showBytes = enable } } -// OptionUseANSICodes will use more optimized terminal I/O. +// OptionUseANSICodes enables use of more optimized terminal I/O. // // Only useful in environments with support for ANSI escape sequences. -func OptionUseANSICodes(val bool) Option { +func OptionUseANSICodes(enable bool) Option { return func(p *ProgressBar) { - p.config.useANSICodes = val + p.config.useANSICodes = enable } } -// NewOptions constructs a new instance of ProgressBar, with any options you specify. +// NewOptions constructs a new instance of ProgressBar with specified options. func NewOptions(max int, options ...Option) *ProgressBar { return NewOptions64(int64(max), options...) } -// NewOptions64 constructs a new instance of ProgressBar, with any options you specify. +// NewOptions64 constructs a new instance of ProgressBar with specified options. func NewOptions64(max int64, options ...Option) *ProgressBar { b := ProgressBar{ state: getBasicState(), @@ -354,20 +354,17 @@ func DefaultBytes(maxBytes int64, description ...string) *ProgressBar { if len(description) > 0 { desc = description[0] } - return NewOptions64( - maxBytes, - OptionSetDescription(desc), - OptionSetWriter(os.Stderr), + return NewOptions64(maxBytes, + OptionDescription(desc), + OptionWriter(os.Stderr), OptionShowBytes(true), - OptionSetWidth(10), + OptionWidth(10), OptionThrottle(65*time.Millisecond), OptionShowCount(), - OptionOnCompletion(func() { - fmt.Fprint(os.Stderr, "\n") - }), + OptionOnCompletion(func() { fmt.Fprint(os.Stderr, "\n") }), OptionSpinnerType(14), OptionFullWidth(), - OptionSetRenderBlankState(true), + OptionRenderBlankState(true), ) } @@ -378,12 +375,11 @@ func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar { if len(description) > 0 { desc = description[0] } - return NewOptions64( - maxBytes, - OptionSetDescription(desc), - OptionSetWriter(io.Discard), + return NewOptions64(maxBytes, + OptionDescription(desc), + OptionWriter(io.Discard), OptionShowBytes(true), - OptionSetWidth(10), + OptionWidth(10), OptionThrottle(65*time.Millisecond), OptionShowCount(), OptionSpinnerType(14), @@ -398,20 +394,17 @@ func Default(max int64, description ...string) *ProgressBar { if len(description) > 0 { desc = description[0] } - return NewOptions64( - max, - OptionSetDescription(desc), - OptionSetWriter(os.Stderr), - OptionSetWidth(10), + return NewOptions64(max, + OptionDescription(desc), + OptionWriter(os.Stderr), + OptionWidth(10), OptionThrottle(65*time.Millisecond), OptionShowCount(), OptionShowIts(), - OptionOnCompletion(func() { - fmt.Fprint(os.Stderr, "\n") - }), + OptionOnCompletion(func() { fmt.Fprint(os.Stderr, "\n") }), OptionSpinnerType(14), OptionFullWidth(), - OptionSetRenderBlankState(true), + OptionRenderBlankState(true), ) } @@ -422,11 +415,10 @@ func DefaultSilent(max int64, description ...string) *ProgressBar { if len(description) > 0 { desc = description[0] } - return NewOptions64( - max, - OptionSetDescription(desc), - OptionSetWriter(io.Discard), - OptionSetWidth(10), + return NewOptions64(max, + OptionDescription(desc), + OptionWriter(io.Discard), + OptionWidth(10), OptionThrottle(65*time.Millisecond), OptionShowCount(), OptionShowIts(), @@ -435,7 +427,7 @@ func DefaultSilent(max int64, description ...string) *ProgressBar { ) } -// String returns the current rendered version of the progress bar. +// String returns the current rendering of the progress bar. // It will never return an empty string while the progress bar is running. func (p *ProgressBar) String() string { return p.state.rendered @@ -450,14 +442,14 @@ func (p *ProgressBar) RenderBlank() error { return p.render() } -// Reset will reset the clock that is used to calculate current time and the time left. +// Reset resets the progress bar to initial state. func (p *ProgressBar) Reset() { p.lock.Lock() p.state = getBasicState() p.lock.Unlock() } -// Finish will fill the bar to full. +// Finish fills the bar to full. func (p *ProgressBar) Finish() error { p.lock.Lock() defer p.lock.Unlock() @@ -477,7 +469,7 @@ func (p *ProgressBar) Stop() error { return p.add(0) } -// Add adds the specified amount to the progressbar value. +// Add adds the specified amount to the progressbar current value. func (p *ProgressBar) Add(delta int) error { p.lock.Lock() defer p.lock.Unlock() @@ -485,7 +477,7 @@ func (p *ProgressBar) Add(delta int) error { return p.add(int64(delta)) } -// Add64 adds the specified amount to the progressbar value. +// Add64 adds the specified amount to the progressbar current value. func (p *ProgressBar) Add64(delta int64) error { p.lock.Lock() defer p.lock.Unlock() @@ -493,7 +485,7 @@ func (p *ProgressBar) Add64(delta int64) error { return p.add(delta) } -// Set sets the progressbar bar value. +// Set sets the progressbar bar current value. func (p *ProgressBar) Set(value int) error { p.lock.Lock() defer p.lock.Unlock() @@ -501,7 +493,7 @@ func (p *ProgressBar) Set(value int) error { return p.add(int64(value) - int64(p.state.currentBytes)) } -// Set64 sets the progressbar bar value. +// Set64 sets the progressbar bar current value. func (p *ProgressBar) Set64(value int64) error { p.lock.Lock() defer p.lock.Unlock() @@ -513,7 +505,7 @@ func (p *ProgressBar) add(delta int64) error { if p.config.invisible { return nil } - if p.config.max == 0 { + if p.config.max <= 0 { return errors.New("max must be greater than 0") } @@ -559,18 +551,17 @@ func (p *ProgressBar) add(delta int64) error { return nil } -// Clear erases the progress bar from the current line. +// Clear erases progress bar from the current line. func (p *ProgressBar) Clear() error { return clearProgressBar(p.config, p.state) } -// Describe will change the description shown before the progress, which -// can be changed on the fly (as for a slow running process). -func (p *ProgressBar) Describe(description string) { +// SetDescription changes the description label of the progress bar. +func (p *ProgressBar) SetDescription(s string) { p.lock.Lock() defer p.lock.Unlock() - p.config.description = description + p.config.description = s p.checkTrickyWidths() if p.config.invisible { return @@ -584,7 +575,7 @@ func New64(max int64) *ProgressBar { return NewOptions64(max) } -// Max returns the maximum value of the progress bar. +// Max returns maximum value of the progress bar. func (p *ProgressBar) Max() int { p.lock.Lock() defer p.lock.Unlock() @@ -592,7 +583,7 @@ func (p *ProgressBar) Max() int { return int(p.config.max) } -// Max64 returns the current maximum value of the progress bar. +// Max64 returns maximum value of the progress bar. func (p *ProgressBar) Max64() int64 { p.lock.Lock() defer p.lock.Unlock() @@ -600,7 +591,7 @@ func (p *ProgressBar) Max64() int64 { return p.config.max } -// AddMax adds the specified amount to the maximum value of the progress bar. +// AddMax adds specified amount to the maximum value of the progress bar. func (p *ProgressBar) AddMax(delta int) error { p.lock.Lock() defer p.lock.Unlock() @@ -608,7 +599,7 @@ func (p *ProgressBar) AddMax(delta int) error { return p.setMax(p.config.max + int64(delta)) } -// AddMax64 adds the specified amount to the maximum value of the progress bar. +// AddMax64 adds specified amount to the maximum value of the progress bar. func (p *ProgressBar) AddMax64(delta int64) error { p.lock.Lock() defer p.lock.Unlock() @@ -733,27 +724,27 @@ func (p *ProgressBar) State() State { s.CurrentBytes = p.state.currentBytes s.SecondsSince = time.Since(p.state.startTime).Seconds() if p.state.currentNum > 0 { - s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * (float64(p.config.max) - float64(p.state.currentNum)) + s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * float64(p.config.max-p.state.currentNum) } s.KBsPerSecond = float64(p.state.currentBytes) / 1000 / s.SecondsSince return s } -// regex matching ansi escape codes. +// Regex matching ANSI escape codes. var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`) func getStringWidth(c config, str string, colorize bool) int { if c.colorCodes { - // convert any color codes in the progress bar into the respective ANSI codes + // convert any color codes in the progress bar into respective ANSI codes str = colorstring.Color(str) } - // the width of the string, if printed to the console - // does not include the carriage return character + // width of the string printed to console + // does not include carriage return characters cleanString := strings.ReplaceAll(str, "\r", "") if c.colorCodes { - // the ANSI codes for the colors do not take up space in the console output, + // ANSI codes for colors do not take up space in the console output, // so they do not count towards the output string width cleanString = ansiRegex.ReplaceAllString(cleanString, "") } @@ -904,7 +895,7 @@ func renderProgressBar(c config, s *state) (int, error) { if c.ignoreLength { if !s.finished { dt, st := time.Since(s.startTime).Milliseconds()/100, c.spinnerType - str = "\r" + + str = "\r " + spinners[st][int(math.Round(math.Mod(float64(dt), float64(len(spinners[st])))))] + sp(" ", c.description != "") + c.description + @@ -914,7 +905,7 @@ func renderProgressBar(c config, s *state) (int, error) { sp(leftBrac, c.elapsedTime) + sp("]", c.elapsedTime) + " " } else { - str = "\r 100%%" + + str = "\r 100%" + sp(" ", c.description != "") + c.description + sp(" ", sb.Len() > 0) + @@ -965,24 +956,20 @@ func clearProgressBar(c config, s state) error { // write the "clear current line" ANSI escape sequence return writeString(c, "\033[2K\r") } - // fill the empty content - // to overwrite the progress bar and jump - // back to the beginning of the line - return writeString(c, fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth))) + // overwrite the bar with spaces and return back to the beginning of the line + return writeString(c, "\r"+strings.Repeat(" ", s.maxLineWidth)+"\r") } func writeString(c config, str string) error { if _, err := io.WriteString(c.writer, str); err != nil { return err } - if f, ok := c.writer.(*os.File); ok { // ignore any errors in Sync(), as stdout // can't be synced on some operating systems // like Debian 9 (Stretch) f.Sync() } - return nil } @@ -992,7 +979,7 @@ type Reader struct { bar *ProgressBar } -// NewReader return a new Reader with a given progress bar. +// NewReader creates a new Reader with a given progress bar. func NewReader(r io.Reader, bar *ProgressBar) Reader { return Reader{ Reader: r, @@ -1000,39 +987,38 @@ func NewReader(r io.Reader, bar *ProgressBar) Reader { } } -// Read will read the data and add the number of bytes to the progressbar. +// Read reads buffer and adds the number of bytes to the progressbar. func (r *Reader) Read(p []byte) (n int, err error) { n, err = r.Reader.Read(p) r.bar.Add(n) return } -// Close the reader when it implements io.Closer. +// Close closes the embedded reader if it implements io.Closer and fills the bar to full. func (r *Reader) Close() (err error) { if closer, ok := r.Reader.(io.Closer); ok { - return closer.Close() + if err := closer.Close(); err != nil { + return err + } } - r.bar.Finish() - return + return r.bar.Finish() } -// Write implement io.Writer. +// Write implements io.Writer. func (p *ProgressBar) Write(b []byte) (n int, err error) { n = len(b) - p.Add(n) - return + return n, p.Add(n) } -// Read implement io.Reader. +// Read implements io.Reader. func (p *ProgressBar) Read(b []byte) (n int, err error) { n = len(b) - p.Add(n) - return + return n, p.Add(n) } +// Close implements io.Closer. func (p *ProgressBar) Close() (err error) { - p.Finish() - return + return p.Finish() } func average(xs []float64) float64 { diff --git a/progressbar_test.go b/progressbar_test.go index 67bb2a3..a35ec49 100644 --- a/progressbar_test.go +++ b/progressbar_test.go @@ -24,16 +24,16 @@ func TestMain(m *testing.M) { } func BenchmarkRenderSimple(b *testing.B) { - bar := NewOptions64(1e8, OptionSetWriter(io.Discard), OptionShowIts(), - OptionSetDescription("ÂŁ")) + bar := NewOptions64(1e8, OptionWriter(io.Discard), OptionShowIts(), + OptionDescription("ÂŁ")) for i := 0; i < b.N; i++ { bar.Add(1) } } func BenchmarkRenderTricky(b *testing.B) { - bar := NewOptions64(1e8, OptionSetWriter(io.Discard), OptionShowIts(), - OptionSetDescription("èż™æ˜Żäž€äžȘă€ăźæ”‹èŻ•")) + bar := NewOptions64(1e8, OptionWriter(io.Discard), OptionShowIts(), + OptionDescription("èż™æ˜Żäž€äžȘă€ăźæ”‹èŻ•")) for i := 0; i < b.N; i++ { bar.Add(1) } @@ -64,7 +64,7 @@ func ExampleProgressBar_Set64() { } func ExampleProgressBar_basic() { - bar := NewOptions(100, OptionSetWidth(10)) + bar := NewOptions(100, OptionWidth(10)) time.Sleep(1 * time.Second) bar.Add(10) @@ -73,7 +73,7 @@ func ExampleProgressBar_basic() { } func ExampleProgressBar_invisible() { - bar := NewOptions(100, OptionSetWidth(10), OptionSetRenderBlankState(true), OptionSetVisibility(false)) + bar := NewOptions(100, OptionWidth(10), OptionRenderBlankState(true), OptionVisibility(false)) fmt.Println("hello, world") time.Sleep(1 * time.Second) bar.Add(10) @@ -83,7 +83,7 @@ func ExampleProgressBar_invisible() { } func ExampleOptionThrottle() { - bar := NewOptions(100, OptionSetWidth(10), OptionThrottle(100*time.Millisecond)) + bar := NewOptions(100, OptionWidth(10), OptionThrottle(100*time.Millisecond)) bar.Add(5) time.Sleep(150 * time.Millisecond) bar.Add(5) @@ -94,7 +94,7 @@ func ExampleOptionThrottle() { } func ExampleOptionClearOnFinish() { - bar := NewOptions(100, OptionSetWidth(10), OptionClearOnFinish()) + bar := NewOptions(100, OptionWidth(10), OptionClearOnFinish()) bar.Finish() fmt.Println("Finished") @@ -103,7 +103,7 @@ func ExampleOptionClearOnFinish() { } func ExampleProgressBar_Finish() { - bar := NewOptions(100, OptionSetWidth(10), OptionSetElapsedTime(false)) + bar := NewOptions(100, OptionWidth(10), OptionElapsed(false)) bar.Finish() // Output: @@ -111,7 +111,7 @@ func ExampleProgressBar_Finish() { } func Example_xOutOfY() { - bar := NewOptions(100, OptionSetPredictTime(true)) + bar := NewOptions(100, OptionEstimate(true)) for i := 0; i < 100; i++ { bar.Add(1) time.Sleep(1 * time.Millisecond) @@ -119,7 +119,7 @@ func Example_xOutOfY() { } func ExampleOptionShowIts_count() { - bar := NewOptions(100, OptionSetWidth(10), OptionShowIts(), OptionShowCount()) + bar := NewOptions(100, OptionWidth(10), OptionShowIts(), OptionShowCount()) time.Sleep(1 * time.Second) bar.Add(10) @@ -128,7 +128,7 @@ func ExampleOptionShowIts_count() { } func ExampleOptionShowIts() { - bar := NewOptions(100, OptionSetWidth(10), OptionShowIts(), OptionSetElapsedTime(false)) + bar := NewOptions(100, OptionWidth(10), OptionShowIts(), OptionElapsed(false)) time.Sleep(1 * time.Second) bar.Add(10) @@ -137,7 +137,7 @@ func ExampleOptionShowIts() { } func ExampleOptionShowCount_minuscule() { - bar := NewOptions(10000, OptionSetWidth(10), OptionShowCount(), OptionSetElapsedTime(false)) + bar := NewOptions(10000, OptionWidth(10), OptionShowCount(), OptionElapsed(false)) bar.Add(1) // Output: @@ -156,7 +156,7 @@ func ExampleDefault() { } func ExampleProgressBar_SetMax() { - bar := NewOptions(100, OptionSetWidth(10), OptionSetElapsedTime(false), OptionThrottle(time.Second)) + bar := NewOptions(100, OptionWidth(10), OptionElapsed(false), OptionThrottle(time.Second)) bar.Add(50) bar.SetMax(50) @@ -164,11 +164,11 @@ func ExampleProgressBar_SetMax() { // 100% |██████████| } -func ExampleOptionSetDescription_spinner() { +func ExampleOptionDescription_spinner() { /* Spinner with description and iteration count */ - bar := NewOptions(-1, OptionSetDescription("Spinning"), OptionShowCount()) + bar := NewOptions(-1, OptionDescription("Spinning"), OptionShowCount()) time.Sleep(1 * time.Second) bar.Add(5) @@ -189,7 +189,7 @@ func ExampleOptionShowIts_spinner() { } func ExampleOptionSpinnerType() { - bar := NewOptions(-1, OptionSetDescription("Tracing"), OptionShowCount(), OptionSpinnerType(59)) + bar := NewOptions(-1, OptionDescription("Tracing"), OptionShowCount(), OptionSpinnerType(59)) time.Sleep(120 * time.Millisecond) bar.Add(11) @@ -248,7 +248,7 @@ func ExampleOptionShowBytes_spinner() { func TestBarSlowAdd(t *testing.T) { buf := strings.Builder{} - bar := NewOptions(100, OptionSetWidth(10), OptionShowIts(), OptionSetWriter(&buf)) + bar := NewOptions(100, OptionWidth(10), OptionShowIts(), OptionWriter(&buf)) time.Sleep(3 * time.Second) bar.Add(1) if !strings.Contains(buf.String(), "1%") { @@ -267,8 +267,8 @@ func TestBarSmallBytes(t *testing.T) { bar := NewOptions64(100000000, OptionShowBytes(true), OptionShowCount(), - OptionSetWidth(10), - OptionSetWriter(&buf)) + OptionWidth(10), + OptionWriter(&buf)) for i := 1; i < 10; i++ { time.Sleep(100 * time.Millisecond) bar.Add(1000) @@ -290,8 +290,8 @@ func TestBarFastBytes(t *testing.T) { bar := NewOptions64(1e8, OptionShowBytes(true), OptionShowCount(), - OptionSetWidth(10), - OptionSetWriter(&buf)) + OptionWidth(10), + OptionWriter(&buf)) time.Sleep(time.Millisecond) bar.Add(2e7) if !strings.Contains(buf.String(), " GB/s)") { @@ -311,7 +311,7 @@ func TestBar(t *testing.T) { } func TestState(t *testing.T) { - bar := NewOptions(100, OptionSetWidth(10)) + bar := NewOptions(100, OptionWidth(10)) time.Sleep(1 * time.Second) bar.Add(10) s := bar.State() @@ -320,8 +320,8 @@ func TestState(t *testing.T) { } } -func ExampleOptionSetRenderBlankState() { - NewOptions(10, OptionSetWidth(10), OptionSetRenderBlankState(true)) +func ExampleOptionRenderBlankState() { + NewOptions(10, OptionWidth(10), OptionRenderBlankState(true)) // Output: // 0% | | [0s:0s] @@ -329,9 +329,9 @@ func ExampleOptionSetRenderBlankState() { func TestBasicSets(t *testing.T) { b := NewOptions(333, - OptionSetWidth(222), - OptionSetRenderBlankState(true), - OptionSetWriter(io.Discard)) // suppressing output for this test + OptionWidth(222), + OptionRenderBlankState(true), + OptionWriter(io.Discard)) // suppressing output for this test tc := b.config @@ -347,12 +347,12 @@ func TestBasicSets(t *testing.T) { } } -func TestOptionSetTheme(t *testing.T) { +func TestOptionTheme(t *testing.T) { buf := strings.Builder{} bar := NewOptions(10, - OptionSetTheme(Theme{Saucer: "#", SaucerPadding: "-", BarStart: ">", BarEnd: "<"}), - OptionSetWidth(10), - OptionSetWriter(&buf)) + OptionTheme(Theme{Saucer: "#", SaucerPadding: "-", BarStart: ">", BarEnd: "<"}), + OptionWidth(10), + OptionWriter(&buf)) bar.Add(5) result := strings.TrimSpace(buf.String()) expect := "50% >#####-----< [0s:0s]" @@ -361,15 +361,15 @@ func TestOptionSetTheme(t *testing.T) { } } -// TestOptionSetPredictTime ensures that when predict time is turned off, the progress -// bar is showing the total steps completed of the given max, otherwise the predicted +// TestOptionEstimate ensures that when estimation is turned off, the progress +// bar is showing the total steps completed of the given max, otherwise the estimated // time in seconds is specified. -func TestOptionSetPredictTime(t *testing.T) { +func TestOptionEstimate(t *testing.T) { buf := strings.Builder{} bar := NewOptions(10, - OptionSetPredictTime(false), - OptionSetWidth(10), - OptionSetWriter(&buf)) + OptionEstimate(false), + OptionWidth(10), + OptionWriter(&buf)) bar.Add(2) result := strings.TrimSpace(buf.String()) @@ -392,12 +392,12 @@ func TestOptionSetPredictTime(t *testing.T) { } } -func TestOptionSetElapsedTime(t *testing.T) { +func TestOptionElapsed(t *testing.T) { buf := strings.Builder{} bar := NewOptions(10, - OptionSetElapsedTime(false), - OptionSetWidth(10), - OptionSetWriter(&buf)) + OptionElapsed(false), + OptionWidth(10), + OptionWriter(&buf)) bar.Add(2) result := strings.TrimSpace(buf.String()) expect := "20% |██ |" @@ -406,13 +406,13 @@ func TestOptionSetElapsedTime(t *testing.T) { } } -func TestOptionSetElapsedTime_spinner(t *testing.T) { +func TestOptionElapsed_spinner(t *testing.T) { buf := strings.Builder{} bar := NewOptions(-1, - OptionSetElapsedTime(false), + OptionElapsed(false), OptionShowIts(), OptionShowCount(), - OptionSetWriter(&buf)) + OptionWriter(&buf)) time.Sleep(1 * time.Second) bar.Add(5) result := strings.TrimSpace(buf.String()) @@ -423,7 +423,7 @@ func TestOptionSetElapsedTime_spinner(t *testing.T) { } func TestSpinnerState(t *testing.T) { - bar := NewOptions(-1, OptionSetWidth(100)) + bar := NewOptions(-1, OptionWidth(100)) time.Sleep(1 * time.Second) bar.Add(10) @@ -574,7 +574,7 @@ func TestReaderToFileUnknownLength(t *testing.T) { func TestConcurrency(t *testing.T) { buf := strings.Builder{} - bar := NewOptions(1000, OptionSetWriter(&buf)) + bar := NewOptions(1000, OptionWriter(&buf)) var wg sync.WaitGroup for i := 0; i < 900; i++ { wg.Add(1) @@ -599,7 +599,7 @@ func TestIterationNames(t *testing.T) { } // Change the default "it/s" to provide context, downloads per second or "dl/s" - b = NewOptions(20, OptionSetItsString("dl")) + b = NewOptions(20, OptionItsString("dl")) tc = b.config if tc.iterationString != "dl" { @@ -615,8 +615,8 @@ func md5sum(r io.Reader) (string, error) { func TestProgressBar_Describe(t *testing.T) { buf := strings.Builder{} - bar := NewOptions(100, OptionSetWidth(10), OptionSetWriter(&buf)) - bar.Describe("performing axial adjustments") + bar := NewOptions(100, OptionWidth(10), OptionWriter(&buf)) + bar.SetDescription("performing axial adjustments") bar.Add(10) result := buf.String() expect := "" + @@ -631,10 +631,10 @@ func TestProgressBar_Describe(t *testing.T) { func TestRenderBlankStateWithThrottle(t *testing.T) { buf := strings.Builder{} bar := NewOptions(100, - OptionSetWidth(10), - OptionSetRenderBlankState(true), + OptionWidth(10), + OptionRenderBlankState(true), OptionThrottle(time.Millisecond), - OptionSetWriter(&buf)) + OptionWriter(&buf)) result := strings.TrimSpace(buf.String()) expect := "0% | | [0s:0s]" if result != expect { @@ -655,21 +655,21 @@ func TestOptionFullWidth(t *testing.T) { "\r 100% |█████████████████████████████████████████████████████████████| [2s] ", }, { // 2 - []Option{OptionSetDescription("Progress:")}, + []Option{OptionDescription("Progress:")}, "" + "\rProgress: 10% |█████ | [1s:9s] " + "\r \r" + "\rProgress: 100% |████████████████████████████████████████████████████| [2s] ", }, { // 3 - []Option{OptionSetPredictTime(false)}, + []Option{OptionEstimate(false)}, "" + "\r 10% |██████ | [1s] " + "\r \r" + "\r 100% |████████████████████████████████████████████████████████████████| [2s] ", }, { // 4 - []Option{OptionSetPredictTime(false), OptionSetElapsedTime(false)}, + []Option{OptionEstimate(false), OptionElapsed(false)}, "" + "\r 10% |██████ | " + "\r \r" + @@ -690,21 +690,21 @@ func TestOptionFullWidth(t *testing.T) { "\r 100% |████████████████████████████████████████████████████| (100/100) [2s] ", }, { // 7 - []Option{OptionSetDescription("Progress:"), OptionShowIts(), OptionShowCount()}, + []Option{OptionDescription("Progress:"), OptionShowIts(), OptionShowCount()}, "" + "\rProgress: 10% |███ | (10/100, 10 it/s) [1s:9s] " + "\r \r" + "\rProgress: 100% |██████████████████████████████████| (100/100, 50 it/s) [2s] ", }, { // 8 - []Option{OptionShowIts(), OptionShowCount(), OptionSetPredictTime(false)}, + []Option{OptionShowIts(), OptionShowCount(), OptionEstimate(false)}, "" + "\r 10% |████ | (10/100, 10 it/s) [1s] " + "\r \r" + "\r 100% |██████████████████████████████████████████████| (100/100, 50 it/s) [2s] ", }, { // 9 - []Option{OptionShowIts(), OptionShowCount(), OptionSetPredictTime(false), OptionSetElapsedTime(false)}, + []Option{OptionShowIts(), OptionShowCount(), OptionEstimate(false), OptionElapsed(false)}, "" + "\r 10% |█████ | (10/100, 10 it/s) " + "\r \r" + @@ -717,7 +717,7 @@ func TestOptionFullWidth(t *testing.T) { t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) { t.Parallel() buf := strings.Builder{} - bar := NewOptions(100, append(test.opts, []Option{OptionFullWidth(), OptionSetWriter(&buf)}...)...) + bar := NewOptions(100, append(test.opts, []Option{OptionFullWidth(), OptionWriter(&buf)}...)...) time.Sleep(1 * time.Second) bar.Add(10) time.Sleep(1 * time.Second) From 94187df305f48b3fcb11e6033a88760eb5f3947f Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Sun, 1 Oct 2023 18:35:06 +0300 Subject: [PATCH 25/26] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 06ec710..fb7a4ea 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/schollz/progressbar/v3 +module github.com/oerlikon/progressbar/v3 go 1.19 From a58d4582d568f2d8c5fe949677d728bfa6ba57e7 Mon Sep 17 00:00:00 2001 From: Denis Romanov Date: Sun, 1 Oct 2023 18:55:49 +0300 Subject: [PATCH 26/26] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 7ee1282..97668a9 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ # progressbar + +[![build](https://github.com/oerlikon/progressbar/actions/workflows/ci.yml/badge.svg)](https://github.com/oerlikon/progressbar/actions/workflows/ci.yml) +[![Go Report](https://goreportcard.com/badge/github.com/oerlikon/progressbar/v3)](https://goreportcard.com/report/github.com/oerlikon/progressbar/v3) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Go Docs](https://img.shields.io/badge/docs-pkg.go.dev-007d9c)](https://pkg.go.dev/github.com/oerlikon/progressbar/v3) + +This repo is a significantly patched permanent fork of [github.com/schollz/progressbar](https://github.com/schollz/progressbar).