diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index c42aa0e..d8c89e0 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -22,17 +22,15 @@ os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} cache: false - - uses: golangci/golangci-lint-action@v3 + - uses: golangci/golangci-lint-action@v6 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. version: latest - # Optional: working directory, useful for monorepos - # working-directory: somedir # Optional: golangci-lint command line arguments. # args: --issues-exit-code=0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ca86ad9..8dfb293 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ - v* branches: - master - - v* + - main pull_request: permissions: @@ -21,8 +21,8 @@ os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - run: go test -race ./... diff --git a/README.md b/README.md index 09825ca..af97c92 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,9 @@ 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}), + decor.Name(name, decor.WC{C: decor.DindentRight | decor.DextraSpace}), // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done", - ), + decor.OnComplete(decor.AverageETA(decor.ET_STYLE_GO), "done"), ), mpb.AppendDecorators(decor.Percentage()), ) diff --git a/_examples/barExtender/go.mod b/_examples/barExtender/go.mod index dcaa9aa..f082eaf 100644 --- a/_examples/barExtender/go.mod +++ b/_examples/barExtender/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/barExtenderRev/go.mod b/_examples/barExtenderRev/go.mod index f8c9ae7..00ed99f 100644 --- a/_examples/barExtenderRev/go.mod +++ b/_examples/barExtenderRev/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/cancel/go.mod b/_examples/cancel/go.mod index 4814890..32ad805 100644 --- a/_examples/cancel/go.mod +++ b/_examples/cancel/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/complex/go.mod b/_examples/complex/go.mod index 03d237e..67934de 100644 --- a/_examples/complex/go.mod +++ b/_examples/complex/go.mod @@ -3,16 +3,16 @@ go 1.17 require ( - github.com/fatih/color v1.15.0 - github.com/vbauerster/mpb/v8 v8.6.1 + github.com/fatih/color v1.17.0 + github.com/vbauerster/mpb/v8 v8.8.2 ) require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/complex/main.go b/_examples/complex/main.go index e64f18d..7647dbd 100644 --- a/_examples/complex/main.go +++ b/_examples/complex/main.go @@ -25,7 +25,7 @@ queue := make([]*mpb.Bar, 2) queue[0] = p.AddBar(rand.Int63n(201)+100, mpb.PrependDecorators( - decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), + decor.Name(task, decor.WC{C: decor.DindentRight | decor.DextraSpace}), decor.Name("downloading", decor.WCSyncSpaceR), decor.CountersNoUnit("%d / %d", decor.WCSyncWidth), ), @@ -37,7 +37,7 @@ mpb.BarQueueAfter(queue[0]), // this bar is queued mpb.BarFillerClearOnComplete(), mpb.PrependDecorators( - decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), + decor.Name(task, decor.WC{C: decor.DindentRight | decor.DextraSpace}), decor.OnCompleteMeta( decor.OnComplete( decor.Meta(decor.Name("installing", decor.WCSyncSpaceR), toMetaFunc(red)), diff --git a/_examples/decoratorsOnTop/go.mod b/_examples/decoratorsOnTop/go.mod index ae8e32d..ec44635 100644 --- a/_examples/decoratorsOnTop/go.mod +++ b/_examples/decoratorsOnTop/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/differentWidth/go.mod b/_examples/differentWidth/go.mod index 6ac87a9..c038d63 100644 --- a/_examples/differentWidth/go.mod +++ b/_examples/differentWidth/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/dynTotal/go.mod b/_examples/dynTotal/go.mod index 33622b1..eba1c53 100644 --- a/_examples/dynTotal/go.mod +++ b/_examples/dynTotal/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/gomodtidyall b/_examples/gomodtidyall new file mode 100755 index 0000000..5b633bc --- /dev/null +++ b/_examples/gomodtidyall @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +for d in *; do + [ ! -d "$d" ] && continue + pushd "$d" >/dev/null 2>&1 + go mod tidy + popd >/dev/null 2>&1 +done diff --git a/_examples/gomodtidyall.sh b/_examples/gomodtidyall.sh deleted file mode 100755 index a9e6100..0000000 --- a/_examples/gomodtidyall.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -for d in *; do - [ ! -d "$d" ] && continue - pushd "$d" >/dev/null 2>&1 - go mod tidy - popd >/dev/null 2>&1 -done diff --git a/_examples/io/go.mod b/_examples/io/go.mod index 8b0e0d4..23b5e0c 100644 --- a/_examples/io/go.mod +++ b/_examples/io/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/io/main.go b/_examples/io/main.go index 252a967..8d765a7 100644 --- a/_examples/io/main.go +++ b/_examples/io/main.go @@ -3,7 +3,6 @@ import ( "crypto/rand" "io" - "io/ioutil" "time" "github.com/vbauerster/mpb/v8" @@ -11,8 +10,17 @@ ) func main() { - var total int64 = 1024 * 1024 * 500 - reader := io.LimitReader(rand.Reader, total) + var total int64 = 64 * 1024 * 1024 + + r, w := io.Pipe() + + go func() { + for i := 0; i < 1024; i++ { + _, _ = io.Copy(w, io.LimitReader(rand.Reader, 64*1024)) + time.Sleep(time.Second / 10) + } + w.Close() + }() p := mpb.New( mpb.WithWidth(60), @@ -32,11 +40,11 @@ ) // create proxy reader - proxyReader := bar.ProxyReader(reader) + proxyReader := bar.ProxyReader(r) defer proxyReader.Close() // copy from proxyReader, ignoring errors - _, _ = io.Copy(ioutil.Discard, proxyReader) + _, _ = io.Copy(io.Discard, proxyReader) p.Wait() } diff --git a/_examples/mexicanBar/go.mod b/_examples/mexicanBar/go.mod index 681c2c0..bbff3d0 100644 --- a/_examples/mexicanBar/go.mod +++ b/_examples/mexicanBar/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/multiBars/go.mod b/_examples/multiBars/go.mod index 00a0b10..133d133 100644 --- a/_examples/multiBars/go.mod +++ b/_examples/multiBars/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/poplog/go.mod b/_examples/poplog/go.mod index dbf2523..9ff0f24 100644 --- a/_examples/poplog/go.mod +++ b/_examples/poplog/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/poplog/main.go b/_examples/poplog/main.go index ff875c9..f4efd6a 100644 --- a/_examples/poplog/main.go +++ b/_examples/poplog/main.go @@ -24,7 +24,7 @@ ), mpb.AppendDecorators( decor.OnComplete(decor.Name(" "), ""), - decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 60), ""), + decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 30), ""), ), ) // simulating some work diff --git a/_examples/progressAsWriter/go.mod b/_examples/progressAsWriter/go.mod index a7b985f..371d88b 100644 --- a/_examples/progressAsWriter/go.mod +++ b/_examples/progressAsWriter/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/progressAsWriter/main.go b/_examples/progressAsWriter/main.go index 61e819b..057ad03 100644 --- a/_examples/progressAsWriter/main.go +++ b/_examples/progressAsWriter/main.go @@ -13,16 +13,31 @@ func main() { total, numBars := 100, 2 - var wg sync.WaitGroup - wg.Add(numBars) + var bwg, qwg sync.WaitGroup + bwg.Add(numBars) + qwg.Add(1) done := make(chan interface{}) - p := mpb.New( - mpb.WithWidth(64), - mpb.WithWaitGroup(&wg), - mpb.WithShutdownNotifier(done), - ) + p := mpb.New(mpb.WithWidth(64), mpb.WithShutdownNotifier(done), mpb.WithWaitGroup(&qwg)) log.SetOutput(p) + + go func() { + defer qwg.Done() + for { + select { + case <-done: + // after done, underlying io.Writer returns mpb.DoneError + // so following isn't printed + log.Println("all done") + return + default: + log.Println("waiting for done") + time.Sleep(150 * time.Millisecond) + } + } + }() + + nopBar := p.MustAdd(0, nil) for i := 0; i < numBars; i++ { name := fmt.Sprintf("Bar#%d:", i) @@ -39,7 +54,7 @@ ) // simulating some work go func() { - defer wg.Done() + defer bwg.Done() rng := rand.New(rand.NewSource(time.Now().UnixNano())) max := 100 * time.Millisecond for i := 0; i < total; i++ { @@ -54,25 +69,9 @@ }() } - var qwg sync.WaitGroup - qwg.Add(1) - go func() { - quit: - for { - select { - case <-done: - // after done, underlying io.Writer returns mpb.DoneError - // so following isn't printed - log.Println("all done") - break quit - default: - log.Println("waiting for done") - time.Sleep(100 * time.Millisecond) - } - } - qwg.Done() - }() + bwg.Wait() + log.Println("completing nop bar") + nopBar.EnableTriggerComplete() p.Wait() - qwg.Wait() } diff --git a/_examples/quietMode/go.mod b/_examples/quietMode/go.mod index 6a42347..857e680 100644 --- a/_examples/quietMode/go.mod +++ b/_examples/quietMode/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/remove/go.mod b/_examples/remove/go.mod index 988c866..dfc61da 100644 --- a/_examples/remove/go.mod +++ b/_examples/remove/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/reverseBar/go.mod b/_examples/reverseBar/go.mod index b062f40..2716b73 100644 --- a/_examples/reverseBar/go.mod +++ b/_examples/reverseBar/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/singleBar/go.mod b/_examples/singleBar/go.mod index e15d831..1d761e3 100644 --- a/_examples/singleBar/go.mod +++ b/_examples/singleBar/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/singleBar/main.go b/_examples/singleBar/main.go index a7c2a1b..12d65ca 100644 --- a/_examples/singleBar/main.go +++ b/_examples/singleBar/main.go @@ -20,11 +20,9 @@ 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}), + decor.Name(name, decor.WC{C: decor.DindentRight | decor.DextraSpace}), // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done", - ), + decor.OnComplete(decor.AverageETA(decor.ET_STYLE_GO), "done"), ), mpb.AppendDecorators(decor.Percentage()), ) diff --git a/_examples/spinTipBar/go.mod b/_examples/spinTipBar/go.mod index 334d1fc..5c6327e 100644 --- a/_examples/spinTipBar/go.mod +++ b/_examples/spinTipBar/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/spinnerBar/go.mod b/_examples/spinnerBar/go.mod index b5722de..8dc9d53 100644 --- a/_examples/spinnerBar/go.mod +++ b/_examples/spinnerBar/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/spinnerDecorator/go.mod b/_examples/spinnerDecorator/go.mod index d239911..b12d3c1 100644 --- a/_examples/spinnerDecorator/go.mod +++ b/_examples/spinnerDecorator/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/stress/go.mod b/_examples/stress/go.mod index 0541360..db3075a 100644 --- a/_examples/stress/go.mod +++ b/_examples/stress/go.mod @@ -4,15 +4,15 @@ require ( github.com/pkg/profile v1.7.0 - github.com/vbauerster/mpb/v8 v8.6.1 + github.com/vbauerster/mpb/v8 v8.8.2 ) require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/felixge/fgprof v0.9.3 // indirect - github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/felixge/fgprof v0.9.4 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/stress/main.go b/_examples/stress/main.go index 04cf476..1537321 100644 --- a/_examples/stress/main.go +++ b/_examples/stress/main.go @@ -14,7 +14,7 @@ ) const ( - totalBars = 32 + totalBars = 42 ) var proftype = flag.String("prof", "", "profile type (cpu, mem)") @@ -38,12 +38,11 @@ bar := p.AddBar(int64(total), mpb.PrependDecorators( decor.Name(name, decor.WCSyncWidthR), - decor.Elapsed(decor.ET_STYLE_GO, decor.WCSyncWidth), + decor.OnComplete(decor.Percentage(decor.WCSyncWidth), "done"), ), mpb.AppendDecorators( - decor.OnComplete( - decor.Percentage(decor.WC{W: 5}), "done", - ), + decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 30, decor.WCSyncWidth), ""), + decor.EwmaSpeed(decor.SizeB1024(0), "", 30, decor.WCSyncSpace), ), ) @@ -51,9 +50,10 @@ defer wg.Done() rng := rand.New(rand.NewSource(time.Now().UnixNano())) max := 100 * time.Millisecond - for !bar.Completed() { + for bar.IsRunning() { + start := time.Now() time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) - bar.Increment() + bar.EwmaIncrement(time.Since(start)) } }() } diff --git a/_examples/suppressBar/go.mod b/_examples/suppressBar/go.mod index c7fd7ee..ab12bc5 100644 --- a/_examples/suppressBar/go.mod +++ b/_examples/suppressBar/go.mod @@ -2,14 +2,12 @@ go 1.17 -require ( - github.com/mattn/go-runewidth v0.0.15 - github.com/vbauerster/mpb/v8 v8.6.1 -) +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/_examples/suppressBar/main.go b/_examples/suppressBar/main.go index 6f68a37..6d3ba6b 100644 --- a/_examples/suppressBar/main.go +++ b/_examples/suppressBar/main.go @@ -3,12 +3,11 @@ import ( "errors" "fmt" - "io" + "math" "math/rand" "sync" "time" - "github.com/mattn/go-runewidth" "github.com/vbauerster/mpb/v8" "github.com/vbauerster/mpb/v8/decor" ) @@ -16,71 +15,44 @@ func main() { p := mpb.New() - total := 100 - msgCh := make(chan string) - resumeCh := make(chan struct{}) - nextCh := make(chan struct{}, 1) - ew := &errorWrapper{} + total, numBars := 100, 3 + err := new(errorWrapper) timer := time.AfterFunc(2*time.Second, func() { - ew.reset(errors.New("timeout")) + err.set(errors.New("timeout"), rand.Intn(numBars)) }) defer timer.Stop() - bar := p.AddBar(int64(total), - mpb.BarFillerMiddleware(func(base mpb.BarFiller) mpb.BarFiller { - var msg *string - var times int - return mpb.BarFillerFunc(func(w io.Writer, st decor.Statistics) error { - if msg == nil { - select { - case m := <-msgCh: - msg = &m - times = 10 - nextCh <- struct{}{} - default: - } - return base.Fill(w, st) + + for i := 0; i < numBars; i++ { + msgCh := make(chan string, 1) + bar := p.AddBar(int64(total), + mpb.PrependDecorators(newTitleDecorator(fmt.Sprintf("Bar#%d:", i), msgCh, 16)), + mpb.AppendDecorators(decor.Percentage(decor.WCSyncWidth)), + ) + // simulating some work + barID := i + go func() { + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + if err.check(barID) { + msgCh <- fmt.Sprintf("%s at %d, retrying...", err.Error(), i) + err.reset() + i-- + bar.SetRefill(int64(i)) + continue } - switch { - case times == 0, st.Completed, st.Aborted: - defer func() { - msg = nil - }() - resumeCh <- struct{}{} - default: - times-- - } - _, err := io.WriteString(w, runewidth.Truncate(*msg, st.AvailableWidth, "…")) - nextCh <- struct{}{} - return err - }) - }), - mpb.PrependDecorators(decor.Name("my bar:")), - mpb.AppendDecorators(newCustomPercentage(nextCh)), - ) - // simulating some work - go func() { - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - max := 100 * time.Millisecond - for i := 0; i < total; i++ { - time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) - if ew.isErr() { - msgCh <- fmt.Sprintf("%s at %d, retrying...", ew.Error(), i) - i-- - bar.SetRefill(int64(i)) - ew.reset(nil) - <-resumeCh - continue + time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) + bar.Increment() } - bar.Increment() - } - }() + }() + } p.Wait() } type errorWrapper struct { sync.RWMutex - err error + err error + barID int } func (ew *errorWrapper) Error() string { @@ -89,35 +61,54 @@ return ew.err.Error() } -func (ew *errorWrapper) isErr() bool { +func (ew *errorWrapper) check(barID int) bool { ew.RLock() defer ew.RUnlock() - return ew.err != nil + return ew.err != nil && ew.barID == barID } -func (ew *errorWrapper) reset(err error) { +func (ew *errorWrapper) set(err error, barID int) { ew.Lock() ew.err = err + ew.barID = barID ew.Unlock() } -type percentage struct { - decor.Decorator - suspend <-chan struct{} +func (ew *errorWrapper) reset() { + ew.Lock() + ew.err = nil + ew.Unlock() } -func (d percentage) Decor(s decor.Statistics) (string, int) { - select { - case <-d.suspend: - return d.Format("") - default: - return d.Decorator.Decor(s) +type title struct { + decor.Decorator + name string + msgCh <-chan string + msg string + count int + limit int +} + +func (d *title) Decor(stat decor.Statistics) (string, int) { + if d.count == 0 { + select { + case msg := <-d.msgCh: + d.count = d.limit + d.msg = msg + default: + return d.Decorator.Decor(stat) + } + } + d.count-- + _, _ = d.Format("") + return fmt.Sprintf("%s %s", d.name, d.msg), math.MaxInt +} + +func newTitleDecorator(name string, msgCh <-chan string, limit int) decor.Decorator { + return &title{ + Decorator: decor.Name(name), + name: name, + msgCh: msgCh, + limit: limit, } } - -func newCustomPercentage(nextCh <-chan struct{}) decor.Decorator { - return percentage{ - Decorator: decor.Percentage(), - suspend: nextCh, - } -} diff --git a/_examples/tipOnComplete/go.mod b/_examples/tipOnComplete/go.mod index c164b7e..691adad 100644 --- a/_examples/tipOnComplete/go.mod +++ b/_examples/tipOnComplete/go.mod @@ -2,12 +2,12 @@ go 1.17 -require github.com/vbauerster/mpb/v8 v8.6.1 +require github.com/vbauerster/mpb/v8 v8.8.2 require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - golang.org/x/sys v0.11.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/bar.go b/bar.go index 7d83268..73753f0 100644 --- a/bar.go +++ b/bar.go @@ -19,41 +19,38 @@ priority int // used by heap frameCh chan *renderFrame operateState chan func(*bState) - done chan struct{} container *Progress bs *bState + bsOk chan struct{} + ctx context.Context cancel func() } type syncTable [2][]chan int -type extenderFunc func([]io.Reader, decor.Statistics) ([]io.Reader, error) +type extenderFunc func(decor.Statistics, ...io.Reader) ([]io.Reader, error) // bState is actual bar's state. type bState struct { - id int - priority int - reqWidth int - shutdown int - total int64 - current int64 - refill int64 - trimSpace bool - completed bool - aborted bool - triggerComplete bool - rmOnComplete bool - noPop bool - autoRefresh bool - aDecorators []decor.Decorator - pDecorators []decor.Decorator - averageDecorators []decor.AverageDecorator - ewmaDecorators []decor.EwmaDecorator - shutdownListeners []decor.ShutdownListener - buffers [3]*bytes.Buffer - filler BarFiller - extender extenderFunc - renderReq chan<- time.Time - waitBar *Bar // key for (*pState).queueBars + id int + priority int + reqWidth int + shutdown int + total int64 + current int64 + refill int64 + trimSpace bool + aborted bool + triggerComplete bool + rmOnComplete bool + noPop bool + autoRefresh bool + buffers [3]*bytes.Buffer + decorators [2][]decor.Decorator + ewmaDecorators []decor.EwmaDecorator + filler BarFiller + extender extenderFunc + renderReq chan<- time.Time + waitBar *Bar // key for (*pState).queueBars } type renderFrame struct { @@ -61,7 +58,6 @@ shutdown int rmOnComplete bool noPop bool - done bool err error } @@ -72,30 +68,33 @@ priority: bs.priority, frameCh: make(chan *renderFrame, 1), operateState: make(chan func(*bState)), - done: make(chan struct{}), + bsOk: make(chan struct{}), container: container, + ctx: ctx, cancel: cancel, } container.bwg.Add(1) - go bar.serve(ctx, bs) + go bar.serve(bs) return bar } -// ProxyReader wraps io.Reader with metrics required for progress tracking. -// If `r` is 'unknown total/size' reader it's mandatory to call -// (*Bar).SetTotal(-1, true) method after (io.Reader).Read returns io.EOF. -// If bar is already completed or aborted, returns nil. +// ProxyReader wraps io.Reader with metrics required for progress +// tracking. If `r` is 'unknown total/size' reader it's mandatory +// to call `(*Bar).SetTotal(-1, true)` after the wrapper returns +// `io.EOF`. If bar is already completed or aborted, returns nil. // Panics if `r` is nil. func (b *Bar) ProxyReader(r io.Reader) io.ReadCloser { if r == nil { panic("expected non nil io.Reader") } - result := make(chan bool) - select { - case b.operateState <- func(s *bState) { result <- len(s.ewmaDecorators) != 0 }: - return newProxyReader(r, b, <-result) - case <-b.done: + result := make(chan io.ReadCloser) + select { + case b.operateState <- func(s *bState) { + result <- newProxyReader(r, b, len(s.ewmaDecorators) != 0) + }: + return <-result + case <-b.ctx.Done(): return nil } } @@ -107,11 +106,13 @@ if w == nil { panic("expected non nil io.Writer") } - result := make(chan bool) - select { - case b.operateState <- func(s *bState) { result <- len(s.ewmaDecorators) != 0 }: - return newProxyWriter(w, b, <-result) - case <-b.done: + result := make(chan io.WriteCloser) + select { + case b.operateState <- func(s *bState) { + result <- newProxyWriter(w, b, len(s.ewmaDecorators) != 0) + }: + return <-result + case <-b.ctx.Done(): return nil } } @@ -122,7 +123,7 @@ select { case b.operateState <- func(s *bState) { result <- s.id }: return <-result - case <-b.done: + case <-b.bsOk: return b.bs.id } } @@ -133,7 +134,7 @@ select { case b.operateState <- func(s *bState) { result <- s.current }: return <-result - case <-b.done: + case <-b.bsOk: return b.bs.current } } @@ -151,61 +152,59 @@ s.refill = s.current } }: - case <-b.done: - } -} - -// TraverseDecorators traverses all available decorators and calls cb func on each. + case <-b.ctx.Done(): + } +} + +// TraverseDecorators traverses available decorators and calls cb func +// on each in a new goroutine. Decorators implementing decor.Wrapper +// interface are unwrapped first. func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) { - iter := make(chan decor.Decorator) - select { - case b.operateState <- func(s *bState) { - for _, decorators := range [][]decor.Decorator{ - s.pDecorators, - s.aDecorators, - } { + select { + case b.operateState <- func(s *bState) { + var wg sync.WaitGroup + for _, decorators := range s.decorators { + wg.Add(len(decorators)) for _, d := range decorators { - iter <- d + d := d + go func() { + cb(unwrap(d)) + wg.Done() + }() } } - close(iter) - }: - for d := range iter { - cb(unwrap(d)) - } - case <-b.done: - } -} - -// EnableTriggerComplete enables triggering complete event. It's -// effective only for bars which were constructed with `total <= 0` and -// after total has been set with (*Bar).SetTotal(int64, false). If bar -// has been incremented to the total, complete event is triggered right -// away. + wg.Wait() + }: + case <-b.ctx.Done(): + } +} + +// EnableTriggerComplete enables triggering complete event. It's effective +// only for bars which were constructed with `total <= 0`. If `curren >= total` +// at the moment of call, complete event is triggered right away. func (b *Bar) EnableTriggerComplete() { select { case b.operateState <- func(s *bState) { - if s.triggerComplete || s.total <= 0 { + if s.triggerComplete { return } if s.current >= s.total { s.current = s.total - s.completed = true - b.triggerCompletion(s) + s.triggerCompletion(b) } else { s.triggerComplete = true } }: - case <-b.done: - } -} - -// SetTotal sets total to an arbitrary value. It's effective only for -// bar which was constructed with `total <= 0`. Setting total to negative -// value is equivalent to (*Bar).SetTotal((*Bar).Current(), bool) but faster. -// If triggerCompletion is true, total value is set to current and -// complete event is triggered right away. -func (b *Bar) SetTotal(total int64, triggerCompletion bool) { + case <-b.ctx.Done(): + } +} + +// SetTotal sets total to an arbitrary value. It's effective only for bar +// which was constructed with `total <= 0`. Setting total to negative value +// is equivalent to `(*Bar).SetTotal((*Bar).Current(), bool)` but faster. +// If `complete` is true complete event is triggered right away. +// Calling `(*Bar).EnableTriggerComplete` makes this one no operational. +func (b *Bar) SetTotal(total int64, complete bool) { select { case b.operateState <- func(s *bState) { if s.triggerComplete { @@ -216,13 +215,12 @@ } else { s.total = total } - if triggerCompletion { + if complete { s.current = s.total - s.completed = true - b.triggerCompletion(s) - } - }: - case <-b.done: + s.triggerCompletion(b) + } + }: + case <-b.ctx.Done(): } } @@ -236,11 +234,69 @@ s.current = current if s.triggerComplete && s.current >= s.total { s.current = s.total - s.completed = true - b.triggerCompletion(s) - } - }: - case <-b.done: + s.triggerCompletion(b) + } + }: + case <-b.ctx.Done(): + } +} + +// Increment is a shorthand for b.IncrInt64(1). +func (b *Bar) Increment() { + b.IncrInt64(1) +} + +// IncrBy is a shorthand for b.IncrInt64(int64(n)). +func (b *Bar) IncrBy(n int) { + b.IncrInt64(int64(n)) +} + +// IncrInt64 increments progress by amount of n. +func (b *Bar) IncrInt64(n int64) { + select { + case b.operateState <- func(s *bState) { + s.current += n + if s.triggerComplete && s.current >= s.total { + s.current = s.total + s.triggerCompletion(b) + } + }: + case <-b.ctx.Done(): + } +} + +// EwmaIncrement is a shorthand for b.EwmaIncrInt64(1, iterDur). +func (b *Bar) EwmaIncrement(iterDur time.Duration) { + b.EwmaIncrInt64(1, iterDur) +} + +// EwmaIncrBy is a shorthand for b.EwmaIncrInt64(int64(n), iterDur). +func (b *Bar) EwmaIncrBy(n int, iterDur time.Duration) { + b.EwmaIncrInt64(int64(n), iterDur) +} + +// EwmaIncrInt64 increments progress by amount of n and updates EWMA based +// decorators by dur of a single iteration. +func (b *Bar) EwmaIncrInt64(n int64, iterDur time.Duration) { + select { + case b.operateState <- func(s *bState) { + var wg sync.WaitGroup + wg.Add(len(s.ewmaDecorators)) + for _, d := range s.ewmaDecorators { + d := d + go func() { + d.EwmaUpdate(n, iterDur) + wg.Done() + }() + } + s.current += n + if s.triggerComplete && s.current >= s.total { + s.current = s.total + s.triggerCompletion(b) + } + wg.Wait() + }: + case <-b.ctx.Done(): } } @@ -252,86 +308,35 @@ } select { case b.operateState <- func(s *bState) { - if n := current - s.current; n > 0 { - s.decoratorEwmaUpdate(n, iterDur) + n := current - s.current + var wg sync.WaitGroup + wg.Add(len(s.ewmaDecorators)) + for _, d := range s.ewmaDecorators { + d := d + go func() { + d.EwmaUpdate(n, iterDur) + wg.Done() + }() } s.current = current if s.triggerComplete && s.current >= s.total { s.current = s.total - s.completed = true - b.triggerCompletion(s) - } - }: - case <-b.done: - } -} - -// Increment is a shorthand for b.IncrInt64(1). -func (b *Bar) Increment() { - b.IncrInt64(1) -} - -// IncrBy is a shorthand for b.IncrInt64(int64(n)). -func (b *Bar) IncrBy(n int) { - b.IncrInt64(int64(n)) -} - -// IncrInt64 increments progress by amount of n. -func (b *Bar) IncrInt64(n int64) { - if n <= 0 { - return - } - select { - case b.operateState <- func(s *bState) { - s.current += n - if s.triggerComplete && s.current >= s.total { - s.current = s.total - s.completed = true - b.triggerCompletion(s) - } - }: - case <-b.done: - } -} - -// EwmaIncrement is a shorthand for b.EwmaIncrInt64(1, iterDur). -func (b *Bar) EwmaIncrement(iterDur time.Duration) { - b.EwmaIncrInt64(1, iterDur) -} - -// EwmaIncrBy is a shorthand for b.EwmaIncrInt64(int64(n), iterDur). -func (b *Bar) EwmaIncrBy(n int, iterDur time.Duration) { - b.EwmaIncrInt64(int64(n), iterDur) -} - -// EwmaIncrInt64 increments progress by amount of n and updates EWMA based -// decorators by dur of a single iteration. -func (b *Bar) EwmaIncrInt64(n int64, iterDur time.Duration) { - if n <= 0 { - return - } - select { - case b.operateState <- func(s *bState) { - s.decoratorEwmaUpdate(n, iterDur) - s.current += n - if s.triggerComplete && s.current >= s.total { - s.current = s.total - s.completed = true - b.triggerCompletion(s) - } - }: - case <-b.done: - } -} - -// DecoratorAverageAdjust adjusts all average based decorators. Call -// if you need to adjust start time of all average based decorators -// or after progress resume. + s.triggerCompletion(b) + } + wg.Wait() + }: + case <-b.ctx.Done(): + } +} + +// DecoratorAverageAdjust adjusts decorators implementing decor.AverageDecorator interface. +// Call if there is need to set start time after decorators have been constructed. func (b *Bar) DecoratorAverageAdjust(start time.Time) { - select { - case b.operateState <- func(s *bState) { s.decoratorAverageAdjust(start) }: - case <-b.done: - } + b.TraverseDecorators(func(d decor.Decorator) { + if d, ok := d.(decor.AverageDecorator); ok { + d.AverageAdjust(start) + } + }) } // SetPriority changes bar's order among multiple bars. Zero is highest @@ -344,18 +349,18 @@ // 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. To make sure that bar has been removed call -// (*Bar).Wait method. +// `(*Bar).Wait()` method. func (b *Bar) Abort(drop bool) { select { case b.operateState <- func(s *bState) { - if s.completed || s.aborted { + if s.aborted || s.completed() { return } s.aborted = true s.rmOnComplete = drop - b.triggerCompletion(s) - }: - case <-b.done: + s.triggerCompletion(b) + }: + case <-b.ctx.Done(): } } @@ -365,7 +370,7 @@ select { case b.operateState <- func(s *bState) { result <- s.aborted }: return <-result - case <-b.done: + case <-b.bsOk: return b.bs.aborted } } @@ -374,72 +379,75 @@ func (b *Bar) Completed() bool { result := make(chan bool) select { - case b.operateState <- func(s *bState) { result <- s.completed }: + case b.operateState <- func(s *bState) { result <- s.completed() }: return <-result - case <-b.done: - return b.bs.completed - } -} - -// IsRunning reports whether the bar is running, i.e. not yet completed -// and not yet aborted. + case <-b.bsOk: + return b.bs.completed() + } +} + +// IsRunning reports whether the bar is in running state. func (b *Bar) IsRunning() bool { - result := make(chan bool) - select { - case b.operateState <- func(s *bState) { result <- !s.completed && !s.aborted }: - return <-result - case <-b.done: + select { + case <-b.ctx.Done(): return false + default: + return true } } // Wait blocks until bar is completed or aborted. func (b *Bar) Wait() { - <-b.done -} - -func (b *Bar) serve(ctx context.Context, bs *bState) { - defer b.container.bwg.Done() + <-b.bsOk +} + +func (b *Bar) serve(bs *bState) { + decoratorsOnShutdown := func(decorators []decor.Decorator) { + for _, d := range decorators { + if d, ok := unwrap(d).(decor.ShutdownListener); ok { + b.container.bwg.Add(1) + go func() { + d.OnShutdown() + b.container.bwg.Done() + }() + } + } + } for { select { case op := <-b.operateState: op(bs) - case <-ctx.Done(): - bs.aborted = !bs.completed - bs.decoratorShutdownNotify() + case <-b.ctx.Done(): + decoratorsOnShutdown(bs.decorators[0]) + decoratorsOnShutdown(bs.decorators[1]) + // bar can be aborted by canceling parent ctx without calling b.Abort + bs.aborted = !bs.completed() b.bs = bs - close(b.done) + close(b.bsOk) + b.container.bwg.Done() return } } } func (b *Bar) render(tw int) { - var done bool fn := func(s *bState) { - var rows []io.Reader - stat := newStatistics(tw, s) + frame := new(renderFrame) + stat := s.newStatistics(tw) r, err := s.draw(stat) if err != nil { - b.frameCh <- &renderFrame{err: err} + for _, buf := range s.buffers { + buf.Reset() + } + frame.err = err + b.frameCh <- frame return } - rows = append(rows, r) - if s.extender != nil { - rows, err = s.extender(rows, stat) - if err != nil { - b.frameCh <- &renderFrame{err: err} - return - } - } - frame := &renderFrame{ - rows: rows, - shutdown: s.shutdown, - rmOnComplete: s.rmOnComplete, - noPop: s.noPop, - done: done, - } - if s.completed || s.aborted { + frame.rows, frame.err = s.extender(stat, r) + if s.aborted || s.completed() { + frame.shutdown = s.shutdown + frame.rmOnComplete = s.rmOnComplete + frame.noPop = s.noPop // post increment makes sure OnComplete decorators are rendered s.shutdown++ } @@ -447,34 +455,25 @@ } select { case b.operateState <- fn: - case <-b.done: - done = true + case <-b.bsOk: fn(b.bs) } } -func (b *Bar) triggerCompletion(s *bState) { - if s.autoRefresh { - // Technically this call isn't required, but if refresh rate is set to - // one hour for example and bar completes within a few minutes p.Wait() - // will wait for one hour. This call helps to avoid unnecessary waiting. - go b.tryEarlyRefresh(s.renderReq) - } else { - b.cancel() - } -} - func (b *Bar) tryEarlyRefresh(renderReq chan<- time.Time) { - var anyOtherRunning bool + var otherRunning int b.container.traverseBars(func(bar *Bar) bool { - anyOtherRunning = b != bar && bar.IsRunning() - return anyOtherRunning + if b != bar && bar.IsRunning() { + otherRunning++ + return false // stop traverse + } + return true // continue traverse }) - if !anyOtherRunning { + if otherRunning == 0 { for { select { case renderReq <- time.Now(): - case <-b.done: + case <-b.ctx.Done(): return } } @@ -486,23 +485,12 @@ select { case b.operateState <- func(s *bState) { result <- s.wSyncTable() }: return <-result - case <-b.done: + case <-b.bsOk: return b.bs.wSyncTable() } } -func (s *bState) draw(stat decor.Statistics) (io.Reader, error) { - r, err := s.drawImpl(stat) - if err != nil { - for _, b := range s.buffers { - b.Reset() - } - return nil, err - } - return io.MultiReader(r, strings.NewReader("\n")), nil -} - -func (s *bState) drawImpl(stat decor.Statistics) (io.Reader, error) { +func (s *bState) draw(stat decor.Statistics) (_ io.Reader, err error) { decorFiller := func(buf *bytes.Buffer, decorators []decor.Decorator) (err error) { for _, d := range decorators { // need to call Decor in any case becase of width synchronization @@ -522,45 +510,45 @@ return err } - bufP, bufB, bufA := s.buffers[0], s.buffers[1], s.buffers[2] - - err := eitherError(decorFiller(bufP, s.pDecorators), decorFiller(bufA, s.aDecorators)) + for i, buf := range s.buffers[:2] { + err = decorFiller(buf, s.decorators[i]) + if err != nil { + return nil, err + } + } + + spaces := []io.Reader{ + strings.NewReader(" "), + strings.NewReader(" "), + } + if s.trimSpace || stat.AvailableWidth < 2 { + for _, r := range spaces { + _, _ = io.Copy(io.Discard, r) + } + } else { + stat.AvailableWidth -= 2 + } + + err = s.filler.Fill(s.buffers[2], stat) if err != nil { return nil, err } - if !s.trimSpace && stat.AvailableWidth >= 2 { - stat.AvailableWidth -= 2 - writeFiller := func(buf *bytes.Buffer) error { - return s.filler.Fill(buf, stat) - } - for _, fn := range []func(*bytes.Buffer) error{ - writeSpace, - writeFiller, - writeSpace, - } { - if err := fn(bufB); err != nil { - return nil, err - } - } - } else { - err := s.filler.Fill(bufB, stat) - if err != nil { - return nil, err - } - } - - return io.MultiReader(bufP, bufB, bufA), nil + return io.MultiReader( + s.buffers[0], + spaces[0], + s.buffers[2], + spaces[1], + s.buffers[1], + strings.NewReader("\n"), + ), nil } func (s *bState) wSyncTable() (table syncTable) { var count int var row []chan int - for i, decorators := range [][]decor.Decorator{ - s.pDecorators, - s.aDecorators, - } { + for i, decorators := range s.decorators { for _, d := range decorators { if ch, ok := d.Sync(); ok { row = append(row, ch) @@ -577,58 +565,31 @@ return table } -func (s bState) decoratorEwmaUpdate(n int64, dur time.Duration) { - var wg sync.WaitGroup - for i := 0; i < len(s.ewmaDecorators); i++ { - switch d := s.ewmaDecorators[i]; i { - case len(s.ewmaDecorators) - 1: - d.EwmaUpdate(n, dur) - default: - wg.Add(1) - go func() { - d.EwmaUpdate(n, dur) - wg.Done() - }() - } - } - wg.Wait() -} - -func (s bState) decoratorAverageAdjust(start time.Time) { - var wg 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() { - var wg sync.WaitGroup - for i := 0; i < len(s.shutdownListeners); i++ { - switch d := s.shutdownListeners[i]; i { - case len(s.shutdownListeners) - 1: - d.OnShutdown() - default: - wg.Add(1) - go func() { - d.OnShutdown() - wg.Done() - }() - } - } - wg.Wait() -} - -func newStatistics(tw int, s *bState) decor.Statistics { +func (s *bState) populateEwmaDecorators(decorators []decor.Decorator) { + for _, d := range decorators { + if d, ok := unwrap(d).(decor.EwmaDecorator); ok { + s.ewmaDecorators = append(s.ewmaDecorators, d) + } + } +} + +func (s *bState) triggerCompletion(b *Bar) { + s.triggerComplete = true + if s.autoRefresh { + // Technically this call isn't required, but if refresh rate is set to + // one hour for example and bar completes within a few minutes p.Wait() + // will wait for one hour. This call helps to avoid unnecessary waiting. + go b.tryEarlyRefresh(s.renderReq) + } else { + b.cancel() + } +} + +func (s bState) completed() bool { + return s.triggerComplete && s.current == s.total +} + +func (s bState) newStatistics(tw int) decor.Statistics { return decor.Statistics{ AvailableWidth: tw, RequestedWidth: s.reqWidth, @@ -636,7 +597,7 @@ Total: s.total, Current: s.current, Refill: s.refill, - Completed: s.completed, + Completed: s.completed(), Aborted: s.aborted, } } @@ -647,16 +608,3 @@ } return d } - -func writeSpace(buf *bytes.Buffer) error { - return buf.WriteByte(' ') -} - -func eitherError(errors ...error) error { - for _, err := range errors { - if err != nil { - return err - } - } - return nil -} diff --git a/bar_filler_bar.go b/bar_filler_bar.go index 5d7837a..7a036d9 100644 --- a/bar_filler_bar.go +++ b/bar_filler_bar.go @@ -50,13 +50,13 @@ } type bFiller struct { - components [components]component - meta [components]func(io.Writer, []byte) error - flush func(io.Writer, ...flushSection) error - tipOnComplete bool - tip struct { - frames []component - count uint + components [components]component + meta [components]func(io.Writer, []byte) error + flush func(io.Writer, ...flushSection) error + tip struct { + onComplete bool + count uint + frames []component } } @@ -155,8 +155,7 @@ func (s barStyle) Build() BarFiller { bf := &bFiller{ - meta: s.metaFuncs, - tipOnComplete: s.tipOnComplete, + meta: s.metaFuncs, } bf.components[iLbound] = component{ width: runewidth.StringWidth(s.style[iLbound]), @@ -178,6 +177,7 @@ width: runewidth.StringWidth(s.style[iPadding]), bytes: []byte(s.style[iPadding]), } + bf.tip.onComplete = s.tipOnComplete bf.tip.frames = make([]component, len(s.tipFrames)) for i, t := range s.tipFrames { bf.tip.frames[i] = component{ @@ -236,24 +236,23 @@ curWidth := int(internal.PercentageRound(stat.Total, stat.Current, uint(width))) if curWidth != 0 { - if !stat.Completed || s.tipOnComplete { + if !stat.Completed || s.tip.onComplete { tip = s.tip.frames[s.tip.count%uint(len(s.tip.frames))] s.tip.count++ fillCount += tip.width } - if stat.Refill != 0 { - refWidth := int(internal.PercentageRound(stat.Total, stat.Refill, uint(width))) + switch refWidth := 0; { + case stat.Refill != 0: + refWidth = int(internal.PercentageRound(stat.Total, stat.Refill, uint(width))) curWidth -= refWidth refWidth += curWidth + fallthrough + default: for w := s.components[iFiller].width; curWidth-fillCount >= w; fillCount += w { filling = append(filling, s.components[iFiller].bytes...) } for w := s.components[iRefiller].width; refWidth-fillCount >= w; fillCount += w { refilling = append(refilling, s.components[iRefiller].bytes...) - } - } else { - for w := s.components[iFiller].width; curWidth-fillCount >= w; fillCount += w { - filling = append(filling, s.components[iFiller].bytes...) } } } diff --git a/bar_option.go b/bar_option.go index d3cb3e2..6247a33 100644 --- a/bar_option.go +++ b/bar_option.go @@ -20,19 +20,21 @@ return } +// PrependDecorators let you inject decorators to the bar's left side. +func PrependDecorators(decorators ...decor.Decorator) BarOption { + decorators = inspect(decorators) + return func(s *bState) { + s.populateEwmaDecorators(decorators) + s.decorators[0] = decorators + } +} + // AppendDecorators let you inject decorators to the bar's right side. func AppendDecorators(decorators ...decor.Decorator) BarOption { decorators = inspect(decorators) return func(s *bState) { - s.aDecorators = decorators - } -} - -// PrependDecorators let you inject decorators to the bar's left side. -func PrependDecorators(decorators ...decor.Decorator) BarOption { - decorators = inspect(decorators) - return func(s *bState) { - s.pDecorators = decorators + s.populateEwmaDecorators(decorators) + s.decorators[1] = decorators } } @@ -112,6 +114,9 @@ if filler == nil { return nil } + if f, ok := filler.(BarFillerFunc); ok && f == nil { + return nil + } fn := makeExtenderFunc(filler, rev) return func(s *bState) { s.extender = fn @@ -120,28 +125,27 @@ func makeExtenderFunc(filler BarFiller, rev bool) extenderFunc { buf := new(bytes.Buffer) - base := func(rows []io.Reader, stat decor.Statistics) ([]io.Reader, error) { + base := func(stat decor.Statistics, rows ...io.Reader) ([]io.Reader, error) { err := filler.Fill(buf, stat) if err != nil { buf.Reset() return rows, err } for { - b, err := buf.ReadBytes('\n') + line, err := buf.ReadBytes('\n') if err != nil { + buf.Reset() break } - rows = append(rows, bytes.NewReader(b)) - } - buf.Reset() + rows = append(rows, bytes.NewReader(line)) + } return rows, err } - if !rev { return base } - return func(rows []io.Reader, stat decor.Statistics) ([]io.Reader, error) { - rows, err := base(rows, stat) + return func(stat decor.Statistics, rows ...io.Reader) ([]io.Reader, error) { + rows, err := base(stat, rows...) if err != nil { return rows, err } diff --git a/bar_test.go b/bar_test.go index c5e0e70..f92b91a 100644 --- a/bar_test.go +++ b/bar_test.go @@ -20,13 +20,13 @@ bar := p.AddBar(int64(total)) if bar.Completed() { - t.Fail() + t.Error("expected bar not to complete") } bar.IncrBy(total) if !bar.Completed() { - t.Error("bar isn't completed after increment") + t.Error("expected bar to complete") } p.Wait() @@ -38,13 +38,13 @@ bar := p.AddBar(int64(total)) if bar.Aborted() { - t.Fail() + t.Error("expected bar not to be aborted") } bar.Abort(false) if !bar.Aborted() { - t.Error("bar isn't aborted after abort call") + t.Error("expected bar to be aborted") } p.Wait() @@ -67,26 +67,51 @@ p.Wait() } +func TestBarEnableTriggerCompleteZeroBar(t *testing.T) { + p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(io.Discard)) + bar := p.AddBar(0) // never complete bar + + if bar.Completed() { + t.Error("expected bar not to complete") + } + + // Calling bar.SetTotal(0, true) has same effect + // but this one is more concise and intuitive + bar.EnableTriggerComplete() + + if !bar.Completed() { + t.Error("expected bar to complete") + } + + p.Wait() +} + func TestBarEnableTriggerCompleteAndIncrementBefore(t *testing.T) { p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(io.Discard)) bar := p.AddBar(0) // never complete bar + + targetTotal := int64(80) for _, f := range []func(){ func() { bar.SetTotal(40, false) }, func() { bar.IncrBy(60) }, - func() { bar.SetTotal(80, false) }, + func() { bar.SetTotal(targetTotal, false) }, func() { bar.IncrBy(20) }, } { f() if bar.Completed() { - t.Fail() + t.Error("expected bar not to complete") } } bar.EnableTriggerComplete() if !bar.Completed() { - t.Fail() + t.Error("expected bar to complete") + } + + if current := bar.Current(); current != targetTotal { + t.Errorf("Expected current: %d, got: %d", targetTotal, current) } p.Wait() @@ -95,23 +120,30 @@ func TestBarEnableTriggerCompleteAndIncrementAfter(t *testing.T) { p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(io.Discard)) bar := p.AddBar(0) // never complete bar + + targetTotal := int64(80) for _, f := range []func(){ func() { bar.SetTotal(40, false) }, func() { bar.IncrBy(60) }, - func() { bar.SetTotal(80, false) }, - func() { bar.EnableTriggerComplete() }, + func() { bar.SetTotal(targetTotal, false) }, + func() { bar.EnableTriggerComplete() }, // disables any next SetTotal + func() { bar.SetTotal(100, true) }, // nop } { f() if bar.Completed() { - t.Fail() + t.Error("expected bar not to complete") } } bar.IncrBy(20) if !bar.Completed() { - t.Fail() + t.Error("expected bar to complete") + } + + if current := bar.Current(); current != targetTotal { + t.Errorf("Expected current: %d, got: %d", targetTotal, current) } p.Wait() diff --git a/barbench_test.go b/barbench_test.go index 47b633f..77e0868 100644 --- a/barbench_test.go +++ b/barbench_test.go @@ -2,7 +2,6 @@ import ( "io" - "sync" "testing" "github.com/vbauerster/mpb/v8" @@ -10,84 +9,81 @@ const total = 1000 -func BenchmarkNopStyle1Bar(b *testing.B) { +func BenchmarkNopStyleB1(b *testing.B) { bench(b, mpb.NopStyle(), false, 1) } -func BenchmarkNopStyle1BarWithAutoRefresh(b *testing.B) { +func BenchmarkNopStyleWithAutoRefreshB1(b *testing.B) { bench(b, mpb.NopStyle(), true, 1) } -func BenchmarkNopStyle2Bars(b *testing.B) { +func BenchmarkNopStylesB2(b *testing.B) { bench(b, mpb.NopStyle(), false, 2) } -func BenchmarkNopStyle2BarsWithAutoRefresh(b *testing.B) { +func BenchmarkNopStylesWithAutoRefreshB2(b *testing.B) { bench(b, mpb.NopStyle(), true, 2) } -func BenchmarkNopStyle3Bars(b *testing.B) { +func BenchmarkNopStylesB3(b *testing.B) { bench(b, mpb.NopStyle(), false, 3) } -func BenchmarkNopStyle3BarsWithAutoRefresh(b *testing.B) { +func BenchmarkNopStylesWithAutoRefreshB3(b *testing.B) { bench(b, mpb.NopStyle(), true, 3) } -func BenchmarkBarStyle1Bar(b *testing.B) { +func BenchmarkBarStyleB1(b *testing.B) { bench(b, mpb.BarStyle(), false, 1) } -func BenchmarkBarStyle1BarWithAutoRefresh(b *testing.B) { +func BenchmarkBarStyleWithAutoRefreshB1(b *testing.B) { bench(b, mpb.BarStyle(), true, 1) } -func BenchmarkBarStyle2Bars(b *testing.B) { +func BenchmarkBarStylesB2(b *testing.B) { bench(b, mpb.BarStyle(), false, 2) } -func BenchmarkBarStyle2BarsWithAutoRefresh(b *testing.B) { +func BenchmarkBarStylesWithAutoRefreshB2(b *testing.B) { bench(b, mpb.BarStyle(), true, 2) } -func BenchmarkBarStyle3Bars(b *testing.B) { +func BenchmarkBarStylesB3(b *testing.B) { bench(b, mpb.BarStyle(), false, 3) } -func BenchmarkBarStyle3BarsWithAutoRefresh(b *testing.B) { +func BenchmarkBarStylesWithAutoRefreshB3(b *testing.B) { bench(b, mpb.BarStyle(), true, 3) } func bench(b *testing.B, builder mpb.BarFillerBuilder, autoRefresh bool, n int) { - var wg sync.WaitGroup p := mpb.New( mpb.WithWidth(100), mpb.WithOutput(io.Discard), mpb.ContainerOptional(mpb.WithAutoRefresh(), autoRefresh), ) + defer p.Wait() b.ResetTimer() for i := 0; i < b.N; i++ { + var bars []*mpb.Bar for j := 0; j < n; j++ { - bar := p.New(total, builder) + bars = append(bars, p.New(total, builder)) switch j { case n - 1: - complete(b, bar) + complete(bars[j]) default: - wg.Add(1) - go func() { - complete(b, bar) - wg.Done() - }() + go complete(bars[j]) } } - wg.Wait() + for _, bar := range bars { + bar.Wait() + } } - p.Wait() } -func complete(b *testing.B, bar *mpb.Bar) { +func complete(bar *mpb.Bar) { for i := 0; i < total; i++ { bar.Increment() } - bar.Wait() } diff --git a/container_option.go b/container_option.go index f2ab01e..177620e 100644 --- a/container_option.go +++ b/container_option.go @@ -93,9 +93,9 @@ } } -// PopCompletedMode will pop completed bars to the top. -// To stop rendering bar after it has been popped, use -// mpb.BarRemoveOnComplete() option on that bar. +// PopCompletedMode pop completed bars out of progress container. +// In this mode completed bars get moved to the top and stop +// participating in rendering cycle. func PopCompletedMode() ContainerOption { return func(s *pState) { s.popCompleted = true diff --git a/debian/changelog b/debian/changelog index 1aa595a..8913fd3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +golang-github-vbauerster-mpb (8.8.2-1) experimental; urgency=medium + + * Team upload + * New upstream version: v8.8.3 + + -- Reinhard Tartler Sat, 09 Nov 2024 11:06:55 -0500 + golang-github-vbauerster-mpb (8.6.1-3) unstable; urgency=medium * Add golang-github-containers-image (<< 5.25) diff --git a/decor/decorator.go b/decor/decorator.go index f537d3f..6bec115 100644 --- a/decor/decorator.go +++ b/decor/decorator.go @@ -8,29 +8,27 @@ ) const ( - // DidentRight bit specifies identation direction. + // DindentRight sets indentation from right to left. // - // |foo |b | With DidentRight - // | foo| b| Without DidentRight - DidentRight = 1 << iota + // |foo |b | DindentRight is set + // | foo| b| DindentRight is not set + DindentRight = 1 << iota - // DextraSpace bit adds extra space, makes sense with DSyncWidth only. - // When DidentRight bit set, the space will be added to the right, - // otherwise to the left. + // DextraSpace bit adds extra indentation space. DextraSpace // DSyncWidth bit enables same column width synchronization. // Effective with multiple bars only. DSyncWidth - // DSyncWidthR is shortcut for DSyncWidth|DidentRight - DSyncWidthR = DSyncWidth | DidentRight + // DSyncWidthR is shortcut for DSyncWidth|DindentRight + DSyncWidthR = DSyncWidth | DindentRight // DSyncSpace is shortcut for DSyncWidth|DextraSpace DSyncSpace = DSyncWidth | DextraSpace - // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight - DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight + // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DindentRight + DSyncSpaceR = DSyncWidth | DextraSpace | DindentRight ) // TimeStyle enum. @@ -87,7 +85,7 @@ // in order to format string according to decor.WC settings. // No need to implement manually as long as decor.WC is embedded. type Formatter interface { - Format(string) (str string, viewWidth int) + Format(string) (_ string, width int) } // Wrapper interface. @@ -140,23 +138,22 @@ // Format should be called by any Decorator implementation. // Returns formatted string and its view (visual) width. func (wc WC) Format(str string) (string, int) { - viewWidth := runewidth.StringWidth(str) - if wc.W > viewWidth { - viewWidth = wc.W + width := runewidth.StringWidth(str) + if wc.W > width { + width = wc.W + } else if (wc.C & DextraSpace) != 0 { + width++ } if (wc.C & DSyncWidth) != 0 { - if (wc.C & DextraSpace) != 0 { - viewWidth++ - } - wc.wsync <- viewWidth - viewWidth = <-wc.wsync + wc.wsync <- width + width = <-wc.wsync } - return wc.fill(str, viewWidth), viewWidth + return wc.fill(str, width), width } // Init initializes width related config. func (wc *WC) Init() WC { - if (wc.C & DidentRight) != 0 { + if (wc.C & DindentRight) != 0 { wc.fill = runewidth.FillRight } else { wc.fill = runewidth.FillLeft diff --git a/decor/elapsed.go b/decor/elapsed.go index 6cee7d1..f3ed7a8 100644 --- a/decor/elapsed.go +++ b/decor/elapsed.go @@ -17,15 +17,15 @@ // // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] // -// `startTime` start time +// `start` start time // // `wcc` optional WC config -func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator { +func NewElapsed(style TimeStyle, start time.Time, wcc ...WC) Decorator { var msg string producer := chooseTimeProducer(style) fn := func(s Statistics) string { - if !s.Completed { - msg = producer(time.Since(startTime)) + if !s.Completed && !s.Aborted { + msg = producer(time.Since(start)) } return msg } diff --git a/decor/eta.go b/decor/eta.go index ecb6f8f..64ec74a 100644 --- a/decor/eta.go +++ b/decor/eta.go @@ -33,13 +33,18 @@ // decorator to work correctly you have to measure each iteration's duration // and pass it to one of the (*Bar).EwmaIncr... family methods. func EwmaETA(style TimeStyle, age float64, wcc ...WC) Decorator { + return EwmaNormalizedETA(style, age, nil, wcc...) +} + +// EwmaNormalizedETA same as EwmaETA but with TimeNormalizer option. +func EwmaNormalizedETA(style TimeStyle, age float64, normalizer TimeNormalizer, wcc ...WC) Decorator { var average ewma.MovingAverage if age == 0 { average = ewma.NewMovingAverage() } else { average = ewma.NewMovingAverage(age) } - return MovingAverageETA(style, NewThreadSafeMovingAverage(average), nil, wcc...) + return MovingAverageETA(style, average, normalizer, wcc...) } // MovingAverageETA decorator relies on MovingAverage implementation to calculate its average. @@ -52,20 +57,24 @@ // // `wcc` optional WC config func MovingAverageETA(style TimeStyle, average ewma.MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator { + if average == nil { + average = NewMedian() + } d := &movingAverageETA{ WC: initWC(wcc...), + producer: chooseTimeProducer(style), average: average, normalizer: normalizer, - producer: chooseTimeProducer(style), } return d } type movingAverageETA struct { WC + producer func(time.Duration) string average ewma.MovingAverage normalizer TimeNormalizer - producer func(time.Duration) string + zDur time.Duration } func (d *movingAverageETA) Decor(s Statistics) (string, int) { @@ -78,10 +87,16 @@ } func (d *movingAverageETA) EwmaUpdate(n int64, dur time.Duration) { - durPerItem := float64(dur) / float64(n) + if n <= 0 { + d.zDur += dur + return + } + durPerItem := float64(d.zDur+dur) / float64(n) if math.IsInf(durPerItem, 0) || math.IsNaN(durPerItem) { + d.zDur += dur return } + d.zDur = 0 d.average.Add(durPerItem) } @@ -98,15 +113,15 @@ // // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] // -// `startTime` start time +// `start` start time // // `normalizer` available implementations are [FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer] // // `wcc` optional WC config -func NewAverageETA(style TimeStyle, startTime time.Time, normalizer TimeNormalizer, wcc ...WC) Decorator { +func NewAverageETA(style TimeStyle, start time.Time, normalizer TimeNormalizer, wcc ...WC) Decorator { d := &averageETA{ WC: initWC(wcc...), - startTime: startTime, + start: start, normalizer: normalizer, producer: chooseTimeProducer(style), } @@ -115,7 +130,7 @@ type averageETA struct { WC - startTime time.Time + start time.Time normalizer TimeNormalizer producer func(time.Duration) string } @@ -123,7 +138,7 @@ func (d *averageETA) Decor(s Statistics) (string, int) { var remaining time.Duration if s.Current != 0 { - durPerItem := float64(time.Since(d.startTime)) / float64(s.Current) + durPerItem := float64(time.Since(d.start)) / float64(s.Current) durPerItem = math.Round(durPerItem) remaining = time.Duration((s.Total - s.Current) * int64(durPerItem)) if d.normalizer != nil { @@ -133,8 +148,8 @@ return d.Format(d.producer(remaining)) } -func (d *averageETA) AverageAdjust(startTime time.Time) { - d.startTime = startTime +func (d *averageETA) AverageAdjust(start time.Time) { + d.start = start } // MaxTolerateTimeNormalizer returns implementation of TimeNormalizer. diff --git a/decor/moving_average.go b/decor/moving_average.go index a1be8ad..165ef1e 100644 --- a/decor/moving_average.go +++ b/decor/moving_average.go @@ -70,5 +70,5 @@ // NewMedian is fixed last 3 samples median MovingAverage. func NewMedian() ewma.MovingAverage { - return NewThreadSafeMovingAverage(new(medianWindow)) + return new(medianWindow) } diff --git a/decor/on_abort.go b/decor/on_abort.go index 50a1dfb..3e35ddf 100644 --- a/decor/on_abort.go +++ b/decor/on_abort.go @@ -56,7 +56,7 @@ } func (d onAbortMetaWrapper) Decor(s Statistics) (string, int) { - if s.Completed { + if s.Aborted { str, width := d.Decorator.Decor(s) return d.fn(str), width } diff --git a/decor/percentage.go b/decor/percentage.go index 9709c19..547117b 100644 --- a/decor/percentage.go +++ b/decor/percentage.go @@ -61,7 +61,7 @@ format = "% d" } f := func(s Statistics) string { - p := internal.Percentage(s.Total, s.Current, 100) + p := internal.Percentage(uint(s.Total), uint(s.Current), 100) return fmt.Sprintf(format, percentageType(p)) } return Any(f, wcc...) diff --git a/decor/percentage_test.go b/decor/percentage_test.go index bbddf5b..a3ad458 100644 --- a/decor/percentage_test.go +++ b/decor/percentage_test.go @@ -56,3 +56,55 @@ }) } } + +func TestPercentageDecor(t *testing.T) { + cases := []struct { + name string + fmt string + current int64 + total int64 + expected string + }{ + { + name: "tot:100 cur:0 fmt:none", + fmt: "", + current: 0, + total: 100, + expected: "0 %", + }, + { + name: "tot:100 cur:10 fmt:none", + fmt: "", + current: 10, + total: 100, + expected: "10 %", + }, + { + name: "tot:100 cur:10 fmt:%.2f", + fmt: "%.2f", + current: 10, + total: 100, + expected: "10.00%", + }, + { + name: "tot:99 cur:10 fmt:%.2f", + fmt: "%.2f", + current: 11, + total: 99, + expected: "11.11%", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + decor := NewPercentage(tc.fmt) + stat := Statistics{ + Total: tc.total, + Current: tc.current, + } + res, _ := decor.Decor(stat) + if res != tc.expected { + t.Fatalf("expected: %q, got: %q\n", tc.expected, res) + } + }) + } +} diff --git a/decor/speed.go b/decor/speed.go index 5879d06..b643e10 100644 --- a/decor/speed.go +++ b/decor/speed.go @@ -21,15 +21,15 @@ // // fmt.Printf("%.1f", FmtAsSpeed(SizeB1024(2048))) func FmtAsSpeed(input fmt.Formatter) fmt.Formatter { - return speedFormatter{input} + return &speedFormatter{input} } type speedFormatter struct { fmt.Formatter } -func (self speedFormatter) Format(st fmt.State, verb rune) { - self.Formatter.Format(st, verb) +func (s *speedFormatter) Format(st fmt.State, verb rune) { + s.Formatter.Format(st, verb) _, err := io.WriteString(st, "/s") if err != nil { panic(err) @@ -46,7 +46,7 @@ } else { average = ewma.NewMovingAverage(age) } - return MovingAverageSpeed(unit, format, NewThreadSafeMovingAverage(average), wcc...) + return MovingAverageSpeed(unit, format, average, wcc...) } // MovingAverageSpeed decorator relies on MovingAverage implementation @@ -69,8 +69,8 @@ func MovingAverageSpeed(unit interface{}, format string, average ewma.MovingAverage, wcc ...WC) Decorator { d := &movingAverageSpeed{ WC: initWC(wcc...), + producer: chooseSpeedProducer(unit, format), average: average, - producer: chooseSpeedProducer(unit, format), } return d } @@ -79,25 +79,31 @@ WC producer func(float64) string average ewma.MovingAverage - msg string + zDur time.Duration } -func (d *movingAverageSpeed) Decor(s Statistics) (string, int) { - if !s.Completed { - var speed float64 - if v := d.average.Value(); v > 0 { - speed = 1 / v - } - d.msg = d.producer(speed * 1e9) +func (d *movingAverageSpeed) Decor(_ Statistics) (string, int) { + var str string + // ewma implementation may return 0 before accumulating certain number of samples + if v := d.average.Value(); v != 0 { + str = d.producer(1e9 / v) + } else { + str = d.producer(0) } - return d.Format(d.msg) + return d.Format(str) } func (d *movingAverageSpeed) EwmaUpdate(n int64, dur time.Duration) { - durPerByte := float64(dur) / float64(n) - if math.IsInf(durPerByte, 0) || math.IsNaN(durPerByte) { + if n <= 0 { + d.zDur += dur return } + durPerByte := float64(d.zDur+dur) / float64(n) + if math.IsInf(durPerByte, 0) || math.IsNaN(durPerByte) { + d.zDur += dur + return + } + d.zDur = 0 d.average.Add(durPerByte) } @@ -114,7 +120,7 @@ // // `format` printf compatible verb for value, like "%f" or "%d" // -// `startTime` start time +// `start` start time // // `wcc` optional WC config // @@ -124,32 +130,32 @@ // unit=SizeB1024(0), format="% .1f" output: "1.0 MiB/s" // unit=SizeB1000(0), format="%.1f" output: "1.0MB/s" // unit=SizeB1000(0), format="% .1f" output: "1.0 MB/s" -func NewAverageSpeed(unit interface{}, format string, startTime time.Time, wcc ...WC) Decorator { +func NewAverageSpeed(unit interface{}, format string, start time.Time, wcc ...WC) Decorator { d := &averageSpeed{ - WC: initWC(wcc...), - startTime: startTime, - producer: chooseSpeedProducer(unit, format), + WC: initWC(wcc...), + start: start, + producer: chooseSpeedProducer(unit, format), } return d } type averageSpeed struct { WC - startTime time.Time - producer func(float64) string - msg string + start time.Time + producer func(float64) string + msg string } func (d *averageSpeed) Decor(s Statistics) (string, int) { if !s.Completed { - speed := float64(s.Current) / float64(time.Since(d.startTime)) + speed := float64(s.Current) / float64(time.Since(d.start)) d.msg = d.producer(speed * 1e9) } return d.Format(d.msg) } -func (d *averageSpeed) AverageAdjust(startTime time.Time) { - d.startTime = startTime +func (d *averageSpeed) AverageAdjust(start time.Time) { + d.start = start } func chooseSpeedProducer(unit interface{}, format string) func(float64) string { diff --git a/decorators_test.go b/decorators_test.go index 56e7a4b..7bc916f 100644 --- a/decorators_test.go +++ b/decorators_test.go @@ -25,7 +25,7 @@ want: " Test", }, { - decorator: decor.Name("Test", decor.WC{W: 10, C: decor.DidentRight}), + decorator: decor.Name("Test", decor.WC{W: 10, C: decor.DindentRight}), want: "Test ", }, } @@ -88,7 +88,7 @@ testDecoratorConcurrently(t, testCases) } -func TestPercentageDwidthSyncDidentRight(t *testing.T) { +func TestPercentageDwidthSyncDindentRight(t *testing.T) { testCases := [][]step{ { diff --git a/draw_test.go b/draw_test.go index f7baeaf..eab5751 100644 --- a/draw_test.go +++ b/draw_test.go @@ -622,6 +622,13 @@ want: " [>-----------------------------------------------------------------------------------------------] ", }, { + style: BarStyle().Tip(""), + name: "t,c{100,1}empty_tip", + total: 100, + current: 1, + want: " [=-----------------------------------------------------------------------------------------------] ", + }, + { style: BarStyle(), name: "t,c{100,1}trim", total: 100, @@ -635,6 +642,13 @@ total: 100, current: 99, want: " [==============================================================================================>-] ", + }, + { + style: BarStyle().Tip(""), + name: "t,c{100,99}empty_tip", + total: 100, + current: 99, + want: " [===============================================================================================-] ", }, { style: BarStyle(), @@ -767,24 +781,21 @@ 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 + ps := pState{reqWidth: tc.barWidth} + s := ps.makeBarState(tc.total, tc.style.Build()) s.current = tc.current s.trimSpace = tc.trim s.refill = tc.refill - s.completed = tc.total > 0 && tc.current >= tc.total + r, err := s.draw(s.newStatistics(tw)) + if err != nil { + t.Fatalf("tw: %d case %q draw error: %s", tw, tc.name, err.Error()) + } tmpBuf.Reset() - r, err := s.draw(newStatistics(tw, s)) - if err != nil { - t.FailNow() - } _, err = tmpBuf.ReadFrom(r) if err != nil { t.FailNow() } by := tmpBuf.Bytes() - got := string(by[:len(by)-1]) if !utf8.ValidString(got) { t.Fail() @@ -1222,18 +1233,16 @@ 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 + ps := pState{reqWidth: tc.barWidth} + s := ps.makeBarState(tc.total, tc.style.Build()) s.current = tc.current s.trimSpace = tc.trim s.refill = tc.refill - s.completed = tc.total > 0 && tc.current >= tc.total + r, err := s.draw(s.newStatistics(tw)) + if err != nil { + t.Fatalf("tw: %d case %q draw error: %s", tw, tc.name, err.Error()) + } tmpBuf.Reset() - r, err := s.draw(newStatistics(tw, s)) - if err != nil { - t.FailNow() - } _, err = tmpBuf.ReadFrom(r) if err != nil { t.FailNow() @@ -1399,18 +1408,16 @@ 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 + ps := pState{reqWidth: tc.barWidth} + s := ps.makeBarState(tc.total, tc.style.Build()) s.current = tc.current s.trimSpace = tc.trim s.refill = tc.refill - s.completed = tc.total > 0 && tc.current >= tc.total + r, err := s.draw(s.newStatistics(tw)) + if err != nil { + t.Fatalf("tw: %d case %q draw error: %s", tw, tc.name, err.Error()) + } tmpBuf.Reset() - r, err := s.draw(newStatistics(tw, s)) - if err != nil { - t.FailNow() - } _, err = tmpBuf.ReadFrom(r) if err != nil { t.FailNow() @@ -1427,13 +1434,3 @@ } } } - -func newTestState(filler BarFiller) *bState { - bs := &bState{ - filler: filler, - } - 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 6d6ccfe..dd7e6a2 100644 --- a/example_test.go +++ b/example_test.go @@ -22,11 +22,9 @@ 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}), + decor.Name(name, decor.WC{C: decor.DindentRight | decor.DextraSpace}), // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done", - ), + decor.OnComplete(decor.AverageETA(decor.ET_STYLE_GO), "done"), ), mpb.AppendDecorators(decor.Percentage()), ) diff --git a/go.mod b/go.mod index 9da11e5..71de411 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ require ( github.com/VividCortex/ewma v1.2.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/mattn/go-runewidth v0.0.15 - golang.org/x/sys v0.11.0 + github.com/mattn/go-runewidth v0.0.16 + golang.org/x/sys v0.24.0 ) -require github.com/rivo/uniseg v0.4.4 // indirect +require github.com/rivo/uniseg v0.4.7 // indirect go 1.17 diff --git a/go.sum b/go.sum index 011a8cb..8066177 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,10 @@ github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/percentage.go b/internal/percentage.go index 4bc36f5..e25cf99 100644 --- a/internal/percentage.go +++ b/internal/percentage.go @@ -3,17 +3,20 @@ import "math" // Percentage is a helper function, to calculate percentage. -func Percentage(total, current int64, width uint) float64 { - if total <= 0 { +func Percentage(total, current, width uint) float64 { + if total == 0 { return 0 } if current >= total { return float64(width) } - return float64(int64(width)*current) / float64(total) + return float64(width*current) / float64(total) } // PercentageRound same as Percentage but with math.Round. func PercentageRound(total, current int64, width uint) float64 { - return math.Round(Percentage(total, current, width)) + if total < 0 || current < 0 { + return 0 + } + return math.Round(Percentage(uint(total), uint(current), width)) } diff --git a/internal/width.go b/internal/width.go index 7677e40..842e811 100644 --- a/internal/width.go +++ b/internal/width.go @@ -3,7 +3,7 @@ // CheckRequestedWidth checks that requested width doesn't overflow // available width func CheckRequestedWidth(requested, available int) int { - if requested < 1 || requested >= available { + if requested < 1 || requested > available { return available } return requested diff --git a/progress.go b/progress.go index f275be3..5c57eaf 100644 --- a/progress.go +++ b/progress.go @@ -14,12 +14,10 @@ "github.com/vbauerster/mpb/v8/decor" ) -const ( - defaultRefreshRate = 150 * time.Millisecond -) - -// DoneError represents an error when `*mpb.Progress` is done but its functionality is requested. -var DoneError = fmt.Errorf("%T instance can't be reused after it's done", (*Progress)(nil)) +const defaultRefreshRate = 150 * time.Millisecond + +// DoneError represents use after `(*Progress).Wait()` error. +var DoneError = fmt.Errorf("%T instance can't be reused after %[1]T.Wait()", (*Progress)(nil)) // Progress represents a container that renders one or more progress bars. type Progress struct { @@ -55,13 +53,13 @@ } // New creates new Progress container instance. It's not possible to -// reuse instance after (*Progress).Wait method has been called. +// reuse instance after `(*Progress).Wait` method has been called. func New(options ...ContainerOption) *Progress { return NewWithContext(context.Background(), options...) } // NewWithContext creates new Progress container instance with provided -// context. It's not possible to reuse instance after (*Progress).Wait +// context. It's not possible to reuse instance after `(*Progress).Wait` // method has been called. func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { if ctx == nil { @@ -74,8 +72,8 @@ dropS: make(chan struct{}), dropD: make(chan struct{}), renderReq: make(chan time.Time), + popPriority: math.MinInt32, refreshRate: defaultRefreshRate, - popPriority: math.MinInt32, queueBars: make(map[*Bar]*Bar), output: os.Stdout, debugOut: io.Discard, @@ -128,13 +126,15 @@ // New creates a bar by calling `Build` method on provided `BarFillerBuilder`. func (p *Progress) New(total int64, builder BarFillerBuilder, options ...BarOption) *Bar { + if builder == nil { + return p.MustAdd(total, nil, options...) + } return p.MustAdd(total, builder.Build(), options...) } // MustAdd creates a bar which renders itself by provided BarFiller. // If `total <= 0` triggering complete event by increment methods is -// disabled. Panics if *Progress instance is done, i.e. called after -// (*Progress).Wait(). +// disabled. Panics if called after `(*Progress).Wait()`. func (p *Progress) MustAdd(total int64, filler BarFiller, options ...BarOption) *Bar { bar, err := p.Add(total, filler, options...) if err != nil { @@ -145,17 +145,15 @@ // Add creates a bar which renders itself by provided BarFiller. // If `total <= 0` triggering complete event by increment methods -// is disabled. If *Progress instance is done, i.e. called after -// (*Progress).Wait(), return error == DoneError. +// is disabled. If called after `(*Progress).Wait()` then +// `(nil, DoneError)` is returned. func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) (*Bar, error) { if filler == nil { filler = NopStyle().Build() - } - type result struct { - bar *Bar - bs *bState - } - ch := make(chan result) + } else if f, ok := filler.(BarFillerFunc); ok && f == nil { + filler = NopStyle().Build() + } + ch := make(chan *Bar) select { case p.operateState <- func(ps *pState) { bs := ps.makeBarState(total, filler, options...) @@ -166,22 +164,9 @@ ps.hm.push(bar, true) } ps.idCount++ - ch <- result{bar, bs} + ch <- bar }: - res := <-ch - bar, bs := res.bar, res.bs - bar.TraverseDecorators(func(d decor.Decorator) { - if d, ok := d.(decor.AverageDecorator); ok { - bs.averageDecorators = append(bs.averageDecorators, d) - } - if d, ok := d.(decor.EwmaDecorator); ok { - bs.ewmaDecorators = append(bs.ewmaDecorators, d) - } - if d, ok := d.(decor.ShutdownListener); ok { - bs.shutdownListeners = append(bs.shutdownListeners, d) - } - }) - return bar, nil + return <-ch, nil case <-p.done: return nil, DoneError } @@ -192,7 +177,7 @@ select { case p.operateState <- func(s *pState) { s.hm.iter(iter, drop) }: for b := range iter { - if cb(b) { + if !cb(b) { close(drop) break } @@ -203,7 +188,7 @@ // UpdateBarPriority either immediately or lazy. // With lazy flag order is updated after the next refresh cycle. -// If you don't care about laziness just use *Bar.SetPriority(int). +// If you don't care about laziness just use `(*Bar).SetPriority(int)`. func (p *Progress) UpdateBarPriority(b *Bar, priority int, lazy bool) { if b == nil { return @@ -215,9 +200,9 @@ } // Write is implementation of io.Writer. -// Writing to `*mpb.Progress` will print lines above a running bar. +// Writing to `*Progress` will print lines above a running bar. // Writes aren't flushed immediately, but at next refresh cycle. -// If Write is called after `*mpb.Progress` is done, `mpb.DoneError` +// If called after `(*Progress).Wait()` then `(0, DoneError)` // is returned. func (p *Progress) Write(b []byte) (int, error) { type result struct { @@ -238,20 +223,19 @@ } // Wait waits for all bars to complete and finally shutdowns container. After -// this method has been called, there is no way to reuse (*Progress) instance. +// this method has been called, there is no way to reuse `*Progress` instance. func (p *Progress) Wait() { + p.bwg.Wait() + p.Shutdown() // wait for user wg, if any if p.uwg != nil { p.uwg.Wait() } - - p.bwg.Wait() - p.Shutdown() -} - -// Shutdown cancels any running bar immediately and then shutdowns (*Progress) +} + +// Shutdown cancels any running bar immediately and then shutdowns `*Progress` // instance. Normally this method shouldn't be called unless you know what you -// are doing. Proper way to shutdown is to call (*Progress).Wait() instead. +// are doing. Proper way to shutdown is to call `(*Progress).Wait()` instead. func (p *Progress) Shutdown() { p.cancel() p.pwg.Wait() @@ -259,34 +243,58 @@ func (p *Progress) serve(s *pState, cw *cwriter.Writer) { defer p.pwg.Done() - render := func() error { return s.render(cw) } var err error + var w *cwriter.Writer + renderReq := s.renderReq + operateState := p.operateState + interceptIO := p.interceptIO + + if s.delayRC != nil { + w = cwriter.New(io.Discard) + } else { + w, cw = cw, nil + } for { select { - case op := <-p.operateState: + case <-s.delayRC: + w, cw = cw, nil + s.delayRC = nil + case op := <-operateState: op(s) - case fn := <-p.interceptIO: - fn(cw) - case <-s.renderReq: - e := render() - if e != nil { + case fn := <-interceptIO: + fn(w) + case <-renderReq: + err = s.render(w) + if err != nil { + // (*pState).(autoRefreshListener|manualRefreshListener) may block + // if not launching following short lived goroutine + go func() { + for { + select { + case <-s.renderReq: + case <-p.done: + return + } + } + }() p.cancel() // cancel all bars - render = func() error { return nil } - err = e + renderReq = nil + operateState = nil + interceptIO = nil } case <-p.done: - update := make(chan bool) - for s.autoRefresh && err == nil { - s.hm.state(update) - if <-update { - err = render() - } else { - break - } - } if err != nil { _, _ = fmt.Fprintln(s.debugOut, err.Error()) + } else if s.autoRefresh { + update := make(chan bool) + for i := 0; i == 0 || <-update; i++ { + if err := s.render(w); err != nil { + _, _ = fmt.Fprintln(s.debugOut, err.Error()) + break + } + s.hm.state(update) + } } s.hm.end(s.shutdownNotifier) return @@ -294,10 +302,7 @@ } } -func (s pState) autoRefreshListener(done chan struct{}) { - if s.delayRC != nil { - <-s.delayRC - } +func (s *pState) autoRefreshListener(done chan struct{}) { ticker := time.NewTicker(s.refreshRate) defer ticker.Stop() for { @@ -311,7 +316,7 @@ } } -func (s pState) manualRefreshListener(done chan struct{}) { +func (s *pState) manualRefreshListener(done chan struct{}) { for { select { case x := <-s.manualRC: @@ -343,9 +348,9 @@ if s.reqWidth > 0 { width = s.reqWidth } else { - width = 100 - } - height = 100 + width = 80 + } + height = width } for b := range iter { @@ -357,7 +362,7 @@ func (s *pState) flush(cw *cwriter.Writer, height int) error { var wg sync.WaitGroup - defer wg.Wait() // waiting for all s.hm.push to complete + defer wg.Wait() // waiting for all s.push to complete var popCount int var rows []io.Reader @@ -381,40 +386,34 @@ _, _ = io.Copy(io.Discard, row) } } - if frame.shutdown != 0 && !frame.done { + + switch frame.shutdown { + case 1: + b.cancel() if qb, ok := s.queueBars[b]; ok { - b.cancel() delete(s.queueBars, b) qb.priority = b.priority wg.Add(1) - go func(b *Bar) { - s.hm.push(b, true) - wg.Done() - }(qb) + go s.push(&wg, qb, true) + } else if s.popCompleted && !frame.noPop { + b.priority = s.popPriority + s.popPriority++ + wg.Add(1) + go s.push(&wg, b, false) + } else if !frame.rmOnComplete { + wg.Add(1) + go s.push(&wg, b, false) + } + case 2: + if s.popCompleted && !frame.noPop { + popCount += usedRows continue } - if s.popCompleted && !frame.noPop { - switch frame.shutdown { - case 1: - b.priority = s.popPriority - s.popPriority++ - default: - b.cancel() - popCount += usedRows - continue - } - } else if frame.rmOnComplete { - b.cancel() - continue - } else { - b.cancel() - } - } - wg.Add(1) - go func(b *Bar) { - s.hm.push(b, false) - wg.Done() - }(b) + fallthrough + default: + wg.Add(1) + go s.push(&wg, b, false) + } } for i := len(rows) - 1; i >= 0; i-- { @@ -425,6 +424,11 @@ } return cw.Flush(len(rows) - popCount) +} + +func (s *pState) push(wg *sync.WaitGroup, b *Bar, sync bool) { + s.hm.push(b, sync) + wg.Done() } func (s pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState { @@ -436,6 +440,9 @@ filler: filler, renderReq: s.renderReq, autoRefresh: s.autoRefresh, + extender: func(_ decor.Statistics, rows ...io.Reader) ([]io.Reader, error) { + return rows, nil + }, } if total > 0 { @@ -448,9 +455,9 @@ } } - for i := 0; i < len(bs.buffers); i++ { - bs.buffers[i] = bytes.NewBuffer(make([]byte, 0, 512)) - } + bs.buffers[0] = bytes.NewBuffer(make([]byte, 0, 128)) // prepend + bs.buffers[1] = bytes.NewBuffer(make([]byte, 0, 128)) // append + bs.buffers[2] = bytes.NewBuffer(make([]byte, 0, 256)) // filler return bs } diff --git a/proxyreader.go b/proxyreader.go index b0e7720..8c324f8 100644 --- a/proxyreader.go +++ b/proxyreader.go @@ -69,28 +69,5 @@ if rc, ok := r.(io.ReadCloser); ok { return rc } - return toNopReadCloser(r) + return io.NopCloser(r) } - -func toNopReadCloser(r io.Reader) io.ReadCloser { - if _, ok := r.(io.WriterTo); ok { - return nopReadCloserWriterTo{r} - } - return nopReadCloser{r} -} - -type nopReadCloser struct { - io.Reader -} - -func (nopReadCloser) Close() error { return nil } - -type nopReadCloserWriterTo struct { - io.Reader -} - -func (nopReadCloserWriterTo) Close() error { return nil } - -func (c nopReadCloserWriterTo) WriteTo(w io.Writer) (int64, error) { - return c.Reader.(io.WriterTo).WriteTo(w) -}