diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2ffdb4d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Test + +on: [push, pull_request] + +jobs: + test: + strategy: + matrix: + go-version: [1.16, 1.17] + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + # In order: + # * Module download cache + # * Build cache (Linux) + # * Build cache (Mac) + # * Build cache (Windows) + path: | + ~/go/pkg/mod + ~/.cache/go-build + ~/Library/Caches/go-build + %LocalAppData%\go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Test + run: go test -race ./... diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9a203a6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: go -arch: - - amd64 - - ppc64le - -go: - - 1.14.x - -script: - - go test -race ./... - - for i in _examples/*/; do go build $i/*.go || exit 1; done diff --git a/README.md b/README.md index d0560d7..413f9e1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Multi Progress Bar [![GoDoc](https://pkg.go.dev/badge/github.com/vbauerster/mpb)](https://pkg.go.dev/github.com/vbauerster/mpb/v7) -[![Build Status](https://travis-ci.org/vbauerster/mpb.svg?branch=master)](https://travis-ci.org/vbauerster/mpb) -[![Go Report Card](https://goreportcard.com/badge/github.com/vbauerster/mpb)](https://goreportcard.com/report/github.com/vbauerster/mpb) +[![Test status](https://github.com/vbauerster/mpb/actions/workflows/test.yml/badge.svg)](https://github.com/vbauerster/mpb/actions/workflows/test.yml) [![Donate with PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/vbauerster) **mpb** is a Go lib for rendering progress bars in terminal applications. @@ -37,10 +36,10 @@ total := 100 name := "Single Bar:" - // adding a single bar, which will inherit container's width - bar := p.Add(int64(total), - // progress bar filler with customized style - mpb.NewBarFiller(mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟")), + // create a single bar, which will inherit container's width + bar := p.New(int64(total), + // BarFillerBuilder with custom style + mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), mpb.PrependDecorators( // display our name with one space on the right decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), @@ -66,7 +65,7 @@ ```go var wg sync.WaitGroup - // passed &wg will be accounted at p.Wait() call + // passed wg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(&wg)) total, numBars := 100, 3 wg.Add(numBars) @@ -84,7 +83,7 @@ // replace ETA decorator with "done" message, OnComplete event decor.OnComplete( // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncWidth), "done", ), ), ) @@ -104,7 +103,7 @@ } }() } - // Waiting for passed &wg and for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() ``` diff --git a/_examples/barExtender/go.mod b/_examples/barExtender/go.mod index 9a2af7e..ca38865 100644 --- a/_examples/barExtender/go.mod +++ b/_examples/barExtender/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/barExtender/main.go b/_examples/barExtender/main.go index d4724b6..bb70f20 100644 --- a/_examples/barExtender/main.go +++ b/_examples/barExtender/main.go @@ -13,6 +13,7 @@ func main() { var wg sync.WaitGroup + // passed wg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(&wg)) total, numBars := 100, 3 wg.Add(numBars) @@ -55,6 +56,6 @@ } }() } - // wait for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/cancel/go.mod b/_examples/cancel/go.mod index b6db6b1..6b442db 100644 --- a/_examples/cancel/go.mod +++ b/_examples/cancel/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/cancel/main.go b/_examples/cancel/main.go index 4025b79..1528ffd 100644 --- a/_examples/cancel/main.go +++ b/_examples/cancel/main.go @@ -16,6 +16,7 @@ defer cancel() var wg sync.WaitGroup + // passed wg will be accounted at p.Wait() call p := mpb.NewWithContext(ctx, mpb.WithWaitGroup(&wg)) total := 300 numBars := 3 @@ -49,6 +50,6 @@ } }() } - + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/complex/go.mod b/_examples/complex/go.mod index 322a14d..2dad190 100644 --- a/_examples/complex/go.mod +++ b/_examples/complex/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/complex/main.go b/_examples/complex/main.go index 0e96a36..a5d42f4 100644 --- a/_examples/complex/main.go +++ b/_examples/complex/main.go @@ -16,6 +16,7 @@ func main() { doneWg := new(sync.WaitGroup) + // passed doneWg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(doneWg)) numBars := 4 @@ -64,7 +65,7 @@ go newTask(doneWg, b, numBars-i) }() } - + // wait for passed doneWg and for all bars to complete and flush p.Wait() } diff --git a/_examples/decoratorsOnTop/go.mod b/_examples/decoratorsOnTop/go.mod index 1f33097..65e4023 100644 --- a/_examples/decoratorsOnTop/go.mod +++ b/_examples/decoratorsOnTop/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/decoratorsOnTop/main.go b/_examples/decoratorsOnTop/main.go index 16163b6..a6e0ae6 100644 --- a/_examples/decoratorsOnTop/main.go +++ b/_examples/decoratorsOnTop/main.go @@ -13,7 +13,9 @@ p := mpb.New() total := 100 - bar := p.Add(int64(total), nil, + bar := p.New(int64(total), + mpb.NopStyle(), // make main bar style nop, so there are just decorators + mpb.BarExtender(extended(mpb.BarStyle())), // extend wtih normal bar on the next line mpb.PrependDecorators( decor.Name("Percentage: "), decor.NewPercentage("%d"), @@ -24,7 +26,6 @@ decor.AverageETA(decor.ET_STYLE_GO), "done", ), ), - mpb.BarExtender(nlBarFiller(mpb.NewBarFiller(mpb.BarStyle()))), ) // simulating some work max := 100 * time.Millisecond @@ -36,7 +37,8 @@ p.Wait() } -func nlBarFiller(filler mpb.BarFiller) mpb.BarFiller { +func extended(builder mpb.BarFillerBuilder) mpb.BarFiller { + filler := builder.Build() return mpb.BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) { filler.Fill(w, reqWidth, st) w.Write([]byte("\n")) diff --git a/_examples/differentWidth/go.mod b/_examples/differentWidth/go.mod index ffca3ec..31a8a42 100644 --- a/_examples/differentWidth/go.mod +++ b/_examples/differentWidth/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/differentWidth/main.go b/_examples/differentWidth/main.go index 378bb57..a358cff 100644 --- a/_examples/differentWidth/main.go +++ b/_examples/differentWidth/main.go @@ -12,9 +12,9 @@ func main() { var wg sync.WaitGroup + // passed wg will be accounted at p.Wait() call p := mpb.New( mpb.WithWaitGroup(&wg), - // container's width. mpb.WithWidth(60), ) total, numBars := 100, 3 @@ -55,6 +55,6 @@ } }() } - // wait for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/dynTotal/go.mod b/_examples/dynTotal/go.mod index 7e9bc47..ee1c623 100644 --- a/_examples/dynTotal/go.mod +++ b/_examples/dynTotal/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/io/go.mod b/_examples/io/go.mod index 6b373db..35e3563 100644 --- a/_examples/io/go.mod +++ b/_examples/io/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/io/main.go b/_examples/io/main.go index 6f2bf89..339d853 100644 --- a/_examples/io/main.go +++ b/_examples/io/main.go @@ -19,8 +19,8 @@ mpb.WithRefreshRate(180*time.Millisecond), ) - bar := p.Add(total, - mpb.NewBarFiller(mpb.BarStyle().Rbound("|")), + bar := p.New(total, + mpb.BarStyle().Rbound("|"), mpb.PrependDecorators( decor.CountersKibiByte("% .2f / % .2f"), ), diff --git a/_examples/merge/go.mod b/_examples/merge/go.mod index 9195740..b3c7177 100644 --- a/_examples/merge/go.mod +++ b/_examples/merge/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/merge/main.go b/_examples/merge/main.go index beda1d4..43f8c44 100644 --- a/_examples/merge/main.go +++ b/_examples/merge/main.go @@ -12,7 +12,7 @@ func main() { var wg sync.WaitGroup - // pass &wg (optional), so p will wait for it eventually + // passed wg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(60)) total, numBars := 100, 3 wg.Add(numBars) @@ -59,7 +59,7 @@ } }() } - // Waiting for passed &wg and for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/mexicanBar/go.mod b/_examples/mexicanBar/go.mod index 185d104..f9490e9 100644 --- a/_examples/mexicanBar/go.mod +++ b/_examples/mexicanBar/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/mexicanBar/main.go b/_examples/mexicanBar/main.go index c157ae9..ce2a733 100644 --- a/_examples/mexicanBar/main.go +++ b/_examples/mexicanBar/main.go @@ -20,8 +20,7 @@ bs.Tip("\u001b[0m⛵\u001b[36;1m") bs.Padding("_") bs.Rbound("\u001b[0m]") - bar := p.Add(int64(total), - mpb.NewBarFiller(bs), + bar := p.New(int64(total), bs, mpb.PrependDecorators(decor.Name(name)), mpb.AppendDecorators(decor.Percentage()), ) diff --git a/_examples/multiBars/go.mod b/_examples/multiBars/go.mod index 14d365a..1a37e92 100644 --- a/_examples/multiBars/go.mod +++ b/_examples/multiBars/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/multiBars/main.go b/_examples/multiBars/main.go index b16562a..ebca301 100644 --- a/_examples/multiBars/main.go +++ b/_examples/multiBars/main.go @@ -12,7 +12,7 @@ func main() { var wg sync.WaitGroup - // passed &wg will be accounted at p.Wait() call + // passed wg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(&wg)) total, numBars := 100, 3 wg.Add(numBars) @@ -30,7 +30,7 @@ // replace ETA decorator with "done" message, OnComplete event decor.OnComplete( // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncWidth), "done", ), ), ) @@ -50,6 +50,6 @@ } }() } - // Waiting for passed &wg and for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/panic/go.mod b/_examples/panic/go.mod index 8f26d32..5d81105 100644 --- a/_examples/panic/go.mod +++ b/_examples/panic/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/panic/main.go b/_examples/panic/main.go index ae0b890..a843ed5 100644 --- a/_examples/panic/main.go +++ b/_examples/panic/main.go @@ -13,7 +13,11 @@ func main() { var wg sync.WaitGroup - p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithDebugOutput(os.Stderr)) + // passed wg will be accounted at p.Wait() call + p := mpb.New( + mpb.WithWaitGroup(&wg), + mpb.WithDebugOutput(os.Stderr), + ) wantPanic := strings.Repeat("Panic ", 64) numBars := 3 @@ -31,7 +35,7 @@ } }() } - + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/poplog/go.mod b/_examples/poplog/go.mod index e7eba2a..0997bc9 100644 --- a/_examples/poplog/go.mod +++ b/_examples/poplog/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/quietMode/go.mod b/_examples/quietMode/go.mod index 246f3df..6d43d51 100644 --- a/_examples/quietMode/go.mod +++ b/_examples/quietMode/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/quietMode/main.go b/_examples/quietMode/main.go index 4c953d5..c3c7d71 100644 --- a/_examples/quietMode/main.go +++ b/_examples/quietMode/main.go @@ -20,7 +20,7 @@ func main() { flag.Parse() var wg sync.WaitGroup - // pass &wg (optional), so p will wait for it eventually + // passed wg will be accounted at p.Wait() call p := mpb.New( mpb.WithWaitGroup(&wg), mpb.ContainerOptional( @@ -68,7 +68,7 @@ } }() } - // Waiting for passed &wg and for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() fmt.Println("done") } diff --git a/_examples/remove/go.mod b/_examples/remove/go.mod index d48ed71..5c0f6bf 100644 --- a/_examples/remove/go.mod +++ b/_examples/remove/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/remove/main.go b/_examples/remove/main.go index d67d360..416df82 100644 --- a/_examples/remove/main.go +++ b/_examples/remove/main.go @@ -12,6 +12,7 @@ func main() { var wg sync.WaitGroup + // passed wg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(&wg)) total := 100 numBars := 3 @@ -24,29 +25,31 @@ mpb.BarOptional(mpb.BarRemoveOnComplete(), i == 0), mpb.PrependDecorators( decor.Name(name), - decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), ), - mpb.AppendDecorators(decor.Percentage()), + mpb.AppendDecorators( + decor.Any(func(s decor.Statistics) string { + return fmt.Sprintf("completed: %v", s.Completed) + }, decor.WCSyncSpaceR), + decor.Any(func(s decor.Statistics) string { + return fmt.Sprintf("aborted: %v", s.Aborted) + }, decor.WCSyncSpaceR), + decor.OnComplete(decor.NewPercentage("%d", decor.WCSyncSpace), "done"), + decor.OnAbort(decor.NewPercentage("%d", decor.WCSyncSpace), "ohno"), + ), ) go func() { defer wg.Done() rng := rand.New(rand.NewSource(time.Now().UnixNano())) max := 100 * time.Millisecond for i := 0; !bar.Completed(); i++ { - // start variable is solely for EWMA calculation - // EWMA's unit of measure is an iteration's duration - start := time.Now() if bar.ID() == 2 && i >= 42 { - // aborting and removing while bar is running - bar.Abort(true) + bar.Abort(false) } time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) bar.Increment() - // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract - bar.DecoratorEwmaUpdate(time.Since(start)) } }() } - + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/reverseBar/go.mod b/_examples/reverseBar/go.mod index 7c7eed0..a6b31e6 100644 --- a/_examples/reverseBar/go.mod +++ b/_examples/reverseBar/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/reverseBar/main.go b/_examples/reverseBar/main.go index 14a13be..542b63c 100644 --- a/_examples/reverseBar/main.go +++ b/_examples/reverseBar/main.go @@ -12,20 +12,14 @@ func main() { var wg sync.WaitGroup - // pass &wg (optional), so p will wait for it eventually + // passed wg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(&wg)) total, numBars := 100, 3 wg.Add(numBars) for i := 0; i < numBars; i++ { name := fmt.Sprintf("Bar#%d:", i) - bs := mpb.BarStyle() - if i == 1 { - // reverse Bar#1 - bs = bs.Tip("<").Reverse() - } - bar := p.Add(int64(total), - mpb.NewBarFiller(bs), + bar := p.New(int64(total), condBuilder(i == 1), mpb.PrependDecorators( // simple name decorator decor.Name(name), @@ -56,6 +50,17 @@ } }() } - // Waiting for passed &wg and for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() } + +func condBuilder(cond bool) mpb.BarFillerBuilder { + return mpb.BarFillerBuilderFunc(func() mpb.BarFiller { + bs := mpb.BarStyle() + if cond { + // reverse Bar on cond + bs = bs.Tip("<").Reverse() + } + return bs.Build() + }) +} diff --git a/_examples/singleBar/go.mod b/_examples/singleBar/go.mod index d74dd07..9aeaab6 100644 --- a/_examples/singleBar/go.mod +++ b/_examples/singleBar/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/singleBar/main.go b/_examples/singleBar/main.go index f0b87a3..925a2d5 100644 --- a/_examples/singleBar/main.go +++ b/_examples/singleBar/main.go @@ -14,10 +14,10 @@ total := 100 name := "Single Bar:" - // adding a single bar, which will inherit container's width - bar := p.Add(int64(total), - // progress bar filler with customized style - mpb.NewBarFiller(mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟")), + // create a single bar, which will inherit container's width + bar := p.New(int64(total), + // BarFillerBuilder with custom style + mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), mpb.PrependDecorators( // display our name with one space on the right decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), diff --git a/_examples/spinTipBar/go.mod b/_examples/spinTipBar/go.mod index 98f55ac..2a372f4 100644 --- a/_examples/spinTipBar/go.mod +++ b/_examples/spinTipBar/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/spinTipBar/main.go b/_examples/spinTipBar/main.go index 51aeb10..7d3f2ef 100644 --- a/_examples/spinTipBar/main.go +++ b/_examples/spinTipBar/main.go @@ -14,8 +14,8 @@ total := 100 name := "Single Bar:" - bar := p.Add(int64(total), - mpb.NewBarFiller(mpb.BarStyle().Tip(`-`, `\`, `|`, `/`)), + bar := p.New(int64(total), + mpb.BarStyle().Tip(`-`, `\`, `|`, `/`), mpb.PrependDecorators(decor.Name(name)), mpb.AppendDecorators(decor.Percentage()), ) diff --git a/_examples/spinnerBar/go.mod b/_examples/spinnerBar/go.mod index 75b4888..0875389 100644 --- a/_examples/spinnerBar/go.mod +++ b/_examples/spinnerBar/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/spinnerBar/main.go b/_examples/spinnerBar/main.go index bf3c8db..db3e080 100644 --- a/_examples/spinnerBar/main.go +++ b/_examples/spinnerBar/main.go @@ -12,50 +12,29 @@ func main() { var wg sync.WaitGroup + // passed wg will be accounted at p.Wait() call p := mpb.New( mpb.WithWaitGroup(&wg), - mpb.WithWidth(14), + mpb.WithWidth(16), ) total, numBars := 101, 3 wg.Add(numBars) - spinnerStyle := []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"} - for i := 0; i < numBars; i++ { name := fmt.Sprintf("Bar#%d:", i) - var bar *mpb.Bar - if i == 0 { - bar = p.Add(int64(total), - mpb.NewBarFiller(mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟")), - mpb.PrependDecorators( - // simple name decorator - decor.Name(name), + bar := p.New(int64(total), condBuilder(i != 0), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", ), - mpb.AppendDecorators( - // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", - ), - ), - ) - } else { - bar = p.Add(int64(total), - mpb.NewBarFiller(mpb.SpinnerStyle(spinnerStyle...)), - mpb.PrependDecorators( - // simple name decorator - decor.Name(name), - ), - mpb.AppendDecorators( - // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", - ), - ), - ) - } - + ), + ) // simulating some work go func() { defer wg.Done() @@ -72,6 +51,17 @@ } }() } - // wait for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() } + +func condBuilder(cond bool) mpb.BarFillerBuilder { + return mpb.BarFillerBuilderFunc(func() mpb.BarFiller { + if cond { + // spinner Bar on cond + frames := []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"} + return mpb.SpinnerStyle(frames...).Build() + } + return mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟").Build() + }) +} diff --git a/_examples/spinnerDecorator/go.mod b/_examples/spinnerDecorator/go.mod index 84dcf8e..c9050fd 100644 --- a/_examples/spinnerDecorator/go.mod +++ b/_examples/spinnerDecorator/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/spinnerDecorator/main.go b/_examples/spinnerDecorator/main.go index 4a64409..3ef27ba 100644 --- a/_examples/spinnerDecorator/main.go +++ b/_examples/spinnerDecorator/main.go @@ -12,7 +12,7 @@ func main() { var wg sync.WaitGroup - // pass &wg (optional), so p will wait for it eventually + // passed wg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(64)) total, numBars := 100, 3 wg.Add(numBars) @@ -44,6 +44,6 @@ } }() } - // Waiting for passed &wg and for all bars to complete and flush + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/stress/go.mod b/_examples/stress/go.mod index ee0641f..0ed6759 100644 --- a/_examples/stress/go.mod +++ b/_examples/stress/go.mod @@ -2,4 +2,4 @@ go 1.14 -require github.com/vbauerster/mpb/v7 v7.0.3 +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/stress/main.go b/_examples/stress/main.go index 86198a7..1b03731 100644 --- a/_examples/stress/main.go +++ b/_examples/stress/main.go @@ -16,10 +16,8 @@ func main() { var wg sync.WaitGroup - p := mpb.New( - mpb.WithWaitGroup(&wg), - mpb.WithRefreshRate(50*time.Millisecond), - ) + // passed wg will be accounted at p.Wait() call + p := mpb.New(mpb.WithWaitGroup(&wg)) wg.Add(totalBars) for i := 0; i < totalBars; i++ { @@ -47,6 +45,6 @@ } }() } - + // wait for passed wg and for all bars to complete and flush p.Wait() } diff --git a/_examples/suppressBar/go.mod b/_examples/suppressBar/go.mod index 61f139c..1856f06 100644 --- a/_examples/suppressBar/go.mod +++ b/_examples/suppressBar/go.mod @@ -4,5 +4,5 @@ require ( github.com/mattn/go-runewidth v0.0.13 - github.com/vbauerster/mpb/v7 v7.0.3 + github.com/vbauerster/mpb/v7 v7.3.2 ) diff --git a/_examples/tipOnComplete/go.mod b/_examples/tipOnComplete/go.mod new file mode 100644 index 0000000..c5e030b --- /dev/null +++ b/_examples/tipOnComplete/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/tipOnComplete + +go 1.14 + +require github.com/vbauerster/mpb/v7 v7.3.2 diff --git a/_examples/tipOnComplete/main.go b/_examples/tipOnComplete/main.go new file mode 100644 index 0000000..62a26b7 --- /dev/null +++ b/_examples/tipOnComplete/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "math/rand" + "time" + + "github.com/vbauerster/mpb/v7" + "github.com/vbauerster/mpb/v7/decor" +) + +func main() { + // initialize progress container, with custom width + p := mpb.New(mpb.WithWidth(80)) + + total := 100 + name := "Single Bar:" + bar := p.New(int64(total), + mpb.BarStyle().TipOnComplete(">"), + mpb.PrependDecorators(decor.Name(name)), + mpb.AppendDecorators(decor.Percentage()), + ) + // simulating some work + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) + bar.Increment() + } + // wait for our bar to complete and flush + p.Wait() +} diff --git a/bar.go b/bar.go index ed6c73e..646cb47 100644 --- a/bar.go +++ b/bar.go @@ -5,9 +5,9 @@ "context" "fmt" "io" - "log" "runtime/debug" "strings" + "sync" "time" "github.com/acarl005/stripansi" @@ -20,25 +20,21 @@ priority int // used by heap index int // used by heap - extendedLines int toShutdown bool toDrop bool noPop bool hasEwmaDecorators bool operateState chan func(*bState) - frameCh chan io.Reader - syncTableCh chan [][]chan int - completed chan bool + frameCh chan *frame // cancel is called either by user or on complete event cancel func() // done is closed after cacheState is assigned done chan struct{} - // cacheState is populated, right after close(shutdown) + // cacheState is populated, right after close(b.done) cacheState *bState container *Progress - dlogger *log.Logger recoveredPanic interface{} } @@ -53,11 +49,11 @@ total int64 current int64 refill int64 - lastN int64 - iterated bool + lastIncrement int64 trimSpace bool completed bool completeFlushed bool + aborted bool triggerComplete bool dropOnComplete bool noPop bool @@ -66,7 +62,7 @@ averageDecorators []decor.AverageDecorator ewmaDecorators []decor.EwmaDecorator shutdownListeners []decor.ShutdownListener - bufP, bufB, bufA *bytes.Buffer + buffers [3]*bytes.Buffer filler BarFiller middleware func(BarFiller) BarFiller extender extenderFunc @@ -77,8 +73,12 @@ debugOut io.Writer } +type frame struct { + reader io.Reader + lines int +} + func newBar(container *Progress, bs *bState) *Bar { - logPrefix := fmt.Sprintf("%sbar#%02d ", container.dlogger.Prefix(), bs.id) ctx, cancel := context.WithCancel(container.ctx) bar := &Bar{ @@ -87,12 +87,9 @@ toDrop: bs.dropOnComplete, noPop: bs.noPop, operateState: make(chan func(*bState)), - frameCh: make(chan io.Reader, 1), - syncTableCh: make(chan [][]chan int, 1), - completed: make(chan bool, 1), + frameCh: make(chan *frame, 1), done: make(chan struct{}), cancel: cancel, - dlogger: log.New(bs.debugOut, logPrefix, log.Lshortfile), } go bar.serve(ctx, bs) @@ -105,7 +102,7 @@ if r == nil { panic("expected non nil io.Reader") } - return newProxyReader(r, b) + return b.newProxyReader(r) } // ID returs id of the bar. @@ -145,6 +142,7 @@ // TraverseDecorators traverses all available decorators and calls cb func on each. func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) { + done := make(chan struct{}) select { case b.operateState <- func(s *bState) { for _, decorators := range [...][]decor.Decorator{ @@ -155,18 +153,20 @@ cb(extractBaseDecorator(d)) } } - }: + close(done) + }: + <-done case <-b.done: } } // SetTotal sets total dynamically. -// If total is less than or equal to zero it takes progress' current value. +// If total is negative it takes progress' current value. func (b *Bar) SetTotal(total int64, triggerComplete bool) { select { case b.operateState <- func(s *bState) { s.triggerComplete = triggerComplete - if total <= 0 { + if total < 0 { s.total = s.current } else { s.total = total @@ -174,7 +174,7 @@ if s.triggerComplete && !s.completed { s.current = s.total s.completed = true - go b.refreshTillShutdown() + go b.forceRefreshIfLastUncompleted() } }: case <-b.done: @@ -186,13 +186,12 @@ func (b *Bar) SetCurrent(current int64) { select { case b.operateState <- func(s *bState) { - s.iterated = true - s.lastN = current - s.current + s.lastIncrement = current - s.current s.current = current if s.triggerComplete && s.current >= s.total { s.current = s.total s.completed = true - go b.refreshTillShutdown() + go b.forceRefreshIfLastUncompleted() } }: case <-b.done: @@ -211,15 +210,17 @@ // IncrInt64 increments progress by amount of n. func (b *Bar) IncrInt64(n int64) { - select { - case b.operateState <- func(s *bState) { - s.iterated = true - s.lastN = n + if n <= 0 { + return + } + select { + case b.operateState <- func(s *bState) { + s.lastIncrement = n s.current += n if s.triggerComplete && s.current >= s.total { s.current = s.total s.completed = true - go b.refreshTillShutdown() + go b.forceRefreshIfLastUncompleted() } }: case <-b.done: @@ -233,10 +234,18 @@ func (b *Bar) DecoratorEwmaUpdate(dur time.Duration) { select { case b.operateState <- func(s *bState) { - ewmaIterationUpdate(false, s, dur) - }: - case <-b.done: - ewmaIterationUpdate(true, b.cacheState, dur) + if s.lastIncrement > 0 { + s.decoratorEwmaUpdate(dur) + s.lastIncrement = 0 + } else { + panic("increment required before ewma iteration update") + } + }: + case <-b.done: + if b.cacheState.lastIncrement > 0 { + b.cacheState.decoratorEwmaUpdate(dur) + b.cacheState.lastIncrement = 0 + } } } @@ -246,9 +255,7 @@ func (b *Bar) DecoratorAverageAdjust(start time.Time) { select { case b.operateState <- func(s *bState) { - for _, d := range s.averageDecorators { - d.AverageAdjust(start) - } + s.decoratorAverageAdjust(start) }: case <-b.done: } @@ -258,32 +265,55 @@ // priority, i.e. bar will be on top. If you don't need to set priority // dynamically, better use BarPriority option. func (b *Bar) SetPriority(priority int) { - select { - case <-b.done: - default: - b.container.setBarPriority(b, priority) - } -} - -// Abort interrupts bar's running goroutine. Call this, if you'd like -// to stop/remove bar before completion event. It has no effect after -// completion event. If drop is true bar will be removed as well. + b.container.UpdateBarPriority(b, priority) +} + +// Abort interrupts bar's running goroutine. Abort won't be engaged +// if bar is already in complete state. If drop is true bar will be +// removed as well. func (b *Bar) Abort(drop bool) { - select { - case <-b.done: - default: - if drop { - b.container.dropBar(b) - } + done := make(chan struct{}) + select { + case b.operateState <- func(s *bState) { + if s.completed { + close(done) + return + } + s.aborted = true b.cancel() + // container must be run during lifetime of this inner goroutine + // we control this by done channel declared above + go func() { + if drop { + b.container.dropBar(b) + } else { + var uncompleted int + b.container.traverseBars(func(bar *Bar) bool { + if b != bar && !bar.Completed() { + uncompleted++ + return false + } + return true + }) + if uncompleted == 0 { + b.container.refreshCh <- time.Now() + } + } + close(done) // release hold of Abort + }() + }: + // guarantee: container is alive during lifetime of this hold + <-done + case <-b.done: } } // Completed reports whether the bar is in completed state. func (b *Bar) Completed() bool { - select { - case b.operateState <- func(s *bState) { b.completed <- s.completed }: - return <-b.completed + result := make(chan bool) + select { + case b.operateState <- func(s *bState) { result <- s.completed }: + return <-result case <-b.done: return true } @@ -296,12 +326,9 @@ case op := <-b.operateState: op(s) case <-ctx.Done(): + s.decoratorShutdownNotify() b.cacheState = s close(b.done) - // Notifying decorators about shutdown event - for _, sl := range s.shutdownListeners { - sl.Shutdown() - } return } } @@ -315,21 +342,22 @@ // recovering if user defined decorator panics for example if p := recover(); p != nil { if b.recoveredPanic == nil { + if s.debugOut != nil { + fmt.Fprintln(s.debugOut, p) + _, _ = s.debugOut.Write(debug.Stack()) + } s.extender = makePanicExtender(p) b.toShutdown = !b.toShutdown b.recoveredPanic = p } - frame, lines := s.extender(nil, s.reqWidth, stat) - b.extendedLines = lines - b.frameCh <- frame - b.dlogger.Println(p) + reader, lines := s.extender(nil, s.reqWidth, stat) + b.frameCh <- &frame{reader, lines + 1} } s.completeFlushed = s.completed }() - frame, lines := s.extender(s.draw(stat), s.reqWidth, stat) - b.extendedLines = lines + reader, lines := s.extender(s.draw(stat), s.reqWidth, stat) b.toShutdown = s.completed && !s.completeFlushed - b.frameCh <- frame + b.frameCh <- &frame{reader, lines + 1} }: case <-b.done: s := b.cacheState @@ -338,9 +366,8 @@ if b.recoveredPanic == nil { r = s.draw(stat) } - frame, lines := s.extender(r, s.reqWidth, stat) - b.extendedLines = lines - b.frameCh <- frame + reader, lines := s.extender(r, s.reqWidth, stat) + b.frameCh <- &frame{reader, lines + 1} } } @@ -359,71 +386,83 @@ shutdownListeners = append(shutdownListeners, d) } }) + b.hasEwmaDecorators = len(ewmaDecorators) != 0 select { case b.operateState <- func(s *bState) { s.averageDecorators = averageDecorators s.ewmaDecorators = ewmaDecorators s.shutdownListeners = shutdownListeners }: - b.hasEwmaDecorators = len(ewmaDecorators) != 0 - case <-b.done: - } -} - -func (b *Bar) refreshTillShutdown() { - for { - select { - case b.container.refreshCh <- time.Now(): - case <-b.done: - return + case <-b.done: + } +} + +func (b *Bar) forceRefreshIfLastUncompleted() { + var uncompleted int + b.container.traverseBars(func(bar *Bar) bool { + if b != bar && !bar.Completed() { + uncompleted++ + return false + } + return true + }) + if uncompleted == 0 { + for { + select { + case b.container.refreshCh <- time.Now(): + case <-b.done: + return + } } } } func (b *Bar) wSyncTable() [][]chan int { - select { - case b.operateState <- func(s *bState) { b.syncTableCh <- s.wSyncTable() }: - return <-b.syncTableCh + result := make(chan [][]chan int) + select { + case b.operateState <- func(s *bState) { result <- s.wSyncTable() }: + return <-result case <-b.done: return b.cacheState.wSyncTable() } } func (s *bState) draw(stat decor.Statistics) io.Reader { + bufP, bufB, bufA := s.buffers[0], s.buffers[1], s.buffers[2] nlr := strings.NewReader("\n") tw := stat.AvailableWidth for _, d := range s.pDecorators { str := d.Decor(stat) stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str)) - s.bufP.WriteString(str) + bufP.WriteString(str) } if stat.AvailableWidth < 1 { - trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(s.bufP.String()), tw, "…")) - s.bufP.Reset() + trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(bufP.String()), tw, "…")) + bufP.Reset() return io.MultiReader(trunc, nlr) } if !s.trimSpace && stat.AvailableWidth > 1 { stat.AvailableWidth -= 2 - s.bufB.WriteByte(' ') - defer s.bufB.WriteByte(' ') + bufB.WriteByte(' ') + defer bufB.WriteByte(' ') } tw = stat.AvailableWidth for _, d := range s.aDecorators { str := d.Decor(stat) stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str)) - s.bufA.WriteString(str) + bufA.WriteString(str) } if stat.AvailableWidth < 1 { - trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(s.bufA.String()), tw, "…")) - s.bufA.Reset() - return io.MultiReader(s.bufP, s.bufB, trunc, nlr) - } - - s.filler.Fill(s.bufB, s.reqWidth, stat) - - return io.MultiReader(s.bufP, s.bufB, s.bufA, nlr) + trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(bufA.String()), tw, "…")) + bufA.Reset() + return io.MultiReader(bufP, bufB, trunc, nlr) + } + + s.filler.Fill(bufB, s.reqWidth, stat) + + return io.MultiReader(bufP, bufB, bufA, nlr) } func (s *bState) wSyncTable() [][]chan int { @@ -446,6 +485,57 @@ table[0] = columns[0:pCount] table[1] = columns[pCount : pCount+aCount : pCount+aCount] return table +} + +func (s bState) decoratorEwmaUpdate(dur time.Duration) { + wg := new(sync.WaitGroup) + for i := 0; i < len(s.ewmaDecorators); i++ { + switch d := s.ewmaDecorators[i]; i { + case len(s.ewmaDecorators) - 1: + d.EwmaUpdate(s.lastIncrement, dur) + default: + wg.Add(1) + go func() { + d.EwmaUpdate(s.lastIncrement, dur) + wg.Done() + }() + } + } + wg.Wait() +} + +func (s bState) decoratorAverageAdjust(start time.Time) { + wg := new(sync.WaitGroup) + for i := 0; i < len(s.averageDecorators); i++ { + switch d := s.averageDecorators[i]; i { + case len(s.averageDecorators) - 1: + d.AverageAdjust(start) + default: + wg.Add(1) + go func() { + d.AverageAdjust(start) + wg.Done() + }() + } + } + wg.Wait() +} + +func (s bState) decoratorShutdownNotify() { + wg := new(sync.WaitGroup) + for i := 0; i < len(s.shutdownListeners); i++ { + switch d := s.shutdownListeners[i]; i { + case len(s.shutdownListeners) - 1: + d.Shutdown() + default: + wg.Add(1) + go func() { + d.Shutdown() + wg.Done() + }() + } + } + wg.Wait() } func newStatistics(tw int, s *bState) decor.Statistics { @@ -456,6 +546,7 @@ Current: s.current, Refill: s.refill, Completed: s.completeFlushed, + Aborted: s.aborted, } } @@ -466,27 +557,13 @@ return d } -func ewmaIterationUpdate(done bool, s *bState, dur time.Duration) { - if !done && !s.iterated { - panic("increment required before ewma iteration update") - } else { - s.iterated = false - } - for _, d := range s.ewmaDecorators { - d.EwmaUpdate(s.lastN, dur) - } -} - func makePanicExtender(p interface{}) extenderFunc { pstr := fmt.Sprint(p) - stack := debug.Stack() - stackLines := bytes.Count(stack, []byte("\n")) return func(_ io.Reader, _ int, st decor.Statistics) (io.Reader, int) { mr := io.MultiReader( strings.NewReader(runewidth.Truncate(pstr, st.AvailableWidth, "…")), - strings.NewReader(fmt.Sprintf("\n%#v\n", st)), - bytes.NewReader(stack), + strings.NewReader("\n"), ) - return mr, stackLines + 1 - } -} + return mr, 0 + } +} diff --git a/bar_filler.go b/bar_filler.go index a69087c..81177fc 100644 --- a/bar_filler.go +++ b/bar_filler.go @@ -9,31 +9,42 @@ // BarFiller interface. // Bar (without decorators) renders itself by calling BarFiller's Fill method. // -// reqWidth is requested width, set by `func WithWidth(int) ContainerOption`. +// reqWidth is requested width set by `func WithWidth(int) ContainerOption`. // If not set, it defaults to terminal width. -// -// Default implementations can be obtained via: -// -// func NewBarFiller(BarStyle()) BarFiller -// func NewBarFiller(SpinnerStyle()) BarFiller // type BarFiller interface { Fill(w io.Writer, reqWidth int, stat decor.Statistics) } // BarFillerBuilder interface. +// Default implementations are: +// +// BarStyle() +// SpinnerStyle() +// NopStyle() +// type BarFillerBuilder interface { Build() BarFiller } -// BarFillerFunc is function type adapter to convert function into BarFiller. +// BarFillerFunc is function type adapter to convert compatible function +// into BarFiller interface. type BarFillerFunc func(w io.Writer, reqWidth int, stat decor.Statistics) func (f BarFillerFunc) Fill(w io.Writer, reqWidth int, stat decor.Statistics) { f(w, reqWidth, stat) } +// BarFillerBuilderFunc is function type adapter to convert compatible +// function into BarFillerBuilder interface. +type BarFillerBuilderFunc func() BarFiller + +func (f BarFillerBuilderFunc) Build() BarFiller { + return f() +} + // NewBarFiller constructs a BarFiller from provided BarFillerBuilder. +// Deprecated. Prefer using `*Progress.New(...)` directly. func NewBarFiller(b BarFillerBuilder) BarFiller { return b.Build() } diff --git a/bar_filler_bar.go b/bar_filler_bar.go index e30d492..54b7bfd 100644 --- a/bar_filler_bar.go +++ b/bar_filler_bar.go @@ -26,17 +26,19 @@ Filler(string) BarStyleComposer Refiller(string) BarStyleComposer Padding(string) BarStyleComposer - Tip(...string) BarStyleComposer + TipOnComplete(string) BarStyleComposer + Tip(frames ...string) BarStyleComposer Reverse() BarStyleComposer } type bFiller struct { + rev bool components [components]*component tip struct { - count uint - frames []*component - } - flush func(dst io.Writer, filling, padding [][]byte) + count uint + onComplete *component + frames []*component + } } type component struct { @@ -45,25 +47,26 @@ } type barStyle struct { - lbound string - rbound string - filler string - refiller string - padding string - tip []string - rev bool + lbound string + rbound string + filler string + refiller string + padding string + tipOnComplete string + tipFrames []string + rev bool } // BarStyle constructs default bar style which can be altered via // BarStyleComposer interface. func BarStyle() BarStyleComposer { return &barStyle{ - lbound: "[", - rbound: "]", - filler: "=", - refiller: "+", - padding: "-", - tip: []string{">"}, + lbound: "[", + rbound: "]", + filler: "=", + refiller: "+", + padding: "-", + tipFrames: []string{">"}, } } @@ -92,9 +95,14 @@ return s } -func (s *barStyle) Tip(tip ...string) BarStyleComposer { - if len(tip) != 0 { - s.tip = append(s.tip[:0], tip...) +func (s *barStyle) TipOnComplete(tip string) BarStyleComposer { + s.tipOnComplete = tip + return s +} + +func (s *barStyle) Tip(frames ...string) BarStyleComposer { + if len(frames) != 0 { + s.tipFrames = append(s.tipFrames[:0], frames...) } return s } @@ -105,14 +113,7 @@ } func (s *barStyle) Build() BarFiller { - bf := new(bFiller) - if s.rev { - bf.flush = func(dst io.Writer, filling, padding [][]byte) { - flush(dst, padding, filling) - } - } else { - bf.flush = flush - } + bf := &bFiller{rev: s.rev} bf.components[iLbound] = &component{ width: runewidth.StringWidth(stripansi.Strip(s.lbound)), bytes: []byte(s.lbound), @@ -133,8 +134,12 @@ width: runewidth.StringWidth(stripansi.Strip(s.padding)), bytes: []byte(s.padding), } - bf.tip.frames = make([]*component, len(s.tip)) - for i, t := range s.tip { + bf.tip.onComplete = &component{ + width: runewidth.StringWidth(stripansi.Strip(s.tipOnComplete)), + bytes: []byte(s.tipOnComplete), + } + bf.tip.frames = make([]*component, len(s.tipFrames)) + for i, t := range s.tipFrames { bf.tip.frames[i] = &component{ width: runewidth.StringWidth(stripansi.Strip(t)), bytes: []byte(t), @@ -146,74 +151,106 @@ func (s *bFiller) Fill(w io.Writer, width int, stat decor.Statistics) { width = internal.CheckRequestedWidth(width, stat.AvailableWidth) brackets := s.components[iLbound].width + s.components[iRbound].width - if width < brackets { - return - } // don't count brackets as progress width -= brackets - - w.Write(s.components[iLbound].bytes) - defer w.Write(s.components[iRbound].bytes) - - curWidth := int(internal.PercentageRound(stat.Total, stat.Current, width)) - refWidth, filled := 0, curWidth - filling := make([][]byte, 0, curWidth) - - if curWidth > 0 && curWidth != width { - tipFrame := s.tip.frames[s.tip.count%uint(len(s.tip.frames))] - filling = append(filling, tipFrame.bytes) - curWidth -= tipFrame.width + if width < 0 { + return + } + + ow := optimisticWriter(w) + ow(s.components[iLbound].bytes) + defer ow(s.components[iRbound].bytes) + + if width == 0 { + return + } + + var filling [][]byte + var padding [][]byte + var tip *component + var filled int + var refWidth int + curWidth := int(internal.PercentageRound(stat.Total, stat.Current, uint(width))) + + if stat.Current >= stat.Total { + tip = s.tip.onComplete + } else { + tip = s.tip.frames[s.tip.count%uint(len(s.tip.frames))] + } + + if curWidth > 0 { + filling = append(filling, tip.bytes) + filled += tip.width s.tip.count++ } - if stat.Refill > 0 && curWidth > 0 { - refWidth = int(internal.PercentageRound(stat.Total, int64(stat.Refill), width)) - if refWidth > curWidth { - refWidth = curWidth - } + if stat.Refill > 0 { + refWidth = int(internal.PercentageRound(stat.Total, stat.Refill, uint(width))) curWidth -= refWidth - } - - for curWidth > 0 && curWidth >= s.components[iFiller].width { - filling = append(filling, s.components[iFiller].bytes) - curWidth -= s.components[iFiller].width - if s.components[iFiller].width == 0 { - break - } - } - - for refWidth > 0 && refWidth >= s.components[iRefiller].width { - filling = append(filling, s.components[iRefiller].bytes) - refWidth -= s.components[iRefiller].width - if s.components[iRefiller].width == 0 { - break - } - } - - filled -= curWidth + refWidth + refWidth += curWidth + } + + for filled < curWidth { + if curWidth-filled >= s.components[iFiller].width { + filling = append(filling, s.components[iFiller].bytes) + if s.components[iFiller].width == 0 { + break + } + filled += s.components[iFiller].width + } else { + filling = append(filling, []byte("…")) + filled++ + } + } + + for filled < refWidth { + if refWidth-filled >= s.components[iRefiller].width { + filling = append(filling, s.components[iRefiller].bytes) + if s.components[iRefiller].width == 0 { + break + } + filled += s.components[iRefiller].width + } else { + filling = append(filling, []byte("…")) + filled++ + } + } + padWidth := width - filled - padding := make([][]byte, 0, padWidth) - for padWidth > 0 && padWidth >= s.components[iPadding].width { - padding = append(padding, s.components[iPadding].bytes) - padWidth -= s.components[iPadding].width - if s.components[iPadding].width == 0 { - break - } - } - for padWidth > 0 { - padding = append(padding, []byte("…")) - padWidth-- - } - - s.flush(w, filling, padding) -} - -func flush(dst io.Writer, filling, padding [][]byte) { + if padWidth >= s.components[iPadding].width { + padding = append(padding, s.components[iPadding].bytes) + if s.components[iPadding].width == 0 { + break + } + padWidth -= s.components[iPadding].width + } else { + padding = append(padding, []byte("…")) + padWidth-- + } + } + + if s.rev { + flush(ow, padding, filling) + } else { + flush(ow, filling, padding) + } +} + +func flush(ow func([]byte), filling, padding [][]byte) { for i := len(filling) - 1; i >= 0; i-- { - dst.Write(filling[i]) + ow(filling[i]) } for i := 0; i < len(padding); i++ { - dst.Write(padding[i]) - } -} + ow(padding[i]) + } +} + +func optimisticWriter(w io.Writer) func([]byte) { + return func(p []byte) { + _, err := w.Write(p) + if err != nil { + panic(err) + } + } +} diff --git a/bar_filler_nop.go b/bar_filler_nop.go new file mode 100644 index 0000000..1a7086f --- /dev/null +++ b/bar_filler_nop.go @@ -0,0 +1,14 @@ +package mpb + +import ( + "io" + + "github.com/vbauerster/mpb/v7/decor" +) + +// NopStyle provides BarFillerBuilder which builds NOP BarFiller. +func NopStyle() BarFillerBuilder { + return BarFillerBuilderFunc(func() BarFiller { + return BarFillerFunc(func(io.Writer, int, decor.Statistics) {}) + }) +} diff --git a/bar_filler_spinner.go b/bar_filler_spinner.go index 58ae1c5..d38525e 100644 --- a/bar_filler_spinner.go +++ b/bar_filler_spinner.go @@ -73,15 +73,19 @@ return } + var err error rest := width - frameWidth switch s.position { case positionLeft: - io.WriteString(w, frame+strings.Repeat(" ", rest)) + _, err = io.WriteString(w, frame+strings.Repeat(" ", rest)) case positionRight: - io.WriteString(w, strings.Repeat(" ", rest)+frame) + _, err = io.WriteString(w, strings.Repeat(" ", rest)+frame) default: str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) - io.WriteString(w, str) + _, err = io.WriteString(w, str) + } + if err != nil { + panic(err) } s.count++ } diff --git a/bar_option.go b/bar_option.go index 46b7de0..4ba4905 100644 --- a/bar_option.go +++ b/bar_option.go @@ -5,11 +5,19 @@ "io" "github.com/vbauerster/mpb/v7/decor" - "github.com/vbauerster/mpb/v7/internal" ) // BarOption is a func option to alter default behavior of a bar. type BarOption func(*bState) + +func skipNil(decorators []decor.Decorator) (filtered []decor.Decorator) { + for _, d := range decorators { + if d != nil { + filtered = append(filtered, d) + } + } + return +} func (s *bState) addDecorators(dest *[]decor.Decorator, decorators ...decor.Decorator) { type mergeWrapper interface { @@ -26,14 +34,14 @@ // AppendDecorators let you inject decorators to the bar's right side. func AppendDecorators(decorators ...decor.Decorator) BarOption { return func(s *bState) { - s.addDecorators(&s.aDecorators, decorators...) + s.addDecorators(&s.aDecorators, skipNil(decorators)...) } } // PrependDecorators let you inject decorators to the bar's left side. func PrependDecorators(decorators ...decor.Decorator) BarOption { return func(s *bState) { - s.addDecorators(&s.pDecorators, decorators...) + s.addDecorators(&s.pDecorators, skipNil(decorators)...) } } @@ -81,7 +89,10 @@ return BarFillerMiddleware(func(base BarFiller) BarFiller { return BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) { if st.Completed { - io.WriteString(w, message) + _, err := io.WriteString(w, message) + if err != nil { + panic(err) + } } else { base.Fill(w, reqWidth, st) } @@ -138,9 +149,12 @@ } } -// BarOptional will invoke provided option only when pick is true. -func BarOptional(option BarOption, pick bool) BarOption { - return BarOptOn(option, internal.Predicate(pick)) +// BarOptional will invoke provided option only when cond is true. +func BarOptional(option BarOption, cond bool) BarOption { + if cond { + return option + } + return nil } // BarOptOn will invoke provided option only when higher order predicate diff --git a/bar_test.go b/bar_test.go index 59d9f88..42451c0 100644 --- a/bar_test.go +++ b/bar_test.go @@ -63,7 +63,7 @@ till := 30 refiller := "+" - bar := p.Add(int64(total), mpb.NewBarFiller(mpb.BarStyle().Refiller(refiller)), mpb.BarFillerTrim()) + bar := p.New(int64(total), mpb.BarStyle().Refiller(refiller), mpb.BarFillerTrim()) bar.SetRefill(int64(till)) bar.IncrBy(till) @@ -152,7 +152,7 @@ bs.Tip(string(runes[2])) bs.Padding(string(runes[3])) bs.Rbound(string(runes[4])) - bar := p.Add(int64(total), mpb.NewBarFiller(bs), mpb.BarFillerTrim()) + bar := p.New(int64(total), bs, mpb.BarFillerTrim()) for i := 0; i < total; i++ { bar.Increment() @@ -255,24 +255,27 @@ } func TestDecorStatisticsAvailableWidth(t *testing.T) { + total := 100 + down := make(chan struct{}) + checkDone := make(chan struct{}) td1 := func(s decor.Statistics) string { if s.AvailableWidth != 80 { t.Errorf("expected AvailableWidth %d got %d\n", 80, s.AvailableWidth) } return fmt.Sprintf("\x1b[31;1;4m%s\x1b[0m", strings.Repeat("0", 20)) } - checkDone := make(chan struct{}) td2 := func(s decor.Statistics) string { defer func() { - checkDone <- struct{}{} + select { + case checkDone <- struct{}{}: + default: + } }() if s.AvailableWidth != 40 { t.Errorf("expected AvailableWidth %d got %d\n", 40, s.AvailableWidth) } return "" } - total := 100 - down := make(chan struct{}) p := mpb.New( mpb.WithWidth(100), mpb.WithShutdownNotifier(down), @@ -293,7 +296,7 @@ for { select { case <-checkDone: - bar.Abort(false) + bar.Abort(true) case <-down: return } diff --git a/barbench_test.go b/barbench_test.go index ab1d602..7926afa 100644 --- a/barbench_test.go +++ b/barbench_test.go @@ -1,43 +1,58 @@ package mpb import ( - "io/ioutil" + "sync" "testing" - - "github.com/vbauerster/mpb/v7/decor" ) -func BenchmarkIncrSingleBar(b *testing.B) { - p := New(WithOutput(ioutil.Discard), WithWidth(80)) - bar := p.AddBar(int64(b.N)) - for i := 0; i < b.N; i++ { - bar.Increment() - } +const total = 1000 + +func BenchmarkIncrementOneBar(b *testing.B) { + benchBody(1, b) } -func BenchmarkIncrSingleBarWhileIsNotCompleted(b *testing.B) { - p := New(WithOutput(ioutil.Discard), WithWidth(80)) - bar := p.AddBar(int64(b.N)) - for !bar.Completed() { - bar.Increment() - } +func BenchmarkIncrementTwoBars(b *testing.B) { + benchBody(2, b) } -func BenchmarkIncrSingleBarWithNameDecorator(b *testing.B) { - p := New(WithOutput(ioutil.Discard), WithWidth(80)) - bar := p.AddBar(int64(b.N), PrependDecorators(decor.Name("test"))) - for i := 0; i < b.N; i++ { - bar.Increment() - } +func BenchmarkIncrementThreeBars(b *testing.B) { + benchBody(3, b) } -func BenchmarkIncrSingleBarWithNameAndEwmaETADecorator(b *testing.B) { - p := New(WithOutput(ioutil.Discard), WithWidth(80)) - bar := p.AddBar(int64(b.N), - PrependDecorators(decor.Name("test")), - AppendDecorators(decor.EwmaETA(decor.ET_STYLE_GO, 60)), - ) +func BenchmarkIncrementFourBars(b *testing.B) { + benchBody(4, b) +} + +func benchBody(n int, b *testing.B) { + p := New(WithOutput(nil), WithWidth(80)) + wg := new(sync.WaitGroup) + b.ResetTimer() for i := 0; i < b.N; i++ { - bar.Increment() + for j := 0; j < n; j++ { + switch j { + case n - 1: + bar := p.AddBar(total) + for c := 0; c < total; c++ { + bar.Increment() + } + if !bar.Completed() { + b.Fail() + } + default: + wg.Add(1) + go func() { + bar := p.AddBar(total) + for c := 0; c < total; c++ { + bar.Increment() + } + if !bar.Completed() { + b.Fail() + } + wg.Done() + }() + } + } + wg.Wait() } + p.Wait() } diff --git a/container_option.go b/container_option.go index e4254f6..e523a17 100644 --- a/container_option.go +++ b/container_option.go @@ -5,8 +5,6 @@ "io/ioutil" "sync" "time" - - "github.com/vbauerster/mpb/v7/internal" ) // ContainerOption is a func option to alter default behavior of a bar @@ -62,7 +60,11 @@ // have been rendered. func WithShutdownNotifier(ch chan struct{}) ContainerOption { return func(s *pState) { - s.shutdownNotifier = ch + select { + case <-ch: + default: + s.shutdownNotifier = ch + } } } @@ -97,9 +99,12 @@ } } -// ContainerOptional will invoke provided option only when pick is true. -func ContainerOptional(option ContainerOption, pick bool) ContainerOption { - return ContainerOptOn(option, internal.Predicate(pick)) +// ContainerOptional will invoke provided option only when cond is true. +func ContainerOptional(option ContainerOption, cond bool) ContainerOption { + if cond { + return option + } + return nil } // ContainerOptOn will invoke provided option only when higher order diff --git a/cwriter/cuuAndEd_construction_bench_test.go b/cwriter/cuuAndEd_construction_bench_test.go index af80be9..25d585b 100644 --- a/cwriter/cuuAndEd_construction_bench_test.go +++ b/cwriter/cuuAndEd_construction_bench_test.go @@ -18,7 +18,7 @@ func BenchmarkWithJoin(b *testing.B) { bCuuAndEd := [][]byte{[]byte("\x1b["), []byte("A\x1b[J")} for i := 0; i < b.N; i++ { - ioutil.Discard.Write(bytes.Join(bCuuAndEd, []byte(strconv.Itoa(4)))) + _, _ = ioutil.Discard.Write(bytes.Join(bCuuAndEd, []byte(strconv.Itoa(4)))) } } @@ -26,14 +26,14 @@ escOpen := []byte("\x1b[") cuuAndEd := []byte("A\x1b[J") for i := 0; i < b.N; i++ { - ioutil.Discard.Write(append(strconv.AppendInt(escOpen, 4, 10), cuuAndEd...)) + _, _ = ioutil.Discard.Write(append(strconv.AppendInt(escOpen, 4, 10), cuuAndEd...)) } } func BenchmarkWithCopy(b *testing.B) { w := New(ioutil.Discard) - w.lineCount = 4 + w.lines = 4 for i := 0; i < b.N; i++ { - w.ansiCuuAndEd() + _ = w.ansiCuuAndEd() } } diff --git a/cwriter/writer.go b/cwriter/writer.go index 1ade547..eaf541c 100644 --- a/cwriter/writer.go +++ b/cwriter/writer.go @@ -22,7 +22,7 @@ type Writer struct { out io.Writer buf bytes.Buffer - lineCount int + lines int fd int isTerminal bool } @@ -38,15 +38,15 @@ } // Flush flushes the underlying buffer. -func (w *Writer) Flush(lineCount int) (err error) { +func (w *Writer) Flush(lines int) (err error) { // some terminals interpret 'cursor up 0' as 'cursor up 1' - if w.lineCount > 0 { + if w.lines > 0 { err = w.clearLines() if err != nil { return } } - w.lineCount = lineCount + w.lines = lines _, err = w.buf.WriteTo(w.out) return } @@ -76,9 +76,9 @@ return tw, err } -func (w *Writer) ansiCuuAndEd() (err error) { +func (w *Writer) ansiCuuAndEd() error { buf := make([]byte, 8) - buf = strconv.AppendInt(buf[:copy(buf, escOpen)], int64(w.lineCount), 10) - _, err = w.out.Write(append(buf, cuuAndEd...)) - return + buf = strconv.AppendInt(buf[:copy(buf, escOpen)], int64(w.lines), 10) + _, err := w.out.Write(append(buf, cuuAndEd...)) + return err } diff --git a/cwriter/writer_windows.go b/cwriter/writer_windows.go index 1a69c81..8f99dbe 100644 --- a/cwriter/writer_windows.go +++ b/cwriter/writer_windows.go @@ -26,7 +26,7 @@ return err } - info.CursorPosition.Y -= int16(w.lineCount) + info.CursorPosition.Y -= int16(w.lines) if info.CursorPosition.Y < 0 { info.CursorPosition.Y = 0 } @@ -40,7 +40,7 @@ X: info.Window.Left, Y: info.CursorPosition.Y, } - count := uint32(info.Size.X) * uint32(w.lineCount) + count := uint32(info.Size.X) * uint32(w.lines) _, _, _ = procFillConsoleOutputCharacter.Call( uintptr(w.fd), uintptr(' '), diff --git a/debian/changelog b/debian/changelog index f5bfbde..32c9fea 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,32 +1,13 @@ -golang-github-vbauerster-mpb (7.0.3-1) unstable; urgency=medium +golang-github-vbauerster-mpb (7.3.2-1) unstable; urgency=medium - * New upstream release + * Team upload. + * New upstream version + * Standards-Version: 4.6.0 (routine-update) + * debhelper-compat 13 (routine-update) + * Rules-Requires-Root: no (routine-update) + * (Build-)Depends: golang-github-mattn-go-runewidth-dev - -- Reinhard Tartler Sat, 28 Aug 2021 22:10:19 +0200 - -golang-github-vbauerster-mpb (6.0.3-2) unstable; urgency=medium - - * Upload to unstable - - -- Reinhard Tartler Sat, 28 Aug 2021 21:11:02 +0200 - -golang-github-vbauerster-mpb (6.0.3-1) experimental; urgency=medium - - * Team upload - * Upgrade to version v6.0.3, Closes: #987511) - Breaking changes (cf. https://github.com/vbauerster/mpb/releases): - Removed: - func BarStyle(style string) BarOption - func BarReverse() BarOption - func SpinnerStyle(frames []string) BarOption - func TrimSpace() BarOption - To set bar style use BarFiller constructors. - . - Added: - func ContainerOptional(option ContainerOption, pick bool) ContainerOption - func BarOptional(option BarOption, pick bool) BarOption - - -- Reinhard Tartler Sun, 25 Apr 2021 11:32:28 -0400 + -- Andreas Tille Wed, 26 Jan 2022 20:33:53 +0100 golang-github-vbauerster-mpb (5.0.3-2) unstable; urgency=low diff --git a/debian/control b/debian/control index 1672367..c876d21 100644 --- a/debian/control +++ b/debian/control @@ -4,16 +4,17 @@ Maintainer: Debian Go Packaging Team Uploaders: Reinhard Tartler , Dmitry Smirnov , -Build-Depends: debhelper-compat (= 12), +Build-Depends: debhelper-compat (= 13), dh-golang, golang-any, golang-github-acarl005-stripansi-dev, golang-github-mattn-go-isatty-dev, golang-github-mattn-go-runewidth-dev, - golang-github-rivo-uniseg-dev, golang-github-vividcortex-ewma-dev, -Standards-Version: 4.5.0 + golang-golang-x-crypto-dev +Standards-Version: 4.6.0 Homepage: https://github.com/vbauerster/mpb +Rules-Requires-Root: no Vcs-Browser: https://salsa.debian.org/go-team/packages/golang-github-vbauerster-mpb Vcs-Git: https://salsa.debian.org/go-team/packages/golang-github-vbauerster-mpb.git XS-Go-Import-Path: github.com/vbauerster/mpb @@ -25,8 +26,8 @@ golang-github-acarl005-stripansi-dev, golang-github-mattn-go-isatty-dev, golang-github-mattn-go-runewidth-dev, - golang-github-rivo-uniseg-dev, golang-github-vividcortex-ewma-dev, + golang-golang-x-crypto-dev Description: multi progress bar for Go cli applications mpb is a golang library for rendering progress bars in terminal applications. diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml new file mode 100644 index 0000000..594e14e --- /dev/null +++ b/debian/gitlab-ci.yml @@ -0,0 +1,6 @@ +# auto-generated, DO NOT MODIFY. +# The authoritative copy of this file lives at: +# https://salsa.debian.org/go-team/infra/pkg-go-tools/blob/master/config/gitlabciyml.go +--- +include: + - https://salsa.debian.org/go-team/infra/pkg-go-tools/-/raw/master/pipeline/test-archive.yml diff --git a/decor/decorator.go b/decor/decorator.go index e81fae3..9fec57b 100644 --- a/decor/decorator.go +++ b/decor/decorator.go @@ -53,6 +53,7 @@ Current int64 Refill int64 Completed bool + Aborted bool } // Decorator interface. diff --git a/decor/merge.go b/decor/merge.go index e41406a..8476711 100644 --- a/decor/merge.go +++ b/decor/merge.go @@ -17,6 +17,9 @@ // +----+--------+---------+--------+ // func Merge(decorator Decorator, placeholders ...WC) Decorator { + if decorator == nil { + return nil + } if _, ok := decorator.Sync(); !ok || len(placeholders) == 0 { return decorator } diff --git a/decor/on_abort.go b/decor/on_abort.go new file mode 100644 index 0000000..10ff670 --- /dev/null +++ b/decor/on_abort.go @@ -0,0 +1,41 @@ +package decor + +// OnAbort returns decorator, which wraps provided decorator with sole +// purpose to display provided message on abort event. It has no effect +// if bar.Abort(drop bool) is called with true argument. +// +// `decorator` Decorator to wrap +// +// `message` message to display on abort event +// +func OnAbort(decorator Decorator, message string) Decorator { + if decorator == nil { + return nil + } + d := &onAbortWrapper{ + Decorator: decorator, + msg: message, + } + if md, ok := decorator.(*mergeDecorator); ok { + d.Decorator, md.Decorator = md.Decorator, d + return md + } + return d +} + +type onAbortWrapper struct { + Decorator + msg string +} + +func (d *onAbortWrapper) Decor(s Statistics) string { + if s.Aborted { + wc := d.GetConf() + return wc.FormatMsg(d.msg) + } + return d.Decorator.Decor(s) +} + +func (d *onAbortWrapper) Base() Decorator { + return d.Decorator +} diff --git a/decor/on_complete.go b/decor/on_complete.go index f46b19a..2ada2b3 100644 --- a/decor/on_complete.go +++ b/decor/on_complete.go @@ -1,6 +1,6 @@ package decor -// OnComplete returns decorator, which wraps provided decorator, with +// OnComplete returns decorator, which wraps provided decorator with // sole purpose to display provided message on complete event. // // `decorator` Decorator to wrap @@ -8,6 +8,9 @@ // `message` message to display on complete event // func OnComplete(decorator Decorator, message string) Decorator { + if decorator == nil { + return nil + } d := &onCompleteWrapper{ Decorator: decorator, msg: message, diff --git a/decor/on_condition.go b/decor/on_condition.go new file mode 100644 index 0000000..a9db065 --- /dev/null +++ b/decor/on_condition.go @@ -0,0 +1,27 @@ +package decor + +// OnPredicate returns decorator if predicate evaluates to true. +// +// `decorator` Decorator +// +// `predicate` func() bool +// +func OnPredicate(decorator Decorator, predicate func() bool) Decorator { + if predicate() { + return decorator + } + return nil +} + +// OnCondition returns decorator if condition is true. +// +// `decorator` Decorator +// +// `cond` bool +// +func OnCondition(decorator Decorator, cond bool) Decorator { + if cond { + return decorator + } + return nil +} diff --git a/decor/optimistic_string_writer.go b/decor/optimistic_string_writer.go new file mode 100644 index 0000000..ea9fda7 --- /dev/null +++ b/decor/optimistic_string_writer.go @@ -0,0 +1,12 @@ +package decor + +import "io" + +func optimisticStringWriter(w io.Writer) func(string) { + return func(s string) { + _, err := io.WriteString(w, s) + if err != nil { + panic(err) + } + } +} diff --git a/decor/percentage.go b/decor/percentage.go index 2b0a7a9..6e7f5c6 100644 --- a/decor/percentage.go +++ b/decor/percentage.go @@ -2,7 +2,6 @@ import ( "fmt" - "io" "strconv" "github.com/vbauerster/mpb/v7/internal" @@ -24,12 +23,12 @@ } } - io.WriteString(st, strconv.FormatFloat(float64(s), 'f', prec, 64)) - + osw := optimisticStringWriter(st) + osw(strconv.FormatFloat(float64(s), 'f', prec, 64)) if st.Flag(' ') { - io.WriteString(st, " ") + osw(" ") } - io.WriteString(st, "%") + osw("%") } // Percentage returns percentage decorator. It's a wrapper of NewPercentage. diff --git a/decor/size_type.go b/decor/size_type.go index e4b9740..12879b8 100644 --- a/decor/size_type.go +++ b/decor/size_type.go @@ -2,8 +2,6 @@ import ( "fmt" - "io" - "math" "strconv" ) @@ -47,16 +45,16 @@ unit = _iMiB case self < _iTiB: unit = _iGiB - case self <= math.MaxInt64: + default: unit = _iTiB } - io.WriteString(st, strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) - + osw := optimisticStringWriter(st) + osw(strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) if st.Flag(' ') { - io.WriteString(st, " ") + osw(" ") } - io.WriteString(st, unit.String()) + osw(unit.String()) } const ( @@ -96,14 +94,14 @@ unit = _MB case self < _TB: unit = _GB - case self <= math.MaxInt64: + default: unit = _TB } - io.WriteString(st, strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) - + osw := optimisticStringWriter(st) + osw(strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) if st.Flag(' ') { - io.WriteString(st, " ") + osw(" ") } - io.WriteString(st, unit.String()) + osw(unit.String()) } diff --git a/decor/speed.go b/decor/speed.go index 634edab..99cfde2 100644 --- a/decor/speed.go +++ b/decor/speed.go @@ -2,7 +2,6 @@ import ( "fmt" - "io" "math" "time" @@ -24,7 +23,7 @@ func (self *speedFormatter) Format(st fmt.State, verb rune) { self.Formatter.Format(st, verb) - io.WriteString(st, "/s") + optimisticStringWriter(st)("/s") } // EwmaSpeed exponential-weighted-moving-average based speed decorator. diff --git a/draw_test.go b/draw_test.go index dbd3bc0..0c7888a 100644 --- a/draw_test.go +++ b/draw_test.go @@ -6,7 +6,7 @@ "unicode/utf8" ) -func TestDraw(t *testing.T) { +func TestDrawDefault(t *testing.T) { // key is termWidth testSuite := map[int][]struct { style BarStyleComposer @@ -20,12 +20,14 @@ }{ 0: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: "", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, @@ -35,12 +37,14 @@ }, 1: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: "", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, @@ -50,12 +54,14 @@ }, 2: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: " ", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, @@ -65,102 +71,296 @@ }, 3: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: " ", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, trim: true, want: "[-]", }, + { + style: BarStyle(), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " ", + }, + { + style: BarStyle(), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[>]", + }, + { + style: BarStyle(), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " ", + }, + { + style: BarStyle(), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[=]", + }, }, 4: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: " [] ", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, trim: true, want: "[>-]", }, + { + style: BarStyle(), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [] ", + }, + { + style: BarStyle(), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[=>]", + }, + { + style: BarStyle(), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [] ", + }, + { + style: BarStyle(), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[==]", + }, }, 5: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: " [-] ", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, trim: true, want: "[>--]", }, + { + style: BarStyle(), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [>] ", + }, + { + style: BarStyle(), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[==>]", + }, + { + style: BarStyle(), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [=] ", + }, + { + style: BarStyle(), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[===]", + }, }, 6: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: " [>-] ", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, trim: true, want: "[>---]", }, + { + style: BarStyle(), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [=>] ", + }, + { + style: BarStyle(), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[===>]", + }, + { + style: BarStyle(), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [==] ", + }, + { + style: BarStyle(), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[====]", + }, }, 7: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: " [>--] ", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, trim: true, want: "[=>---]", }, + { + style: BarStyle(), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [==>] ", + }, + { + style: BarStyle(), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[====>]", + }, + { + style: BarStyle(), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [===] ", + }, + { + style: BarStyle(), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[=====]", + }, }, 8: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: " [>---] ", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, trim: true, want: "[=>----]", }, + { + style: BarStyle(), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [===>] ", + }, + { + style: BarStyle(), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[=====>]", + }, + { + style: BarStyle(), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [====] ", + }, + { + style: BarStyle(), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[======]", + }, }, 80: { { + style: BarStyle(), name: "t,c{60,20}", total: 60, current: 20, want: " [========================>---------------------------------------------------] ", }, { + style: BarStyle(), name: "t,c{60,20}trim", total: 60, current: 20, @@ -168,6 +368,7 @@ want: "[=========================>----------------------------------------------------]", }, { + style: BarStyle(), name: "t,c,bw{60,20,60}", total: 60, current: 20, @@ -175,6 +376,7 @@ want: " [==================>---------------------------------------] ", }, { + style: BarStyle(), name: "t,c,bw{60,20,60}trim", total: 60, current: 20, @@ -182,66 +384,230 @@ trim: true, want: "[==================>---------------------------------------]", }, + { + style: BarStyle(), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [==========================================================================>-] ", + }, + { + style: BarStyle(), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[============================================================================>-]", + }, + { + style: BarStyle(), + name: "t,c,bw{60,59,60}", + total: 60, + current: 59, + barWidth: 60, + want: " [========================================================>-] ", + }, + { + style: BarStyle(), + name: "t,c,bw{60,59,60}trim", + total: 60, + current: 59, + barWidth: 60, + trim: true, + want: "[========================================================>-]", + }, + { + style: BarStyle(), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [============================================================================] ", + }, + { + style: BarStyle(), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[==============================================================================]", + }, + { + style: BarStyle(), + name: "t,c,bw{60,60,60}", + total: 60, + current: 60, + barWidth: 60, + want: " [==========================================================] ", + }, + { + style: BarStyle(), + name: "t,c,bw{60,60,60}trim", + total: 60, + current: 60, + barWidth: 60, + trim: true, + want: "[==========================================================]", + }, }, 99: { { - style: BarStyle().Lbound("[").Filler("の").Tip("だ").Padding("つ").Rbound("]"), - name: "[のだつ] t,c{99,1}", - total: 99, + style: BarStyle(), + name: "t,c{100,1}", + total: 100, current: 1, - want: " [だつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", - }, - { - style: BarStyle().Lbound("[").Filler("の").Tip("だ").Padding("つ").Rbound("]"), - name: "[のだつ] t,c{99,2}", - total: 99, - current: 2, - want: " [だつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", - }, - { - style: BarStyle().Lbound("[").Filler("の").Tip("だ").Padding("つ").Rbound("]"), - name: "[のだつ] t,c{99,3}", - total: 99, - current: 3, - want: " [だつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", - }, - { - style: BarStyle().Lbound("[").Filler("の").Tip("だ").Padding("つ").Rbound("]"), - name: "[のだつ] t,c{99,4}", - total: 99, - current: 4, - want: " [のだつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", - }, - { - style: BarStyle().Lbound("[").Filler("の").Tip("だ").Padding("つ").Rbound("]"), - name: "[のだつ] t,c{99,5}", - total: 99, - current: 5, - want: " [のだつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", - }, - { - style: BarStyle().Lbound("[").Filler("の").Tip("だ").Padding("つ").Rbound("]"), - name: "[のだつ] t,c{99,6}", - total: 99, - current: 6, - want: " [ののだつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", - }, - { - style: BarStyle().Lbound("[").Filler("の").Tip("だ").Padding("つ").Rbound("]").Reverse(), - name: "[のだつ] t,c{99,6}rev", - total: 99, - current: 6, - want: " […つつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつだのの] ", + want: " [>----------------------------------------------------------------------------------------------] ", + }, + { + style: BarStyle(), + name: "t,c{100,1}trim", + total: 100, + current: 1, + trim: true, + want: "[>------------------------------------------------------------------------------------------------]", + }, + { + style: BarStyle(), + name: "t,c,r{100,40,33}", + total: 100, + current: 40, + refill: 33, + want: " [+++++++++++++++++++++++++++++++======>---------------------------------------------------------] ", + }, + { + style: BarStyle(), + name: "t,c,r{100,40,33}trim", + total: 100, + current: 40, + refill: 33, + trim: true, + want: "[++++++++++++++++++++++++++++++++======>----------------------------------------------------------]", + }, + { + style: BarStyle().Tip("<").Reverse(), + name: "t,c,r{100,40,33},rev", + total: 100, + current: 40, + refill: 33, + want: " [---------------------------------------------------------<======+++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle().Tip("<").Reverse(), + name: "t,c,r{100,40,33}trim,rev", + total: 100, + current: 40, + refill: 33, + trim: true, + want: "[----------------------------------------------------------<======++++++++++++++++++++++++++++++++]", + }, + { + style: BarStyle(), + name: "t,c{100,99}", + total: 100, + current: 99, + want: " [=============================================================================================>-] ", + }, + { + style: BarStyle(), + name: "t,c{100,99}trim", + total: 100, + current: 99, + trim: true, + want: "[===============================================================================================>-]", + }, + { + style: BarStyle(), + name: "t,c{100,100}", + total: 100, + current: 100, + want: " [===============================================================================================] ", + }, + { + style: BarStyle(), + name: "t,c{100,100}trim", + total: 100, + current: 100, + trim: true, + want: "[=================================================================================================]", + }, + { + style: BarStyle(), + name: "t,c,r{100,100,99}", + total: 100, + current: 100, + refill: 99, + want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=] ", + }, + { + style: BarStyle(), + name: "t,c,r{100,100,99}trim", + total: 100, + current: 100, + refill: 99, + trim: true, + want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=]", + }, + { + style: BarStyle().Reverse(), + name: "t,c,r{100,100,99}rev", + total: 100, + current: 100, + refill: 99, + want: " [=++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle().Reverse(), + name: "t,c,r{100,100,99}trim,rev", + total: 100, + current: 100, + refill: 99, + trim: true, + want: "[=++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", + }, + { + style: BarStyle(), + name: "t,c,r{100,100,100}", + total: 100, + current: 100, + refill: 100, + want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle(), + name: "t,c,r{100,100,100}trim", + total: 100, + current: 100, + refill: 100, + trim: true, + want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", + }, + { + style: BarStyle().Reverse(), + name: "t,c,r{100,100,100}rev", + total: 100, + current: 100, + refill: 100, + want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle().Reverse(), + name: "t,c,r{100,100,100}trim", + total: 100, + current: 100, + refill: 100, + trim: true, + want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", }, }, 100: { { + style: BarStyle(), name: "t,c{100,0}", total: 100, current: 0, want: " [------------------------------------------------------------------------------------------------] ", }, { + style: BarStyle(), name: "t,c{100,0}trim", total: 100, current: 0, @@ -249,12 +615,14 @@ want: "[--------------------------------------------------------------------------------------------------]", }, { + style: BarStyle(), name: "t,c{100,1}", total: 100, current: 1, want: " [>-----------------------------------------------------------------------------------------------] ", }, { + style: BarStyle(), name: "t,c{100,1}trim", total: 100, current: 1, @@ -262,12 +630,14 @@ want: "[>-------------------------------------------------------------------------------------------------]", }, { + style: BarStyle(), name: "t,c{100,99}", total: 100, current: 99, want: " [==============================================================================================>-] ", }, { + style: BarStyle(), name: "t,c{100,99}trim", total: 100, current: 99, @@ -275,12 +645,14 @@ want: "[================================================================================================>-]", }, { + style: BarStyle(), name: "t,c{100,100}", total: 100, current: 100, want: " [================================================================================================] ", }, { + style: BarStyle(), name: "t,c{100,100}trim", total: 100, current: 100, @@ -288,6 +660,32 @@ want: "[==================================================================================================]", }, { + style: BarStyle(), + name: "t,c,r{100,100,99}", + total: 100, + current: 100, + refill: 99, + want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=] ", + }, + { + style: BarStyle(), + name: "t,c,r{100,100,99}trim", + total: 100, + current: 100, + refill: 99, + trim: true, + want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=]", + }, + { + style: BarStyle(), + name: "t,c,r{100,100,100}", + total: 100, + current: 100, + refill: 100, + want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle(), name: "t,c,r{100,100,100}trim", total: 100, current: 100, @@ -296,51 +694,41 @@ want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", }, { - name: "t,c{100,33}", - total: 100, - current: 33, - want: " [===============================>----------------------------------------------------------------] ", - }, - { - name: "t,c{100,33}trim", - total: 100, - current: 33, - trim: true, - want: "[===============================>------------------------------------------------------------------]", - }, - { - style: BarStyle().Tip("<").Reverse(), - name: "t,c{100,33}trim,rev", - total: 100, - current: 33, - trim: true, - want: "[------------------------------------------------------------------<===============================]", - }, - { - name: "t,c,r{100,33,33}", - total: 100, - current: 33, - refill: 33, - want: " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ", - }, - { - name: "t,c,r{100,33,33}trim", - total: 100, - current: 33, - refill: 33, - trim: true, - want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]", - }, - { - style: BarStyle().Tip("<").Reverse(), - name: "t,c,r{100,33,33}trim,rev", - total: 100, - current: 33, - refill: 33, - trim: true, - want: "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]", - }, - { + style: BarStyle().Reverse(), + name: "t,c,r{100,100,99}rev", + total: 100, + current: 100, + refill: 99, + want: " [=+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle().Reverse(), + name: "t,c,r{100,100,99}trim,rev", + total: 100, + current: 100, + refill: 99, + trim: true, + want: "[=+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", + }, + { + style: BarStyle().Reverse(), + name: "t,c,r{100,100,100}rev", + total: 100, + current: 100, + refill: 100, + want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle().Reverse(), + name: "t,c,r{100,100,100}trim", + total: 100, + current: 100, + refill: 100, + trim: true, + want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", + }, + { + style: BarStyle(), name: "t,c,r{100,40,33}", total: 100, current: 40, @@ -348,6 +736,7 @@ want: " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ", }, { + style: BarStyle(), name: "t,c,r{100,40,33}trim", total: 100, current: 40, @@ -371,17 +760,6 @@ refill: 33, trim: true, want: "[-----------------------------------------------------------<======++++++++++++++++++++++++++++++++]", - }, - }, - 197: { - { - name: "t,c,r{97486999,2805950,2805483}trim", - total: 97486999, - current: 2805950, - refill: 2805483, - barWidth: 60, - trim: true, - want: "[+>--------------------------------------------------------]", }, }, } @@ -389,17 +767,639 @@ var tmpBuf bytes.Buffer for tw, cases := range testSuite { for _, tc := range cases { - if tc.style == nil { - tc.style = BarStyle() - } - s := newTestState(NewBarFiller(tc.style)) + s := newTestState(tc.style.Build()) s.reqWidth = tc.barWidth s.total = tc.total s.current = tc.current s.trimSpace = tc.trim s.refill = tc.refill tmpBuf.Reset() - tmpBuf.ReadFrom(s.draw(newStatistics(tw, s))) + _, err := tmpBuf.ReadFrom(s.draw(newStatistics(tw, s))) + if err != nil { + t.FailNow() + } + by := tmpBuf.Bytes() + + got := string(by[:len(by)-1]) + if !utf8.ValidString(got) { + t.Fail() + } + if got != tc.want { + t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got)) + } + } + } +} + +func TestDrawTipOnComplete(t *testing.T) { + // key is termWidth + testSuite := map[int][]struct { + style BarStyleComposer + name string + total int64 + current int64 + refill int64 + barWidth int + trim bool + want string + }{ + 3: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[>]", + }, + }, + 4: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[=>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[=>]", + }, + }, + 5: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[==>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[==>]", + }, + }, + 6: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [=>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[===>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [=>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[===>]", + }, + }, + 7: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [==>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[====>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [==>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[====>]", + }, + }, + 8: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [===>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[=====>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [===>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[=====>]", + }, + }, + 80: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}", + total: 60, + current: 59, + want: " [==========================================================================>-] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,59}trim", + total: 60, + current: 59, + trim: true, + want: "[============================================================================>-]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,bw{60,59,60}", + total: 60, + current: 59, + barWidth: 60, + want: " [========================================================>-] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,bw{60,59,60}trim", + total: 60, + current: 59, + barWidth: 60, + trim: true, + want: "[========================================================>-]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}", + total: 60, + current: 60, + want: " [===========================================================================>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{60,60}trim", + total: 60, + current: 60, + trim: true, + want: "[=============================================================================>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,bw{60,60,60}", + total: 60, + current: 60, + barWidth: 60, + want: " [=========================================================>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,bw{60,60,60}trim", + total: 60, + current: 60, + barWidth: 60, + trim: true, + want: "[=========================================================>]", + }, + }, + 99: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{100,99}", + total: 100, + current: 99, + want: " [=============================================================================================>-] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{100,99}trim", + total: 100, + current: 99, + trim: true, + want: "[===============================================================================================>-]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{100,100}", + total: 100, + current: 100, + want: " [==============================================================================================>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{100,100}trim", + total: 100, + current: 100, + trim: true, + want: "[================================================================================================>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,r{100,100,99}", + total: 100, + current: 100, + refill: 99, + want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,r{100,100,99}trim", + total: 100, + current: 100, + refill: 99, + trim: true, + want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>]", + }, + { + style: BarStyle().TipOnComplete("<").Reverse(), + name: `t,c,r{100,100,99}TipOnComplete("<").Reverse()`, + total: 100, + current: 100, + refill: 99, + want: " [<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle().TipOnComplete("<").Reverse(), + name: `t,c,r{100,100,99}TipOnComplete("<").Reverse()trim`, + total: 100, + current: 100, + refill: 99, + trim: true, + want: "[<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,r{100,100,100}", + total: 100, + current: 100, + refill: 100, + want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,r{100,100,100}trim", + total: 100, + current: 100, + refill: 100, + trim: true, + want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>]", + }, + { + style: BarStyle().TipOnComplete("<").Reverse(), + name: `t,c,r{100,100,100}TipOnComplete("<").Reverse()`, + total: 100, + current: 100, + refill: 100, + want: " [<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", + }, + { + style: BarStyle().TipOnComplete("<").Reverse(), + name: `t,c,r{100,100,100}TipOnComplete("<").Reverse()trim`, + total: 100, + current: 100, + refill: 100, + trim: true, + want: "[<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", + }, + }, + 100: { + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{100,99}", + total: 100, + current: 99, + want: " [==============================================================================================>-] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{100,99}trim", + total: 100, + current: 99, + trim: true, + want: "[================================================================================================>-]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{100,100}", + total: 100, + current: 100, + want: " [===============================================================================================>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c{100,100}trim", + total: 100, + current: 100, + trim: true, + want: "[=================================================================================================>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,r{100,100,99}", + total: 100, + current: 100, + refill: 99, + want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,r{100,100,99}trim", + total: 100, + current: 100, + refill: 99, + trim: true, + want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>]", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,r{100,100,100}", + total: 100, + current: 100, + refill: 100, + want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>] ", + }, + { + style: BarStyle().TipOnComplete(">"), + name: "t,c,r{100,100,100}trim", + total: 100, + current: 100, + refill: 100, + trim: true, + want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>]", + }, + }, + } + + var tmpBuf bytes.Buffer + for tw, cases := range testSuite { + for _, tc := range cases { + s := newTestState(tc.style.Build()) + s.reqWidth = tc.barWidth + s.total = tc.total + s.current = tc.current + s.trimSpace = tc.trim + s.refill = tc.refill + tmpBuf.Reset() + _, err := tmpBuf.ReadFrom(s.draw(newStatistics(tw, s))) + if err != nil { + t.FailNow() + } + by := tmpBuf.Bytes() + + got := string(by[:len(by)-1]) + if !utf8.ValidString(got) { + t.Fail() + } + if got != tc.want { + t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got)) + } + } + } +} + +func TestDrawDoubleWidth(t *testing.T) { + // key is termWidth + testSuite := map[int][]struct { + style BarStyleComposer + name string + total int64 + current int64 + refill int64 + barWidth int + trim bool + want string + }{ + 99: { + { + style: BarStyle().Lbound("の").Rbound("の"), + name: `t,c{100,1}.Lbound("の").Rbound("の")`, + total: 100, + current: 1, + want: " の>--------------------------------------------------------------------------------------------の ", + }, + { + style: BarStyle().Lbound("の").Rbound("の"), + name: `t,c{100,1}.Lbound("の").Rbound("の")`, + total: 100, + current: 2, + want: " の=>-------------------------------------------------------------------------------------------の ", + }, + { + style: BarStyle().Tip("だ"), + name: `t,c{100,1}Tip("だ")`, + total: 100, + current: 1, + want: " [だ---------------------------------------------------------------------------------------------] ", + }, + { + style: BarStyle().Tip("だ"), + name: `t,c{100,2}Tip("だ")`, + total: 100, + current: 2, + want: " [だ---------------------------------------------------------------------------------------------] ", + }, + { + style: BarStyle().Tip("だ"), + name: `t,c{100,3}Tip("だ")`, + total: 100, + current: 3, + want: " [=だ--------------------------------------------------------------------------------------------] ", + }, + { + style: BarStyle().Tip("だ"), + name: `t,c{100,99}Tip("だ")`, + total: 100, + current: 99, + want: " [============================================================================================だ-] ", + }, + { + style: BarStyle().Tip("だ"), + name: `t,c{100,100}Tip("だ")`, + total: 100, + current: 100, + want: " [===============================================================================================] ", + }, + { + style: BarStyle().TipOnComplete("だ"), + name: `t,c{100,100}TipOnComplete("だ")`, + total: 100, + current: 100, + want: " [=============================================================================================だ] ", + }, + { + style: BarStyle().Filler("の").Tip("だ").Padding("つ"), + name: `t,c{100,1}Filler("の").Tip("だ").Padding("つ")`, + total: 100, + current: 1, + want: " [だつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", + }, + { + style: BarStyle().Filler("の").Tip("だ").Padding("つ"), + name: `t,c{100,2}Filler("の").Tip("だ").Padding("つ")`, + total: 100, + current: 2, + want: " [だつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", + }, + { + style: BarStyle().Filler("の").Tip("だ").Padding("つ"), + name: `t,c{100,99}Filler("の").Tip("だ").Padding("つ")`, + total: 100, + current: 99, + want: " [ののののののののののののののののののののののののののののののののののののののののののののののだ…] ", + }, + { + style: BarStyle().Filler("の").Tip("だ").Padding("つ"), + name: `t,c{100,100}.Filler("の").Tip("だ").Padding("つ")`, + total: 100, + current: 100, + want: " […ののののののののののののののののののののののののののののののののののののののののののののののの] ", + }, + { + style: BarStyle().Filler("の").Tip("だ").Padding("つ").Reverse(), + name: `t,c{100,100}Filler("の").Tip("だ").Padding("つ").Reverse()`, + total: 100, + current: 100, + want: " [ののののののののののののののののののののののののののののののののののののののののののののののの…] ", + }, + { + style: BarStyle().Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ"), + name: `t,c{100,99}Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ")`, + total: 100, + current: 99, + want: " [ののののののののののののののののののののののののののののののののののののののののののののののだ…] ", + }, + { + style: BarStyle().Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ"), + name: `t,c{100,100}.Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ")`, + total: 100, + current: 100, + want: " […ののののののののののののののののののののののののののののののののののののののののののののののだ] ", + }, + { + style: BarStyle().Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ").Reverse(), + name: `t,c{100,100}.Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ").Reverse()`, + total: 100, + current: 100, + want: " [だのののののののののののののののののののののののののののののののののののののののののののののの…] ", + }, + { + style: BarStyle().Refiller("の"), + name: `t,c,r{100,100,99}Refiller("の")`, + total: 100, + current: 100, + refill: 99, + want: " [ののののののののののののののののののののののののののののののののののののののののののののののの=] ", + }, + { + style: BarStyle().Refiller("の"), + name: `t,c,r{100,100,99}Refiller("の")trim`, + total: 100, + current: 100, + refill: 99, + trim: true, + want: "[のののののののののののののののののののののののののののののののののののののののののののののののの=]", + }, + }, + } + + var tmpBuf bytes.Buffer + for tw, cases := range testSuite { + for _, tc := range cases { + s := newTestState(tc.style.Build()) + s.reqWidth = tc.barWidth + s.total = tc.total + s.current = tc.current + s.trimSpace = tc.trim + s.refill = tc.refill + tmpBuf.Reset() + _, err := tmpBuf.ReadFrom(s.draw(newStatistics(tw, s))) + if err != nil { + t.FailNow() + } by := tmpBuf.Bytes() got := string(by[:len(by)-1]) @@ -414,11 +1414,11 @@ } func newTestState(filler BarFiller) *bState { - s := &bState{ + bs := &bState{ filler: filler, - bufP: new(bytes.Buffer), - bufB: new(bytes.Buffer), - bufA: new(bytes.Buffer), } - return s + for i := 0; i < len(bs.buffers); i++ { + bs.buffers[i] = bytes.NewBuffer(make([]byte, 0, 512)) + } + return bs } diff --git a/example_test.go b/example_test.go index cde49f8..72596c4 100644 --- a/example_test.go +++ b/example_test.go @@ -17,10 +17,10 @@ total := 100 name := "Single Bar:" - // adding a single bar, which will inherit container's width - bar := p.Add(int64(total), - // progress bar filler with customized style - mpb.NewBarFiller(mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟")), + // create a single bar, which will inherit container's width + bar := p.New(int64(total), + // BarFillerBuilder with custom style + mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), mpb.PrependDecorators( // display our name with one space on the right decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), @@ -78,7 +78,7 @@ defer proxyReader.Close() // and copy from reader, ignoring errors - io.Copy(ioutil.Discard, proxyReader) + _, _ = io.Copy(ioutil.Discard, proxyReader) p.Wait() } diff --git a/go.mod b/go.mod index 22a2c65..8fa790d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ github.com/VividCortex/ewma v1.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/mattn/go-runewidth v0.0.13 - golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 ) go 1.14 diff --git a/go.sum b/go.sum index 59051bd..aebe4d9 100644 --- a/go.sum +++ b/go.sum @@ -6,5 +6,5 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/percentage.go b/internal/percentage.go index a8ef8be..4bc36f5 100644 --- a/internal/percentage.go +++ b/internal/percentage.go @@ -3,7 +3,7 @@ import "math" // Percentage is a helper function, to calculate percentage. -func Percentage(total, current int64, width int) float64 { +func Percentage(total, current int64, width uint) float64 { if total <= 0 { return 0 } @@ -14,6 +14,6 @@ } // PercentageRound same as Percentage but with math.Round. -func PercentageRound(total, current int64, width int) float64 { +func PercentageRound(total, current int64, width uint) float64 { return math.Round(Percentage(total, current, width)) } diff --git a/internal/percentage_test.go b/internal/percentage_test.go index 6d5410d..515ad60 100644 --- a/internal/percentage_test.go +++ b/internal/percentage_test.go @@ -4,7 +4,7 @@ func TestPercentage(t *testing.T) { // key is barWidth - testSuite := map[int][]struct { + testSuite := map[uint][]struct { name string total int64 current int64 diff --git a/internal/predicate.go b/internal/predicate.go deleted file mode 100644 index 1e4dd24..0000000 --- a/internal/predicate.go +++ /dev/null @@ -1,6 +0,0 @@ -package internal - -// Predicate helper for internal use. -func Predicate(pick bool) func() bool { - return func() bool { return pick } -} diff --git a/progress.go b/progress.go index b2017f3..123af17 100644 --- a/progress.go +++ b/progress.go @@ -6,8 +6,6 @@ "context" "fmt" "io" - "io/ioutil" - "log" "math" "os" "sync" @@ -19,7 +17,7 @@ const ( // default RefreshRate - prr = 120 * time.Millisecond + prr = 150 * time.Millisecond ) // Progress represents a container that renders one or more progress @@ -33,7 +31,6 @@ done chan struct{} refreshCh chan time.Time once sync.Once - dlogger *log.Logger } // pState holds bars in its priorityQueue. It gets passed to @@ -75,7 +72,6 @@ rr: prr, parkedBars: make(map[*Bar]*Bar), output: os.Stdout, - debugOut: ioutil.Discard, } for _, opt := range options { @@ -91,7 +87,6 @@ bwg: new(sync.WaitGroup), operateState: make(chan func(*pState)), done: make(chan struct{}), - dlogger: log.New(s.debugOut, "[mpb] ", log.Lshortfile), } p.cwg.Add(1) @@ -99,17 +94,19 @@ return p } -// AddBar creates a bar with default bar filler. Different filler can -// be chosen and applied via `*Progress.Add(...) *Bar` method. +// AddBar creates a bar with default bar filler. func (p *Progress) AddBar(total int64, options ...BarOption) *Bar { - return p.Add(total, NewBarFiller(BarStyle()), options...) -} - -// AddSpinner creates a bar with default spinner filler. Different -// filler can be chosen and applied via `*Progress.Add(...) *Bar` -// method. + return p.New(total, BarStyle(), options...) +} + +// AddSpinner creates a bar with default spinner filler. func (p *Progress) AddSpinner(total int64, options ...BarOption) *Bar { - return p.Add(total, NewBarFiller(SpinnerStyle()), options...) + return p.New(total, SpinnerStyle(), options...) +} + +// New creates a bar with provided BarFillerBuilder. +func (p *Progress) New(total int64, builder BarFillerBuilder, options ...BarOption) *Bar { + return p.Add(total, builder.Build(), options...) } // Add creates a bar which renders itself by provided filler. @@ -117,7 +114,7 @@ // Panics if *Progress instance is done, i.e. called after *Progress.Wait(). func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) *Bar { if filler == nil { - filler = BarFillerFunc(func(io.Writer, int, decor.Statistics) {}) + filler = NopStyle().Build() } p.bwg.Add(1) result := make(chan *Bar) @@ -157,7 +154,25 @@ } } -func (p *Progress) setBarPriority(b *Bar, priority int) { +func (p *Progress) traverseBars(cb func(b *Bar) bool) { + done := make(chan struct{}) + select { + case p.operateState <- func(s *pState) { + for i := 0; i < s.bHeap.Len(); i++ { + bar := s.bHeap[i] + if !cb(bar) { + break + } + } + close(done) + }: + <-done + case <-p.done: + } +} + +// UpdateBarPriority same as *Bar.SetPriority(int). +func (p *Progress) UpdateBarPriority(b *Bar, priority int) { select { case p.operateState <- func(s *pState) { if b.index < 0 { @@ -170,14 +185,9 @@ } } -// UpdateBarPriority same as *Bar.SetPriority(int). -func (p *Progress) UpdateBarPriority(b *Bar, priority int) { - p.setBarPriority(b, priority) -} - // BarCount returns bars count. func (p *Progress) BarCount() int { - result := make(chan int, 1) + result := make(chan int) select { case p.operateState <- func(s *pState) { result <- s.bHeap.Len() }: return <-result @@ -219,12 +229,26 @@ op(s) case <-p.refreshCh: if err := s.render(cw); err != nil { - p.dlogger.Println(err) + if s.debugOut != nil { + _, e := fmt.Fprintln(s.debugOut, err) + if e != nil { + panic(err) + } + } else { + panic(err) + } } case <-s.shutdownNotifier: - if s.heapUpdated { + for s.heapUpdated { if err := s.render(cw); err != nil { - p.dlogger.Println(err) + if s.debugOut != nil { + _, e := fmt.Fprintln(s.debugOut, err) + if e != nil { + panic(err) + } + } else { + panic(err) + } } } return @@ -291,11 +315,15 @@ } func (s *pState) flush(cw *cwriter.Writer) error { - var lineCount int - bm := make(map[*Bar]struct{}, s.bHeap.Len()) + var totalLines int + bm := make(map[*Bar]int, s.bHeap.Len()) for s.bHeap.Len() > 0 { b := heap.Pop(&s.bHeap).(*Bar) - cw.ReadFrom(<-b.frameCh) + frame := <-b.frameCh + _, err := cw.ReadFrom(frame.reader) + if err != nil { + return err + } if b.toShutdown { if b.recoveredPanic != nil { s.barShutdownQueue = append(s.barShutdownQueue, b) @@ -308,8 +336,8 @@ }() } } - lineCount += b.extendedLines + 1 - bm[b] = struct{}{} + bm[b] = frame.lines + totalLines += frame.lines } for _, b := range s.barShutdownQueue { @@ -320,7 +348,7 @@ b.toDrop = true } if s.popCompleted && !b.noPop { - lineCount -= b.extendedLines + 1 + totalLines -= bm[b] b.toDrop = true } if b.toDrop { @@ -335,7 +363,7 @@ heap.Push(&s.bHeap, b) } - return cw.Flush(lineCount) + return cw.Flush(totalLines) } func (s *pState) updateSyncMatrix() { @@ -386,9 +414,9 @@ bs.priority = -(math.MaxInt32 - s.idCount) } - bs.bufP = bytes.NewBuffer(make([]byte, 0, 128)) - bs.bufB = bytes.NewBuffer(make([]byte, 0, 256)) - bs.bufA = bytes.NewBuffer(make([]byte, 0, 128)) + for i := 0; i < len(bs.buffers); i++ { + bs.buffers[i] = bytes.NewBuffer(make([]byte, 0, 512)) + } return bs } diff --git a/progress_test.go b/progress_test.go index 86bfdaf..64e0fa6 100644 --- a/progress_test.go +++ b/progress_test.go @@ -69,7 +69,7 @@ wg.Wait() count := p.BarCount() if count != 2 { - t.Errorf("BarCount want: %q, got: %q\n", 2, count) + t.Errorf("BarCount want: %d, got: %d\n", 2, count) } bars[1].Abort(true) bars[2].Abort(true) @@ -77,36 +77,38 @@ } func TestWithContext(t *testing.T) { + shutdown := make(chan struct{}) ctx, cancel := context.WithCancel(context.Background()) - shutdown := make(chan struct{}) - p := mpb.NewWithContext(ctx, - mpb.WithOutput(ioutil.Discard), - mpb.WithRefreshRate(50*time.Millisecond), - mpb.WithShutdownNotifier(shutdown), - ) + p := mpb.NewWithContext(ctx, mpb.WithShutdownNotifier(shutdown), mpb.WithOutput(ioutil.Discard)) - total := 10000 - numBars := 3 - bars := make([]*mpb.Bar, 0, numBars) - for i := 0; i < numBars; i++ { - bar := p.AddBar(int64(total)) - bars = append(bars, bar) - go func() { - for !bar.Completed() { - bar.Increment() - time.Sleep(randomDuration(100 * time.Millisecond)) - } - }() - } + start := make(chan struct{}) + done := make(chan struct{}) + fail := make(chan struct{}) + bar := p.AddBar(0) // never complete bar + go func() { + close(start) + for !bar.Completed() { + bar.Increment() + time.Sleep(randomDuration(100 * time.Millisecond)) + } + close(done) + }() - time.Sleep(50 * time.Millisecond) + go func() { + select { + case <-done: + p.Wait() + case <-time.After(150 * time.Millisecond): + close(fail) + } + }() + + <-start cancel() - - p.Wait() select { case <-shutdown: - case <-time.After(100 * time.Millisecond): - t.Error("Progress didn't stop") + case <-fail: + t.Error("Progress didn't shutdown") } } @@ -127,7 +129,7 @@ *mpb.MaxWidthDistributor = makeWrapper(*mpb.MaxWidthDistributor, start, end) total := 80 - numBars := 3 + numBars := 6 p := mpb.New(mpb.WithOutput(ioutil.Discard)) for i := 0; i < numBars; i++ { bar := p.AddBar(int64(total), @@ -141,11 +143,15 @@ rng := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < total; i++ { start := time.Now() - if bar.ID() >= numBars-1 && i >= 42 { - bar.Abort(true) + if id := bar.ID(); id > 1 && i >= 42 { + if id&1 == 1 { + bar.Abort(true) + } else { + bar.Abort(false) + } } - time.Sleep((time.Duration(rng.Intn(10)+1) * (10 * time.Millisecond)) / 2) - bar.Increment() + time.Sleep((time.Duration(rng.Intn(10)+1) * (50 * time.Millisecond)) / 2) + bar.IncrInt64(rand.Int63n(5) + 1) bar.DecoratorEwmaUpdate(time.Since(start)) } }() diff --git a/proxyreader.go b/proxyreader.go index 316f438..25f195b 100644 --- a/proxyreader.go +++ b/proxyreader.go @@ -11,73 +11,69 @@ bar *Bar } -func (x *proxyReader) Read(p []byte) (int, error) { +func (x proxyReader) Read(p []byte) (int, error) { n, err := x.ReadCloser.Read(p) x.bar.IncrBy(n) if err == io.EOF { - go x.bar.SetTotal(0, true) + go x.bar.SetTotal(-1, true) } return n, err } type proxyWriterTo struct { - io.ReadCloser // *proxyReader - wt io.WriterTo - bar *Bar + proxyReader + wt io.WriterTo } -func (x *proxyWriterTo) WriteTo(w io.Writer) (int64, error) { +func (x proxyWriterTo) WriteTo(w io.Writer) (int64, error) { n, err := x.wt.WriteTo(w) x.bar.IncrInt64(n) if err == io.EOF { - go x.bar.SetTotal(0, true) + go x.bar.SetTotal(-1, true) } return n, err } type ewmaProxyReader struct { - io.ReadCloser // *proxyReader - bar *Bar - iT time.Time + proxyReader } -func (x *ewmaProxyReader) Read(p []byte) (int, error) { - n, err := x.ReadCloser.Read(p) +func (x ewmaProxyReader) Read(p []byte) (int, error) { + start := time.Now() + n, err := x.proxyReader.Read(p) if n > 0 { - x.bar.DecoratorEwmaUpdate(time.Since(x.iT)) - x.iT = time.Now() + x.bar.DecoratorEwmaUpdate(time.Since(start)) } return n, err } type ewmaProxyWriterTo struct { - io.ReadCloser // *ewmaProxyReader - wt io.WriterTo // *proxyWriterTo - bar *Bar - iT time.Time + ewmaProxyReader + wt proxyWriterTo } -func (x *ewmaProxyWriterTo) WriteTo(w io.Writer) (int64, error) { +func (x ewmaProxyWriterTo) WriteTo(w io.Writer) (int64, error) { + start := time.Now() n, err := x.wt.WriteTo(w) if n > 0 { - x.bar.DecoratorEwmaUpdate(time.Since(x.iT)) - x.iT = time.Now() + x.bar.DecoratorEwmaUpdate(time.Since(start)) } return n, err } -func newProxyReader(r io.Reader, bar *Bar) io.ReadCloser { - rc := toReadCloser(r) - rc = &proxyReader{rc, bar} - - if wt, isWriterTo := r.(io.WriterTo); bar.hasEwmaDecorators { - now := time.Now() - rc = &ewmaProxyReader{rc, bar, now} - if isWriterTo { - rc = &ewmaProxyWriterTo{rc, wt, bar, now} +func (b *Bar) newProxyReader(r io.Reader) (rc io.ReadCloser) { + pr := proxyReader{toReadCloser(r), b} + if wt, ok := r.(io.WriterTo); ok { + pw := proxyWriterTo{pr, wt} + if b.hasEwmaDecorators { + rc = ewmaProxyWriterTo{ewmaProxyReader{pr}, pw} + } else { + rc = pw } - } else if isWriterTo { - rc = &proxyWriterTo{rc, wt, bar} + } else if b.hasEwmaDecorators { + rc = ewmaProxyReader{pr} + } else { + rc = pr } return rc } diff --git a/proxyreader_test.go b/proxyreader_test.go index 923226e..789695e 100644 --- a/proxyreader_test.go +++ b/proxyreader_test.go @@ -67,8 +67,7 @@ p := mpb.New(mpb.WithOutput(ioutil.Discard)) var reader io.Reader = strings.NewReader(content) - wt := reader.(io.WriterTo) - tReader := &testWriterTo{reader, wt, false} + tReader := &testWriterTo{reader, reader.(io.WriterTo), false} bar := p.AddBar(int64(len(content)), mpb.BarFillerTrim())