New upstream version 8.8.2
Reinhard Tartler
1 year, 7 months ago
| 21 | 21 | os: [ubuntu-latest, macos-latest] |
| 22 | 22 | runs-on: ${{ matrix.os }} |
| 23 | 23 | steps: |
| 24 | - uses: actions/checkout@v3 | |
| 25 | - uses: actions/setup-go@v4 | |
| 24 | - uses: actions/checkout@v4 | |
| 25 | - uses: actions/setup-go@v5 | |
| 26 | 26 | with: |
| 27 | 27 | go-version: ${{ matrix.go-version }} |
| 28 | 28 | cache: false |
| 29 | - uses: golangci/golangci-lint-action@v3 | |
| 29 | - uses: golangci/golangci-lint-action@v6 | |
| 30 | 30 | with: |
| 31 | 31 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. |
| 32 | 32 | version: latest |
| 33 | # Optional: working directory, useful for monorepos | |
| 34 | # working-directory: somedir | |
| 35 | 33 | |
| 36 | 34 | # Optional: golangci-lint command line arguments. |
| 37 | 35 | # args: --issues-exit-code=0 |
| 5 | 5 | - v* |
| 6 | 6 | branches: |
| 7 | 7 | - master |
| 8 | - v* | |
| 8 | - main | |
| 9 | 9 | pull_request: |
| 10 | 10 | |
| 11 | 11 | permissions: |
| 20 | 20 | os: [ubuntu-latest, macos-latest, windows-latest] |
| 21 | 21 | runs-on: ${{ matrix.os }} |
| 22 | 22 | steps: |
| 23 | - uses: actions/checkout@v3 | |
| 24 | - uses: actions/setup-go@v4 | |
| 23 | - uses: actions/checkout@v4 | |
| 24 | - uses: actions/setup-go@v5 | |
| 25 | 25 | with: |
| 26 | 26 | go-version: ${{ matrix.go-version }} |
| 27 | 27 | - run: go test -race ./... |
| 41 | 41 | mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), |
| 42 | 42 | mpb.PrependDecorators( |
| 43 | 43 | // display our name with one space on the right |
| 44 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), | |
| 44 | decor.Name(name, decor.WC{C: decor.DindentRight | decor.DextraSpace}), | |
| 45 | 45 | // replace ETA decorator with "done" message, OnComplete event |
| 46 | decor.OnComplete( | |
| 47 | decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done", | |
| 48 | ), | |
| 46 | decor.OnComplete(decor.AverageETA(decor.ET_STYLE_GO), "done"), | |
| 49 | 47 | ), |
| 50 | 48 | mpb.AppendDecorators(decor.Percentage()), |
| 51 | 49 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | 4 | require ( |
| 5 | github.com/fatih/color v1.15.0 | |
| 6 | github.com/vbauerster/mpb/v8 v8.6.1 | |
| 5 | github.com/fatih/color v1.17.0 | |
| 6 | github.com/vbauerster/mpb/v8 v8.8.2 | |
| 7 | 7 | ) |
| 8 | 8 | |
| 9 | 9 | require ( |
| 10 | 10 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 11 | 11 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 12 | 12 | github.com/mattn/go-colorable v0.1.13 // indirect |
| 13 | github.com/mattn/go-isatty v0.0.19 // indirect | |
| 14 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 15 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 16 | golang.org/x/sys v0.11.0 // indirect | |
| 13 | github.com/mattn/go-isatty v0.0.20 // indirect | |
| 14 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 15 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 16 | golang.org/x/sys v0.24.0 // indirect | |
| 17 | 17 | ) |
| 24 | 24 | queue := make([]*mpb.Bar, 2) |
| 25 | 25 | queue[0] = p.AddBar(rand.Int63n(201)+100, |
| 26 | 26 | mpb.PrependDecorators( |
| 27 | decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), | |
| 27 | decor.Name(task, decor.WC{C: decor.DindentRight | decor.DextraSpace}), | |
| 28 | 28 | decor.Name("downloading", decor.WCSyncSpaceR), |
| 29 | 29 | decor.CountersNoUnit("%d / %d", decor.WCSyncWidth), |
| 30 | 30 | ), |
| 36 | 36 | mpb.BarQueueAfter(queue[0]), // this bar is queued |
| 37 | 37 | mpb.BarFillerClearOnComplete(), |
| 38 | 38 | mpb.PrependDecorators( |
| 39 | decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), | |
| 39 | decor.Name(task, decor.WC{C: decor.DindentRight | decor.DextraSpace}), | |
| 40 | 40 | decor.OnCompleteMeta( |
| 41 | 41 | decor.OnComplete( |
| 42 | 42 | decor.Meta(decor.Name("installing", decor.WCSyncSpaceR), toMetaFunc(red)), |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 0 | #!/bin/sh | |
| 1 | set -e | |
| 2 | ||
| 3 | for d in *; do | |
| 4 | [ ! -d "$d" ] && continue | |
| 5 | pushd "$d" >/dev/null 2>&1 | |
| 6 | go mod tidy | |
| 7 | popd >/dev/null 2>&1 | |
| 8 | done |
| 0 | #!/bin/sh | |
| 1 | ||
| 2 | for d in *; do | |
| 3 | [ ! -d "$d" ] && continue | |
| 4 | pushd "$d" >/dev/null 2>&1 | |
| 5 | go mod tidy | |
| 6 | popd >/dev/null 2>&1 | |
| 7 | done |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 2 | 2 | import ( |
| 3 | 3 | "crypto/rand" |
| 4 | 4 | "io" |
| 5 | "io/ioutil" | |
| 6 | 5 | "time" |
| 7 | 6 | |
| 8 | 7 | "github.com/vbauerster/mpb/v8" |
| 10 | 9 | ) |
| 11 | 10 | |
| 12 | 11 | func main() { |
| 13 | var total int64 = 1024 * 1024 * 500 | |
| 14 | reader := io.LimitReader(rand.Reader, total) | |
| 12 | var total int64 = 64 * 1024 * 1024 | |
| 13 | ||
| 14 | r, w := io.Pipe() | |
| 15 | ||
| 16 | go func() { | |
| 17 | for i := 0; i < 1024; i++ { | |
| 18 | _, _ = io.Copy(w, io.LimitReader(rand.Reader, 64*1024)) | |
| 19 | time.Sleep(time.Second / 10) | |
| 20 | } | |
| 21 | w.Close() | |
| 22 | }() | |
| 15 | 23 | |
| 16 | 24 | p := mpb.New( |
| 17 | 25 | mpb.WithWidth(60), |
| 31 | 39 | ) |
| 32 | 40 | |
| 33 | 41 | // create proxy reader |
| 34 | proxyReader := bar.ProxyReader(reader) | |
| 42 | proxyReader := bar.ProxyReader(r) | |
| 35 | 43 | defer proxyReader.Close() |
| 36 | 44 | |
| 37 | 45 | // copy from proxyReader, ignoring errors |
| 38 | _, _ = io.Copy(ioutil.Discard, proxyReader) | |
| 46 | _, _ = io.Copy(io.Discard, proxyReader) | |
| 39 | 47 | |
| 40 | 48 | p.Wait() |
| 41 | 49 | } |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 23 | 23 | ), |
| 24 | 24 | mpb.AppendDecorators( |
| 25 | 25 | decor.OnComplete(decor.Name(" "), ""), |
| 26 | decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 60), ""), | |
| 26 | decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 30), ""), | |
| 27 | 27 | ), |
| 28 | 28 | ) |
| 29 | 29 | // simulating some work |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 12 | 12 | |
| 13 | 13 | func main() { |
| 14 | 14 | total, numBars := 100, 2 |
| 15 | var wg sync.WaitGroup | |
| 16 | wg.Add(numBars) | |
| 15 | var bwg, qwg sync.WaitGroup | |
| 16 | bwg.Add(numBars) | |
| 17 | qwg.Add(1) | |
| 17 | 18 | done := make(chan interface{}) |
| 18 | p := mpb.New( | |
| 19 | mpb.WithWidth(64), | |
| 20 | mpb.WithWaitGroup(&wg), | |
| 21 | mpb.WithShutdownNotifier(done), | |
| 22 | ) | |
| 19 | p := mpb.New(mpb.WithWidth(64), mpb.WithShutdownNotifier(done), mpb.WithWaitGroup(&qwg)) | |
| 23 | 20 | |
| 24 | 21 | log.SetOutput(p) |
| 22 | ||
| 23 | go func() { | |
| 24 | defer qwg.Done() | |
| 25 | for { | |
| 26 | select { | |
| 27 | case <-done: | |
| 28 | // after done, underlying io.Writer returns mpb.DoneError | |
| 29 | // so following isn't printed | |
| 30 | log.Println("all done") | |
| 31 | return | |
| 32 | default: | |
| 33 | log.Println("waiting for done") | |
| 34 | time.Sleep(150 * time.Millisecond) | |
| 35 | } | |
| 36 | } | |
| 37 | }() | |
| 38 | ||
| 39 | nopBar := p.MustAdd(0, nil) | |
| 25 | 40 | |
| 26 | 41 | for i := 0; i < numBars; i++ { |
| 27 | 42 | name := fmt.Sprintf("Bar#%d:", i) |
| 38 | 53 | ) |
| 39 | 54 | // simulating some work |
| 40 | 55 | go func() { |
| 41 | defer wg.Done() | |
| 56 | defer bwg.Done() | |
| 42 | 57 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) |
| 43 | 58 | max := 100 * time.Millisecond |
| 44 | 59 | for i := 0; i < total; i++ { |
| 53 | 68 | }() |
| 54 | 69 | } |
| 55 | 70 | |
| 56 | var qwg sync.WaitGroup | |
| 57 | qwg.Add(1) | |
| 58 | go func() { | |
| 59 | quit: | |
| 60 | for { | |
| 61 | select { | |
| 62 | case <-done: | |
| 63 | // after done, underlying io.Writer returns mpb.DoneError | |
| 64 | // so following isn't printed | |
| 65 | log.Println("all done") | |
| 66 | break quit | |
| 67 | default: | |
| 68 | log.Println("waiting for done") | |
| 69 | time.Sleep(100 * time.Millisecond) | |
| 70 | } | |
| 71 | } | |
| 72 | qwg.Done() | |
| 73 | }() | |
| 71 | bwg.Wait() | |
| 72 | log.Println("completing nop bar") | |
| 73 | nopBar.EnableTriggerComplete() | |
| 74 | 74 | |
| 75 | 75 | p.Wait() |
| 76 | qwg.Wait() | |
| 77 | 76 | } |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 19 | 19 | mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), |
| 20 | 20 | mpb.PrependDecorators( |
| 21 | 21 | // display our name with one space on the right |
| 22 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), | |
| 22 | decor.Name(name, decor.WC{C: decor.DindentRight | decor.DextraSpace}), | |
| 23 | 23 | // replace ETA decorator with "done" message, OnComplete event |
| 24 | decor.OnComplete( | |
| 25 | decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done", | |
| 26 | ), | |
| 24 | decor.OnComplete(decor.AverageETA(decor.ET_STYLE_GO), "done"), | |
| 27 | 25 | ), |
| 28 | 26 | mpb.AppendDecorators(decor.Percentage()), |
| 29 | 27 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 3 | 3 | |
| 4 | 4 | require ( |
| 5 | 5 | github.com/pkg/profile v1.7.0 |
| 6 | github.com/vbauerster/mpb/v8 v8.6.1 | |
| 6 | github.com/vbauerster/mpb/v8 v8.8.2 | |
| 7 | 7 | ) |
| 8 | 8 | |
| 9 | 9 | require ( |
| 10 | 10 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 11 | 11 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 12 | github.com/felixge/fgprof v0.9.3 // indirect | |
| 13 | github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect | |
| 14 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 15 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 16 | golang.org/x/sys v0.11.0 // indirect | |
| 12 | github.com/felixge/fgprof v0.9.4 // indirect | |
| 13 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect | |
| 14 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 15 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 16 | golang.org/x/sys v0.24.0 // indirect | |
| 17 | 17 | ) |
| 13 | 13 | ) |
| 14 | 14 | |
| 15 | 15 | const ( |
| 16 | totalBars = 32 | |
| 16 | totalBars = 42 | |
| 17 | 17 | ) |
| 18 | 18 | |
| 19 | 19 | var proftype = flag.String("prof", "", "profile type (cpu, mem)") |
| 37 | 37 | bar := p.AddBar(int64(total), |
| 38 | 38 | mpb.PrependDecorators( |
| 39 | 39 | decor.Name(name, decor.WCSyncWidthR), |
| 40 | decor.Elapsed(decor.ET_STYLE_GO, decor.WCSyncWidth), | |
| 40 | decor.OnComplete(decor.Percentage(decor.WCSyncWidth), "done"), | |
| 41 | 41 | ), |
| 42 | 42 | mpb.AppendDecorators( |
| 43 | decor.OnComplete( | |
| 44 | decor.Percentage(decor.WC{W: 5}), "done", | |
| 45 | ), | |
| 43 | decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 30, decor.WCSyncWidth), ""), | |
| 44 | decor.EwmaSpeed(decor.SizeB1024(0), "", 30, decor.WCSyncSpace), | |
| 46 | 45 | ), |
| 47 | 46 | ) |
| 48 | 47 | |
| 50 | 49 | defer wg.Done() |
| 51 | 50 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) |
| 52 | 51 | max := 100 * time.Millisecond |
| 53 | for !bar.Completed() { | |
| 52 | for bar.IsRunning() { | |
| 53 | start := time.Now() | |
| 54 | 54 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) |
| 55 | bar.Increment() | |
| 55 | bar.EwmaIncrement(time.Since(start)) | |
| 56 | 56 | } |
| 57 | 57 | }() |
| 58 | 58 | } |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require ( | |
| 5 | github.com/mattn/go-runewidth v0.0.15 | |
| 6 | github.com/vbauerster/mpb/v8 v8.6.1 | |
| 7 | ) | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 8 | 5 | |
| 9 | 6 | require ( |
| 10 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 11 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 12 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 13 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 14 | 12 | ) |
| 2 | 2 | import ( |
| 3 | 3 | "errors" |
| 4 | 4 | "fmt" |
| 5 | "io" | |
| 5 | "math" | |
| 6 | 6 | "math/rand" |
| 7 | 7 | "sync" |
| 8 | 8 | "time" |
| 9 | 9 | |
| 10 | "github.com/mattn/go-runewidth" | |
| 11 | 10 | "github.com/vbauerster/mpb/v8" |
| 12 | 11 | "github.com/vbauerster/mpb/v8/decor" |
| 13 | 12 | ) |
| 15 | 14 | func main() { |
| 16 | 15 | p := mpb.New() |
| 17 | 16 | |
| 18 | total := 100 | |
| 19 | msgCh := make(chan string) | |
| 20 | resumeCh := make(chan struct{}) | |
| 21 | nextCh := make(chan struct{}, 1) | |
| 22 | ew := &errorWrapper{} | |
| 17 | total, numBars := 100, 3 | |
| 18 | err := new(errorWrapper) | |
| 23 | 19 | timer := time.AfterFunc(2*time.Second, func() { |
| 24 | ew.reset(errors.New("timeout")) | |
| 20 | err.set(errors.New("timeout"), rand.Intn(numBars)) | |
| 25 | 21 | }) |
| 26 | 22 | defer timer.Stop() |
| 27 | bar := p.AddBar(int64(total), | |
| 28 | mpb.BarFillerMiddleware(func(base mpb.BarFiller) mpb.BarFiller { | |
| 29 | var msg *string | |
| 30 | var times int | |
| 31 | return mpb.BarFillerFunc(func(w io.Writer, st decor.Statistics) error { | |
| 32 | if msg == nil { | |
| 33 | select { | |
| 34 | case m := <-msgCh: | |
| 35 | msg = &m | |
| 36 | times = 10 | |
| 37 | nextCh <- struct{}{} | |
| 38 | default: | |
| 39 | } | |
| 40 | return base.Fill(w, st) | |
| 23 | ||
| 24 | for i := 0; i < numBars; i++ { | |
| 25 | msgCh := make(chan string, 1) | |
| 26 | bar := p.AddBar(int64(total), | |
| 27 | mpb.PrependDecorators(newTitleDecorator(fmt.Sprintf("Bar#%d:", i), msgCh, 16)), | |
| 28 | mpb.AppendDecorators(decor.Percentage(decor.WCSyncWidth)), | |
| 29 | ) | |
| 30 | // simulating some work | |
| 31 | barID := i | |
| 32 | go func() { | |
| 33 | max := 100 * time.Millisecond | |
| 34 | for i := 0; i < total; i++ { | |
| 35 | if err.check(barID) { | |
| 36 | msgCh <- fmt.Sprintf("%s at %d, retrying...", err.Error(), i) | |
| 37 | err.reset() | |
| 38 | i-- | |
| 39 | bar.SetRefill(int64(i)) | |
| 40 | continue | |
| 41 | 41 | } |
| 42 | switch { | |
| 43 | case times == 0, st.Completed, st.Aborted: | |
| 44 | defer func() { | |
| 45 | msg = nil | |
| 46 | }() | |
| 47 | resumeCh <- struct{}{} | |
| 48 | default: | |
| 49 | times-- | |
| 50 | } | |
| 51 | _, err := io.WriteString(w, runewidth.Truncate(*msg, st.AvailableWidth, "…")) | |
| 52 | nextCh <- struct{}{} | |
| 53 | return err | |
| 54 | }) | |
| 55 | }), | |
| 56 | mpb.PrependDecorators(decor.Name("my bar:")), | |
| 57 | mpb.AppendDecorators(newCustomPercentage(nextCh)), | |
| 58 | ) | |
| 59 | // simulating some work | |
| 60 | go func() { | |
| 61 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 62 | max := 100 * time.Millisecond | |
| 63 | for i := 0; i < total; i++ { | |
| 64 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) | |
| 65 | if ew.isErr() { | |
| 66 | msgCh <- fmt.Sprintf("%s at %d, retrying...", ew.Error(), i) | |
| 67 | i-- | |
| 68 | bar.SetRefill(int64(i)) | |
| 69 | ew.reset(nil) | |
| 70 | <-resumeCh | |
| 71 | continue | |
| 42 | time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) | |
| 43 | bar.Increment() | |
| 72 | 44 | } |
| 73 | bar.Increment() | |
| 74 | } | |
| 75 | }() | |
| 45 | }() | |
| 46 | } | |
| 76 | 47 | |
| 77 | 48 | p.Wait() |
| 78 | 49 | } |
| 79 | 50 | |
| 80 | 51 | type errorWrapper struct { |
| 81 | 52 | sync.RWMutex |
| 82 | err error | |
| 53 | err error | |
| 54 | barID int | |
| 83 | 55 | } |
| 84 | 56 | |
| 85 | 57 | func (ew *errorWrapper) Error() string { |
| 88 | 60 | return ew.err.Error() |
| 89 | 61 | } |
| 90 | 62 | |
| 91 | func (ew *errorWrapper) isErr() bool { | |
| 63 | func (ew *errorWrapper) check(barID int) bool { | |
| 92 | 64 | ew.RLock() |
| 93 | 65 | defer ew.RUnlock() |
| 94 | return ew.err != nil | |
| 66 | return ew.err != nil && ew.barID == barID | |
| 95 | 67 | } |
| 96 | 68 | |
| 97 | func (ew *errorWrapper) reset(err error) { | |
| 69 | func (ew *errorWrapper) set(err error, barID int) { | |
| 98 | 70 | ew.Lock() |
| 99 | 71 | ew.err = err |
| 72 | ew.barID = barID | |
| 100 | 73 | ew.Unlock() |
| 101 | 74 | } |
| 102 | 75 | |
| 103 | type percentage struct { | |
| 104 | decor.Decorator | |
| 105 | suspend <-chan struct{} | |
| 76 | func (ew *errorWrapper) reset() { | |
| 77 | ew.Lock() | |
| 78 | ew.err = nil | |
| 79 | ew.Unlock() | |
| 106 | 80 | } |
| 107 | 81 | |
| 108 | func (d percentage) Decor(s decor.Statistics) (string, int) { | |
| 109 | select { | |
| 110 | case <-d.suspend: | |
| 111 | return d.Format("") | |
| 112 | default: | |
| 113 | return d.Decorator.Decor(s) | |
| 82 | type title struct { | |
| 83 | decor.Decorator | |
| 84 | name string | |
| 85 | msgCh <-chan string | |
| 86 | msg string | |
| 87 | count int | |
| 88 | limit int | |
| 89 | } | |
| 90 | ||
| 91 | func (d *title) Decor(stat decor.Statistics) (string, int) { | |
| 92 | if d.count == 0 { | |
| 93 | select { | |
| 94 | case msg := <-d.msgCh: | |
| 95 | d.count = d.limit | |
| 96 | d.msg = msg | |
| 97 | default: | |
| 98 | return d.Decorator.Decor(stat) | |
| 99 | } | |
| 100 | } | |
| 101 | d.count-- | |
| 102 | _, _ = d.Format("") | |
| 103 | return fmt.Sprintf("%s %s", d.name, d.msg), math.MaxInt | |
| 104 | } | |
| 105 | ||
| 106 | func newTitleDecorator(name string, msgCh <-chan string, limit int) decor.Decorator { | |
| 107 | return &title{ | |
| 108 | Decorator: decor.Name(name), | |
| 109 | name: name, | |
| 110 | msgCh: msgCh, | |
| 111 | limit: limit, | |
| 114 | 112 | } |
| 115 | 113 | } |
| 116 | ||
| 117 | func newCustomPercentage(nextCh <-chan struct{}) decor.Decorator { | |
| 118 | return percentage{ | |
| 119 | Decorator: decor.Percentage(), | |
| 120 | suspend: nextCh, | |
| 121 | } | |
| 122 | } | |
| 1 | 1 | |
| 2 | 2 | go 1.17 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.6.1 | |
| 4 | require github.com/vbauerster/mpb/v8 v8.8.2 | |
| 5 | 5 | |
| 6 | 6 | require ( |
| 7 | 7 | github.com/VividCortex/ewma v1.2.0 // indirect |
| 8 | 8 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect |
| 9 | github.com/mattn/go-runewidth v0.0.15 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 11 | golang.org/x/sys v0.11.0 // indirect | |
| 9 | github.com/mattn/go-runewidth v0.0.16 // indirect | |
| 10 | github.com/rivo/uniseg v0.4.7 // indirect | |
| 11 | golang.org/x/sys v0.24.0 // indirect | |
| 12 | 12 | ) |
| 18 | 18 | priority int // used by heap |
| 19 | 19 | frameCh chan *renderFrame |
| 20 | 20 | operateState chan func(*bState) |
| 21 | done chan struct{} | |
| 22 | 21 | container *Progress |
| 23 | 22 | bs *bState |
| 23 | bsOk chan struct{} | |
| 24 | ctx context.Context | |
| 24 | 25 | cancel func() |
| 25 | 26 | } |
| 26 | 27 | |
| 27 | 28 | type syncTable [2][]chan int |
| 28 | type extenderFunc func([]io.Reader, decor.Statistics) ([]io.Reader, error) | |
| 29 | type extenderFunc func(decor.Statistics, ...io.Reader) ([]io.Reader, error) | |
| 29 | 30 | |
| 30 | 31 | // bState is actual bar's state. |
| 31 | 32 | type bState struct { |
| 32 | id int | |
| 33 | priority int | |
| 34 | reqWidth int | |
| 35 | shutdown int | |
| 36 | total int64 | |
| 37 | current int64 | |
| 38 | refill int64 | |
| 39 | trimSpace bool | |
| 40 | completed bool | |
| 41 | aborted bool | |
| 42 | triggerComplete bool | |
| 43 | rmOnComplete bool | |
| 44 | noPop bool | |
| 45 | autoRefresh bool | |
| 46 | aDecorators []decor.Decorator | |
| 47 | pDecorators []decor.Decorator | |
| 48 | averageDecorators []decor.AverageDecorator | |
| 49 | ewmaDecorators []decor.EwmaDecorator | |
| 50 | shutdownListeners []decor.ShutdownListener | |
| 51 | buffers [3]*bytes.Buffer | |
| 52 | filler BarFiller | |
| 53 | extender extenderFunc | |
| 54 | renderReq chan<- time.Time | |
| 55 | waitBar *Bar // key for (*pState).queueBars | |
| 33 | id int | |
| 34 | priority int | |
| 35 | reqWidth int | |
| 36 | shutdown int | |
| 37 | total int64 | |
| 38 | current int64 | |
| 39 | refill int64 | |
| 40 | trimSpace bool | |
| 41 | aborted bool | |
| 42 | triggerComplete bool | |
| 43 | rmOnComplete bool | |
| 44 | noPop bool | |
| 45 | autoRefresh bool | |
| 46 | buffers [3]*bytes.Buffer | |
| 47 | decorators [2][]decor.Decorator | |
| 48 | ewmaDecorators []decor.EwmaDecorator | |
| 49 | filler BarFiller | |
| 50 | extender extenderFunc | |
| 51 | renderReq chan<- time.Time | |
| 52 | waitBar *Bar // key for (*pState).queueBars | |
| 56 | 53 | } |
| 57 | 54 | |
| 58 | 55 | type renderFrame struct { |
| 60 | 57 | shutdown int |
| 61 | 58 | rmOnComplete bool |
| 62 | 59 | noPop bool |
| 63 | done bool | |
| 64 | 60 | err error |
| 65 | 61 | } |
| 66 | 62 | |
| 71 | 67 | priority: bs.priority, |
| 72 | 68 | frameCh: make(chan *renderFrame, 1), |
| 73 | 69 | operateState: make(chan func(*bState)), |
| 74 | done: make(chan struct{}), | |
| 70 | bsOk: make(chan struct{}), | |
| 75 | 71 | container: container, |
| 72 | ctx: ctx, | |
| 76 | 73 | cancel: cancel, |
| 77 | 74 | } |
| 78 | 75 | |
| 79 | 76 | container.bwg.Add(1) |
| 80 | go bar.serve(ctx, bs) | |
| 77 | go bar.serve(bs) | |
| 81 | 78 | return bar |
| 82 | 79 | } |
| 83 | 80 | |
| 84 | // ProxyReader wraps io.Reader with metrics required for progress tracking. | |
| 85 | // If `r` is 'unknown total/size' reader it's mandatory to call | |
| 86 | // (*Bar).SetTotal(-1, true) method after (io.Reader).Read returns io.EOF. | |
| 87 | // If bar is already completed or aborted, returns nil. | |
| 81 | // ProxyReader wraps io.Reader with metrics required for progress | |
| 82 | // tracking. If `r` is 'unknown total/size' reader it's mandatory | |
| 83 | // to call `(*Bar).SetTotal(-1, true)` after the wrapper returns | |
| 84 | // `io.EOF`. If bar is already completed or aborted, returns nil. | |
| 88 | 85 | // Panics if `r` is nil. |
| 89 | 86 | func (b *Bar) ProxyReader(r io.Reader) io.ReadCloser { |
| 90 | 87 | if r == nil { |
| 91 | 88 | panic("expected non nil io.Reader") |
| 92 | 89 | } |
| 93 | result := make(chan bool) | |
| 94 | select { | |
| 95 | case b.operateState <- func(s *bState) { result <- len(s.ewmaDecorators) != 0 }: | |
| 96 | return newProxyReader(r, b, <-result) | |
| 97 | case <-b.done: | |
| 90 | result := make(chan io.ReadCloser) | |
| 91 | select { | |
| 92 | case b.operateState <- func(s *bState) { | |
| 93 | result <- newProxyReader(r, b, len(s.ewmaDecorators) != 0) | |
| 94 | }: | |
| 95 | return <-result | |
| 96 | case <-b.ctx.Done(): | |
| 98 | 97 | return nil |
| 99 | 98 | } |
| 100 | 99 | } |
| 106 | 105 | if w == nil { |
| 107 | 106 | panic("expected non nil io.Writer") |
| 108 | 107 | } |
| 109 | result := make(chan bool) | |
| 110 | select { | |
| 111 | case b.operateState <- func(s *bState) { result <- len(s.ewmaDecorators) != 0 }: | |
| 112 | return newProxyWriter(w, b, <-result) | |
| 113 | case <-b.done: | |
| 108 | result := make(chan io.WriteCloser) | |
| 109 | select { | |
| 110 | case b.operateState <- func(s *bState) { | |
| 111 | result <- newProxyWriter(w, b, len(s.ewmaDecorators) != 0) | |
| 112 | }: | |
| 113 | return <-result | |
| 114 | case <-b.ctx.Done(): | |
| 114 | 115 | return nil |
| 115 | 116 | } |
| 116 | 117 | } |
| 121 | 122 | select { |
| 122 | 123 | case b.operateState <- func(s *bState) { result <- s.id }: |
| 123 | 124 | return <-result |
| 124 | case <-b.done: | |
| 125 | case <-b.bsOk: | |
| 125 | 126 | return b.bs.id |
| 126 | 127 | } |
| 127 | 128 | } |
| 132 | 133 | select { |
| 133 | 134 | case b.operateState <- func(s *bState) { result <- s.current }: |
| 134 | 135 | return <-result |
| 135 | case <-b.done: | |
| 136 | case <-b.bsOk: | |
| 136 | 137 | return b.bs.current |
| 137 | 138 | } |
| 138 | 139 | } |
| 150 | 151 | s.refill = s.current |
| 151 | 152 | } |
| 152 | 153 | }: |
| 153 | case <-b.done: | |
| 154 | } | |
| 155 | } | |
| 156 | ||
| 157 | // TraverseDecorators traverses all available decorators and calls cb func on each. | |
| 154 | case <-b.ctx.Done(): | |
| 155 | } | |
| 156 | } | |
| 157 | ||
| 158 | // TraverseDecorators traverses available decorators and calls cb func | |
| 159 | // on each in a new goroutine. Decorators implementing decor.Wrapper | |
| 160 | // interface are unwrapped first. | |
| 158 | 161 | func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) { |
| 159 | iter := make(chan decor.Decorator) | |
| 160 | select { | |
| 161 | case b.operateState <- func(s *bState) { | |
| 162 | for _, decorators := range [][]decor.Decorator{ | |
| 163 | s.pDecorators, | |
| 164 | s.aDecorators, | |
| 165 | } { | |
| 162 | select { | |
| 163 | case b.operateState <- func(s *bState) { | |
| 164 | var wg sync.WaitGroup | |
| 165 | for _, decorators := range s.decorators { | |
| 166 | wg.Add(len(decorators)) | |
| 166 | 167 | for _, d := range decorators { |
| 167 | iter <- d | |
| 168 | d := d | |
| 169 | go func() { | |
| 170 | cb(unwrap(d)) | |
| 171 | wg.Done() | |
| 172 | }() | |
| 168 | 173 | } |
| 169 | 174 | } |
| 170 | close(iter) | |
| 171 | }: | |
| 172 | for d := range iter { | |
| 173 | cb(unwrap(d)) | |
| 174 | } | |
| 175 | case <-b.done: | |
| 176 | } | |
| 177 | } | |
| 178 | ||
| 179 | // EnableTriggerComplete enables triggering complete event. It's | |
| 180 | // effective only for bars which were constructed with `total <= 0` and | |
| 181 | // after total has been set with (*Bar).SetTotal(int64, false). If bar | |
| 182 | // has been incremented to the total, complete event is triggered right | |
| 183 | // away. | |
| 175 | wg.Wait() | |
| 176 | }: | |
| 177 | case <-b.ctx.Done(): | |
| 178 | } | |
| 179 | } | |
| 180 | ||
| 181 | // EnableTriggerComplete enables triggering complete event. It's effective | |
| 182 | // only for bars which were constructed with `total <= 0`. If `curren >= total` | |
| 183 | // at the moment of call, complete event is triggered right away. | |
| 184 | 184 | func (b *Bar) EnableTriggerComplete() { |
| 185 | 185 | select { |
| 186 | 186 | case b.operateState <- func(s *bState) { |
| 187 | if s.triggerComplete || s.total <= 0 { | |
| 187 | if s.triggerComplete { | |
| 188 | 188 | return |
| 189 | 189 | } |
| 190 | 190 | if s.current >= s.total { |
| 191 | 191 | s.current = s.total |
| 192 | s.completed = true | |
| 193 | b.triggerCompletion(s) | |
| 192 | s.triggerCompletion(b) | |
| 194 | 193 | } else { |
| 195 | 194 | s.triggerComplete = true |
| 196 | 195 | } |
| 197 | 196 | }: |
| 198 | case <-b.done: | |
| 199 | } | |
| 200 | } | |
| 201 | ||
| 202 | // SetTotal sets total to an arbitrary value. It's effective only for | |
| 203 | // bar which was constructed with `total <= 0`. Setting total to negative | |
| 204 | // value is equivalent to (*Bar).SetTotal((*Bar).Current(), bool) but faster. | |
| 205 | // If triggerCompletion is true, total value is set to current and | |
| 206 | // complete event is triggered right away. | |
| 207 | func (b *Bar) SetTotal(total int64, triggerCompletion bool) { | |
| 197 | case <-b.ctx.Done(): | |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | // SetTotal sets total to an arbitrary value. It's effective only for bar | |
| 202 | // which was constructed with `total <= 0`. Setting total to negative value | |
| 203 | // is equivalent to `(*Bar).SetTotal((*Bar).Current(), bool)` but faster. | |
| 204 | // If `complete` is true complete event is triggered right away. | |
| 205 | // Calling `(*Bar).EnableTriggerComplete` makes this one no operational. | |
| 206 | func (b *Bar) SetTotal(total int64, complete bool) { | |
| 208 | 207 | select { |
| 209 | 208 | case b.operateState <- func(s *bState) { |
| 210 | 209 | if s.triggerComplete { |
| 215 | 214 | } else { |
| 216 | 215 | s.total = total |
| 217 | 216 | } |
| 218 | if triggerCompletion { | |
| 217 | if complete { | |
| 219 | 218 | s.current = s.total |
| 220 | s.completed = true | |
| 221 | b.triggerCompletion(s) | |
| 222 | } | |
| 223 | }: | |
| 224 | case <-b.done: | |
| 219 | s.triggerCompletion(b) | |
| 220 | } | |
| 221 | }: | |
| 222 | case <-b.ctx.Done(): | |
| 225 | 223 | } |
| 226 | 224 | } |
| 227 | 225 | |
| 235 | 233 | s.current = current |
| 236 | 234 | if s.triggerComplete && s.current >= s.total { |
| 237 | 235 | s.current = s.total |
| 238 | s.completed = true | |
| 239 | b.triggerCompletion(s) | |
| 240 | } | |
| 241 | }: | |
| 242 | case <-b.done: | |
| 236 | s.triggerCompletion(b) | |
| 237 | } | |
| 238 | }: | |
| 239 | case <-b.ctx.Done(): | |
| 240 | } | |
| 241 | } | |
| 242 | ||
| 243 | // Increment is a shorthand for b.IncrInt64(1). | |
| 244 | func (b *Bar) Increment() { | |
| 245 | b.IncrInt64(1) | |
| 246 | } | |
| 247 | ||
| 248 | // IncrBy is a shorthand for b.IncrInt64(int64(n)). | |
| 249 | func (b *Bar) IncrBy(n int) { | |
| 250 | b.IncrInt64(int64(n)) | |
| 251 | } | |
| 252 | ||
| 253 | // IncrInt64 increments progress by amount of n. | |
| 254 | func (b *Bar) IncrInt64(n int64) { | |
| 255 | select { | |
| 256 | case b.operateState <- func(s *bState) { | |
| 257 | s.current += n | |
| 258 | if s.triggerComplete && s.current >= s.total { | |
| 259 | s.current = s.total | |
| 260 | s.triggerCompletion(b) | |
| 261 | } | |
| 262 | }: | |
| 263 | case <-b.ctx.Done(): | |
| 264 | } | |
| 265 | } | |
| 266 | ||
| 267 | // EwmaIncrement is a shorthand for b.EwmaIncrInt64(1, iterDur). | |
| 268 | func (b *Bar) EwmaIncrement(iterDur time.Duration) { | |
| 269 | b.EwmaIncrInt64(1, iterDur) | |
| 270 | } | |
| 271 | ||
| 272 | // EwmaIncrBy is a shorthand for b.EwmaIncrInt64(int64(n), iterDur). | |
| 273 | func (b *Bar) EwmaIncrBy(n int, iterDur time.Duration) { | |
| 274 | b.EwmaIncrInt64(int64(n), iterDur) | |
| 275 | } | |
| 276 | ||
| 277 | // EwmaIncrInt64 increments progress by amount of n and updates EWMA based | |
| 278 | // decorators by dur of a single iteration. | |
| 279 | func (b *Bar) EwmaIncrInt64(n int64, iterDur time.Duration) { | |
| 280 | select { | |
| 281 | case b.operateState <- func(s *bState) { | |
| 282 | var wg sync.WaitGroup | |
| 283 | wg.Add(len(s.ewmaDecorators)) | |
| 284 | for _, d := range s.ewmaDecorators { | |
| 285 | d := d | |
| 286 | go func() { | |
| 287 | d.EwmaUpdate(n, iterDur) | |
| 288 | wg.Done() | |
| 289 | }() | |
| 290 | } | |
| 291 | s.current += n | |
| 292 | if s.triggerComplete && s.current >= s.total { | |
| 293 | s.current = s.total | |
| 294 | s.triggerCompletion(b) | |
| 295 | } | |
| 296 | wg.Wait() | |
| 297 | }: | |
| 298 | case <-b.ctx.Done(): | |
| 243 | 299 | } |
| 244 | 300 | } |
| 245 | 301 | |
| 251 | 307 | } |
| 252 | 308 | select { |
| 253 | 309 | case b.operateState <- func(s *bState) { |
| 254 | if n := current - s.current; n > 0 { | |
| 255 | s.decoratorEwmaUpdate(n, iterDur) | |
| 310 | n := current - s.current | |
| 311 | var wg sync.WaitGroup | |
| 312 | wg.Add(len(s.ewmaDecorators)) | |
| 313 | for _, d := range s.ewmaDecorators { | |
| 314 | d := d | |
| 315 | go func() { | |
| 316 | d.EwmaUpdate(n, iterDur) | |
| 317 | wg.Done() | |
| 318 | }() | |
| 256 | 319 | } |
| 257 | 320 | s.current = current |
| 258 | 321 | if s.triggerComplete && s.current >= s.total { |
| 259 | 322 | s.current = s.total |
| 260 | s.completed = true | |
| 261 | b.triggerCompletion(s) | |
| 262 | } | |
| 263 | }: | |
| 264 | case <-b.done: | |
| 265 | } | |
| 266 | } | |
| 267 | ||
| 268 | // Increment is a shorthand for b.IncrInt64(1). | |
| 269 | func (b *Bar) Increment() { | |
| 270 | b.IncrInt64(1) | |
| 271 | } | |
| 272 | ||
| 273 | // IncrBy is a shorthand for b.IncrInt64(int64(n)). | |
| 274 | func (b *Bar) IncrBy(n int) { | |
| 275 | b.IncrInt64(int64(n)) | |
| 276 | } | |
| 277 | ||
| 278 | // IncrInt64 increments progress by amount of n. | |
| 279 | func (b *Bar) IncrInt64(n int64) { | |
| 280 | if n <= 0 { | |
| 281 | return | |
| 282 | } | |
| 283 | select { | |
| 284 | case b.operateState <- func(s *bState) { | |
| 285 | s.current += n | |
| 286 | if s.triggerComplete && s.current >= s.total { | |
| 287 | s.current = s.total | |
| 288 | s.completed = true | |
| 289 | b.triggerCompletion(s) | |
| 290 | } | |
| 291 | }: | |
| 292 | case <-b.done: | |
| 293 | } | |
| 294 | } | |
| 295 | ||
| 296 | // EwmaIncrement is a shorthand for b.EwmaIncrInt64(1, iterDur). | |
| 297 | func (b *Bar) EwmaIncrement(iterDur time.Duration) { | |
| 298 | b.EwmaIncrInt64(1, iterDur) | |
| 299 | } | |
| 300 | ||
| 301 | // EwmaIncrBy is a shorthand for b.EwmaIncrInt64(int64(n), iterDur). | |
| 302 | func (b *Bar) EwmaIncrBy(n int, iterDur time.Duration) { | |
| 303 | b.EwmaIncrInt64(int64(n), iterDur) | |
| 304 | } | |
| 305 | ||
| 306 | // EwmaIncrInt64 increments progress by amount of n and updates EWMA based | |
| 307 | // decorators by dur of a single iteration. | |
| 308 | func (b *Bar) EwmaIncrInt64(n int64, iterDur time.Duration) { | |
| 309 | if n <= 0 { | |
| 310 | return | |
| 311 | } | |
| 312 | select { | |
| 313 | case b.operateState <- func(s *bState) { | |
| 314 | s.decoratorEwmaUpdate(n, iterDur) | |
| 315 | s.current += n | |
| 316 | if s.triggerComplete && s.current >= s.total { | |
| 317 | s.current = s.total | |
| 318 | s.completed = true | |
| 319 | b.triggerCompletion(s) | |
| 320 | } | |
| 321 | }: | |
| 322 | case <-b.done: | |
| 323 | } | |
| 324 | } | |
| 325 | ||
| 326 | // DecoratorAverageAdjust adjusts all average based decorators. Call | |
| 327 | // if you need to adjust start time of all average based decorators | |
| 328 | // or after progress resume. | |
| 323 | s.triggerCompletion(b) | |
| 324 | } | |
| 325 | wg.Wait() | |
| 326 | }: | |
| 327 | case <-b.ctx.Done(): | |
| 328 | } | |
| 329 | } | |
| 330 | ||
| 331 | // DecoratorAverageAdjust adjusts decorators implementing decor.AverageDecorator interface. | |
| 332 | // Call if there is need to set start time after decorators have been constructed. | |
| 329 | 333 | func (b *Bar) DecoratorAverageAdjust(start time.Time) { |
| 330 | select { | |
| 331 | case b.operateState <- func(s *bState) { s.decoratorAverageAdjust(start) }: | |
| 332 | case <-b.done: | |
| 333 | } | |
| 334 | b.TraverseDecorators(func(d decor.Decorator) { | |
| 335 | if d, ok := d.(decor.AverageDecorator); ok { | |
| 336 | d.AverageAdjust(start) | |
| 337 | } | |
| 338 | }) | |
| 334 | 339 | } |
| 335 | 340 | |
| 336 | 341 | // SetPriority changes bar's order among multiple bars. Zero is highest |
| 343 | 348 | // Abort interrupts bar's running goroutine. Abort won't be engaged |
| 344 | 349 | // if bar is already in complete state. If drop is true bar will be |
| 345 | 350 | // removed as well. To make sure that bar has been removed call |
| 346 | // (*Bar).Wait method. | |
| 351 | // `(*Bar).Wait()` method. | |
| 347 | 352 | func (b *Bar) Abort(drop bool) { |
| 348 | 353 | select { |
| 349 | 354 | case b.operateState <- func(s *bState) { |
| 350 | if s.completed || s.aborted { | |
| 355 | if s.aborted || s.completed() { | |
| 351 | 356 | return |
| 352 | 357 | } |
| 353 | 358 | s.aborted = true |
| 354 | 359 | s.rmOnComplete = drop |
| 355 | b.triggerCompletion(s) | |
| 356 | }: | |
| 357 | case <-b.done: | |
| 360 | s.triggerCompletion(b) | |
| 361 | }: | |
| 362 | case <-b.ctx.Done(): | |
| 358 | 363 | } |
| 359 | 364 | } |
| 360 | 365 | |
| 364 | 369 | select { |
| 365 | 370 | case b.operateState <- func(s *bState) { result <- s.aborted }: |
| 366 | 371 | return <-result |
| 367 | case <-b.done: | |
| 372 | case <-b.bsOk: | |
| 368 | 373 | return b.bs.aborted |
| 369 | 374 | } |
| 370 | 375 | } |
| 373 | 378 | func (b *Bar) Completed() bool { |
| 374 | 379 | result := make(chan bool) |
| 375 | 380 | select { |
| 376 | case b.operateState <- func(s *bState) { result <- s.completed }: | |
| 381 | case b.operateState <- func(s *bState) { result <- s.completed() }: | |
| 377 | 382 | return <-result |
| 378 | case <-b.done: | |
| 379 | return b.bs.completed | |
| 380 | } | |
| 381 | } | |
| 382 | ||
| 383 | // IsRunning reports whether the bar is running, i.e. not yet completed | |
| 384 | // and not yet aborted. | |
| 383 | case <-b.bsOk: | |
| 384 | return b.bs.completed() | |
| 385 | } | |
| 386 | } | |
| 387 | ||
| 388 | // IsRunning reports whether the bar is in running state. | |
| 385 | 389 | func (b *Bar) IsRunning() bool { |
| 386 | result := make(chan bool) | |
| 387 | select { | |
| 388 | case b.operateState <- func(s *bState) { result <- !s.completed && !s.aborted }: | |
| 389 | return <-result | |
| 390 | case <-b.done: | |
| 390 | select { | |
| 391 | case <-b.ctx.Done(): | |
| 391 | 392 | return false |
| 393 | default: | |
| 394 | return true | |
| 392 | 395 | } |
| 393 | 396 | } |
| 394 | 397 | |
| 395 | 398 | // Wait blocks until bar is completed or aborted. |
| 396 | 399 | func (b *Bar) Wait() { |
| 397 | <-b.done | |
| 398 | } | |
| 399 | ||
| 400 | func (b *Bar) serve(ctx context.Context, bs *bState) { | |
| 401 | defer b.container.bwg.Done() | |
| 400 | <-b.bsOk | |
| 401 | } | |
| 402 | ||
| 403 | func (b *Bar) serve(bs *bState) { | |
| 404 | decoratorsOnShutdown := func(decorators []decor.Decorator) { | |
| 405 | for _, d := range decorators { | |
| 406 | if d, ok := unwrap(d).(decor.ShutdownListener); ok { | |
| 407 | b.container.bwg.Add(1) | |
| 408 | go func() { | |
| 409 | d.OnShutdown() | |
| 410 | b.container.bwg.Done() | |
| 411 | }() | |
| 412 | } | |
| 413 | } | |
| 414 | } | |
| 402 | 415 | for { |
| 403 | 416 | select { |
| 404 | 417 | case op := <-b.operateState: |
| 405 | 418 | op(bs) |
| 406 | case <-ctx.Done(): | |
| 407 | bs.aborted = !bs.completed | |
| 408 | bs.decoratorShutdownNotify() | |
| 419 | case <-b.ctx.Done(): | |
| 420 | decoratorsOnShutdown(bs.decorators[0]) | |
| 421 | decoratorsOnShutdown(bs.decorators[1]) | |
| 422 | // bar can be aborted by canceling parent ctx without calling b.Abort | |
| 423 | bs.aborted = !bs.completed() | |
| 409 | 424 | b.bs = bs |
| 410 | close(b.done) | |
| 425 | close(b.bsOk) | |
| 426 | b.container.bwg.Done() | |
| 411 | 427 | return |
| 412 | 428 | } |
| 413 | 429 | } |
| 414 | 430 | } |
| 415 | 431 | |
| 416 | 432 | func (b *Bar) render(tw int) { |
| 417 | var done bool | |
| 418 | 433 | fn := func(s *bState) { |
| 419 | var rows []io.Reader | |
| 420 | stat := newStatistics(tw, s) | |
| 434 | frame := new(renderFrame) | |
| 435 | stat := s.newStatistics(tw) | |
| 421 | 436 | r, err := s.draw(stat) |
| 422 | 437 | if err != nil { |
| 423 | b.frameCh <- &renderFrame{err: err} | |
| 438 | for _, buf := range s.buffers { | |
| 439 | buf.Reset() | |
| 440 | } | |
| 441 | frame.err = err | |
| 442 | b.frameCh <- frame | |
| 424 | 443 | return |
| 425 | 444 | } |
| 426 | rows = append(rows, r) | |
| 427 | if s.extender != nil { | |
| 428 | rows, err = s.extender(rows, stat) | |
| 429 | if err != nil { | |
| 430 | b.frameCh <- &renderFrame{err: err} | |
| 431 | return | |
| 432 | } | |
| 433 | } | |
| 434 | frame := &renderFrame{ | |
| 435 | rows: rows, | |
| 436 | shutdown: s.shutdown, | |
| 437 | rmOnComplete: s.rmOnComplete, | |
| 438 | noPop: s.noPop, | |
| 439 | done: done, | |
| 440 | } | |
| 441 | if s.completed || s.aborted { | |
| 445 | frame.rows, frame.err = s.extender(stat, r) | |
| 446 | if s.aborted || s.completed() { | |
| 447 | frame.shutdown = s.shutdown | |
| 448 | frame.rmOnComplete = s.rmOnComplete | |
| 449 | frame.noPop = s.noPop | |
| 442 | 450 | // post increment makes sure OnComplete decorators are rendered |
| 443 | 451 | s.shutdown++ |
| 444 | 452 | } |
| 446 | 454 | } |
| 447 | 455 | select { |
| 448 | 456 | case b.operateState <- fn: |
| 449 | case <-b.done: | |
| 450 | done = true | |
| 457 | case <-b.bsOk: | |
| 451 | 458 | fn(b.bs) |
| 452 | 459 | } |
| 453 | 460 | } |
| 454 | 461 | |
| 455 | func (b *Bar) triggerCompletion(s *bState) { | |
| 456 | if s.autoRefresh { | |
| 457 | // Technically this call isn't required, but if refresh rate is set to | |
| 458 | // one hour for example and bar completes within a few minutes p.Wait() | |
| 459 | // will wait for one hour. This call helps to avoid unnecessary waiting. | |
| 460 | go b.tryEarlyRefresh(s.renderReq) | |
| 461 | } else { | |
| 462 | b.cancel() | |
| 463 | } | |
| 464 | } | |
| 465 | ||
| 466 | 462 | func (b *Bar) tryEarlyRefresh(renderReq chan<- time.Time) { |
| 467 | var anyOtherRunning bool | |
| 463 | var otherRunning int | |
| 468 | 464 | b.container.traverseBars(func(bar *Bar) bool { |
| 469 | anyOtherRunning = b != bar && bar.IsRunning() | |
| 470 | return anyOtherRunning | |
| 465 | if b != bar && bar.IsRunning() { | |
| 466 | otherRunning++ | |
| 467 | return false // stop traverse | |
| 468 | } | |
| 469 | return true // continue traverse | |
| 471 | 470 | }) |
| 472 | if !anyOtherRunning { | |
| 471 | if otherRunning == 0 { | |
| 473 | 472 | for { |
| 474 | 473 | select { |
| 475 | 474 | case renderReq <- time.Now(): |
| 476 | case <-b.done: | |
| 475 | case <-b.ctx.Done(): | |
| 477 | 476 | return |
| 478 | 477 | } |
| 479 | 478 | } |
| 485 | 484 | select { |
| 486 | 485 | case b.operateState <- func(s *bState) { result <- s.wSyncTable() }: |
| 487 | 486 | return <-result |
| 488 | case <-b.done: | |
| 487 | case <-b.bsOk: | |
| 489 | 488 | return b.bs.wSyncTable() |
| 490 | 489 | } |
| 491 | 490 | } |
| 492 | 491 | |
| 493 | func (s *bState) draw(stat decor.Statistics) (io.Reader, error) { | |
| 494 | r, err := s.drawImpl(stat) | |
| 495 | if err != nil { | |
| 496 | for _, b := range s.buffers { | |
| 497 | b.Reset() | |
| 498 | } | |
| 499 | return nil, err | |
| 500 | } | |
| 501 | return io.MultiReader(r, strings.NewReader("\n")), nil | |
| 502 | } | |
| 503 | ||
| 504 | func (s *bState) drawImpl(stat decor.Statistics) (io.Reader, error) { | |
| 492 | func (s *bState) draw(stat decor.Statistics) (_ io.Reader, err error) { | |
| 505 | 493 | decorFiller := func(buf *bytes.Buffer, decorators []decor.Decorator) (err error) { |
| 506 | 494 | for _, d := range decorators { |
| 507 | 495 | // need to call Decor in any case becase of width synchronization |
| 521 | 509 | return err |
| 522 | 510 | } |
| 523 | 511 | |
| 524 | bufP, bufB, bufA := s.buffers[0], s.buffers[1], s.buffers[2] | |
| 525 | ||
| 526 | err := eitherError(decorFiller(bufP, s.pDecorators), decorFiller(bufA, s.aDecorators)) | |
| 512 | for i, buf := range s.buffers[:2] { | |
| 513 | err = decorFiller(buf, s.decorators[i]) | |
| 514 | if err != nil { | |
| 515 | return nil, err | |
| 516 | } | |
| 517 | } | |
| 518 | ||
| 519 | spaces := []io.Reader{ | |
| 520 | strings.NewReader(" "), | |
| 521 | strings.NewReader(" "), | |
| 522 | } | |
| 523 | if s.trimSpace || stat.AvailableWidth < 2 { | |
| 524 | for _, r := range spaces { | |
| 525 | _, _ = io.Copy(io.Discard, r) | |
| 526 | } | |
| 527 | } else { | |
| 528 | stat.AvailableWidth -= 2 | |
| 529 | } | |
| 530 | ||
| 531 | err = s.filler.Fill(s.buffers[2], stat) | |
| 527 | 532 | if err != nil { |
| 528 | 533 | return nil, err |
| 529 | 534 | } |
| 530 | 535 | |
| 531 | if !s.trimSpace && stat.AvailableWidth >= 2 { | |
| 532 | stat.AvailableWidth -= 2 | |
| 533 | writeFiller := func(buf *bytes.Buffer) error { | |
| 534 | return s.filler.Fill(buf, stat) | |
| 535 | } | |
| 536 | for _, fn := range []func(*bytes.Buffer) error{ | |
| 537 | writeSpace, | |
| 538 | writeFiller, | |
| 539 | writeSpace, | |
| 540 | } { | |
| 541 | if err := fn(bufB); err != nil { | |
| 542 | return nil, err | |
| 543 | } | |
| 544 | } | |
| 545 | } else { | |
| 546 | err := s.filler.Fill(bufB, stat) | |
| 547 | if err != nil { | |
| 548 | return nil, err | |
| 549 | } | |
| 550 | } | |
| 551 | ||
| 552 | return io.MultiReader(bufP, bufB, bufA), nil | |
| 536 | return io.MultiReader( | |
| 537 | s.buffers[0], | |
| 538 | spaces[0], | |
| 539 | s.buffers[2], | |
| 540 | spaces[1], | |
| 541 | s.buffers[1], | |
| 542 | strings.NewReader("\n"), | |
| 543 | ), nil | |
| 553 | 544 | } |
| 554 | 545 | |
| 555 | 546 | func (s *bState) wSyncTable() (table syncTable) { |
| 556 | 547 | var count int |
| 557 | 548 | var row []chan int |
| 558 | 549 | |
| 559 | for i, decorators := range [][]decor.Decorator{ | |
| 560 | s.pDecorators, | |
| 561 | s.aDecorators, | |
| 562 | } { | |
| 550 | for i, decorators := range s.decorators { | |
| 563 | 551 | for _, d := range decorators { |
| 564 | 552 | if ch, ok := d.Sync(); ok { |
| 565 | 553 | row = append(row, ch) |
| 576 | 564 | return table |
| 577 | 565 | } |
| 578 | 566 | |
| 579 | func (s bState) decoratorEwmaUpdate(n int64, dur time.Duration) { | |
| 580 | var wg sync.WaitGroup | |
| 581 | for i := 0; i < len(s.ewmaDecorators); i++ { | |
| 582 | switch d := s.ewmaDecorators[i]; i { | |
| 583 | case len(s.ewmaDecorators) - 1: | |
| 584 | d.EwmaUpdate(n, dur) | |
| 585 | default: | |
| 586 | wg.Add(1) | |
| 587 | go func() { | |
| 588 | d.EwmaUpdate(n, dur) | |
| 589 | wg.Done() | |
| 590 | }() | |
| 591 | } | |
| 592 | } | |
| 593 | wg.Wait() | |
| 594 | } | |
| 595 | ||
| 596 | func (s bState) decoratorAverageAdjust(start time.Time) { | |
| 597 | var wg sync.WaitGroup | |
| 598 | for i := 0; i < len(s.averageDecorators); i++ { | |
| 599 | switch d := s.averageDecorators[i]; i { | |
| 600 | case len(s.averageDecorators) - 1: | |
| 601 | d.AverageAdjust(start) | |
| 602 | default: | |
| 603 | wg.Add(1) | |
| 604 | go func() { | |
| 605 | d.AverageAdjust(start) | |
| 606 | wg.Done() | |
| 607 | }() | |
| 608 | } | |
| 609 | } | |
| 610 | wg.Wait() | |
| 611 | } | |
| 612 | ||
| 613 | func (s bState) decoratorShutdownNotify() { | |
| 614 | var wg sync.WaitGroup | |
| 615 | for i := 0; i < len(s.shutdownListeners); i++ { | |
| 616 | switch d := s.shutdownListeners[i]; i { | |
| 617 | case len(s.shutdownListeners) - 1: | |
| 618 | d.OnShutdown() | |
| 619 | default: | |
| 620 | wg.Add(1) | |
| 621 | go func() { | |
| 622 | d.OnShutdown() | |
| 623 | wg.Done() | |
| 624 | }() | |
| 625 | } | |
| 626 | } | |
| 627 | wg.Wait() | |
| 628 | } | |
| 629 | ||
| 630 | func newStatistics(tw int, s *bState) decor.Statistics { | |
| 567 | func (s *bState) populateEwmaDecorators(decorators []decor.Decorator) { | |
| 568 | for _, d := range decorators { | |
| 569 | if d, ok := unwrap(d).(decor.EwmaDecorator); ok { | |
| 570 | s.ewmaDecorators = append(s.ewmaDecorators, d) | |
| 571 | } | |
| 572 | } | |
| 573 | } | |
| 574 | ||
| 575 | func (s *bState) triggerCompletion(b *Bar) { | |
| 576 | s.triggerComplete = true | |
| 577 | if s.autoRefresh { | |
| 578 | // Technically this call isn't required, but if refresh rate is set to | |
| 579 | // one hour for example and bar completes within a few minutes p.Wait() | |
| 580 | // will wait for one hour. This call helps to avoid unnecessary waiting. | |
| 581 | go b.tryEarlyRefresh(s.renderReq) | |
| 582 | } else { | |
| 583 | b.cancel() | |
| 584 | } | |
| 585 | } | |
| 586 | ||
| 587 | func (s bState) completed() bool { | |
| 588 | return s.triggerComplete && s.current == s.total | |
| 589 | } | |
| 590 | ||
| 591 | func (s bState) newStatistics(tw int) decor.Statistics { | |
| 631 | 592 | return decor.Statistics{ |
| 632 | 593 | AvailableWidth: tw, |
| 633 | 594 | RequestedWidth: s.reqWidth, |
| 635 | 596 | Total: s.total, |
| 636 | 597 | Current: s.current, |
| 637 | 598 | Refill: s.refill, |
| 638 | Completed: s.completed, | |
| 599 | Completed: s.completed(), | |
| 639 | 600 | Aborted: s.aborted, |
| 640 | 601 | } |
| 641 | 602 | } |
| 646 | 607 | } |
| 647 | 608 | return d |
| 648 | 609 | } |
| 649 | ||
| 650 | func writeSpace(buf *bytes.Buffer) error { | |
| 651 | return buf.WriteByte(' ') | |
| 652 | } | |
| 653 | ||
| 654 | func eitherError(errors ...error) error { | |
| 655 | for _, err := range errors { | |
| 656 | if err != nil { | |
| 657 | return err | |
| 658 | } | |
| 659 | } | |
| 660 | return nil | |
| 661 | } | |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | 51 | type bFiller struct { |
| 52 | components [components]component | |
| 53 | meta [components]func(io.Writer, []byte) error | |
| 54 | flush func(io.Writer, ...flushSection) error | |
| 55 | tipOnComplete bool | |
| 56 | tip struct { | |
| 57 | frames []component | |
| 58 | count uint | |
| 52 | components [components]component | |
| 53 | meta [components]func(io.Writer, []byte) error | |
| 54 | flush func(io.Writer, ...flushSection) error | |
| 55 | tip struct { | |
| 56 | onComplete bool | |
| 57 | count uint | |
| 58 | frames []component | |
| 59 | 59 | } |
| 60 | 60 | } |
| 61 | 61 | |
| 154 | 154 | |
| 155 | 155 | func (s barStyle) Build() BarFiller { |
| 156 | 156 | bf := &bFiller{ |
| 157 | meta: s.metaFuncs, | |
| 158 | tipOnComplete: s.tipOnComplete, | |
| 157 | meta: s.metaFuncs, | |
| 159 | 158 | } |
| 160 | 159 | bf.components[iLbound] = component{ |
| 161 | 160 | width: runewidth.StringWidth(s.style[iLbound]), |
| 177 | 176 | width: runewidth.StringWidth(s.style[iPadding]), |
| 178 | 177 | bytes: []byte(s.style[iPadding]), |
| 179 | 178 | } |
| 179 | bf.tip.onComplete = s.tipOnComplete | |
| 180 | 180 | bf.tip.frames = make([]component, len(s.tipFrames)) |
| 181 | 181 | for i, t := range s.tipFrames { |
| 182 | 182 | bf.tip.frames[i] = component{ |
| 235 | 235 | curWidth := int(internal.PercentageRound(stat.Total, stat.Current, uint(width))) |
| 236 | 236 | |
| 237 | 237 | if curWidth != 0 { |
| 238 | if !stat.Completed || s.tipOnComplete { | |
| 238 | if !stat.Completed || s.tip.onComplete { | |
| 239 | 239 | tip = s.tip.frames[s.tip.count%uint(len(s.tip.frames))] |
| 240 | 240 | s.tip.count++ |
| 241 | 241 | fillCount += tip.width |
| 242 | 242 | } |
| 243 | if stat.Refill != 0 { | |
| 244 | refWidth := int(internal.PercentageRound(stat.Total, stat.Refill, uint(width))) | |
| 243 | switch refWidth := 0; { | |
| 244 | case stat.Refill != 0: | |
| 245 | refWidth = int(internal.PercentageRound(stat.Total, stat.Refill, uint(width))) | |
| 245 | 246 | curWidth -= refWidth |
| 246 | 247 | refWidth += curWidth |
| 248 | fallthrough | |
| 249 | default: | |
| 247 | 250 | for w := s.components[iFiller].width; curWidth-fillCount >= w; fillCount += w { |
| 248 | 251 | filling = append(filling, s.components[iFiller].bytes...) |
| 249 | 252 | } |
| 250 | 253 | for w := s.components[iRefiller].width; refWidth-fillCount >= w; fillCount += w { |
| 251 | 254 | refilling = append(refilling, s.components[iRefiller].bytes...) |
| 252 | } | |
| 253 | } else { | |
| 254 | for w := s.components[iFiller].width; curWidth-fillCount >= w; fillCount += w { | |
| 255 | filling = append(filling, s.components[iFiller].bytes...) | |
| 256 | 255 | } |
| 257 | 256 | } |
| 258 | 257 | } |
| 19 | 19 | return |
| 20 | 20 | } |
| 21 | 21 | |
| 22 | // PrependDecorators let you inject decorators to the bar's left side. | |
| 23 | func PrependDecorators(decorators ...decor.Decorator) BarOption { | |
| 24 | decorators = inspect(decorators) | |
| 25 | return func(s *bState) { | |
| 26 | s.populateEwmaDecorators(decorators) | |
| 27 | s.decorators[0] = decorators | |
| 28 | } | |
| 29 | } | |
| 30 | ||
| 22 | 31 | // AppendDecorators let you inject decorators to the bar's right side. |
| 23 | 32 | func AppendDecorators(decorators ...decor.Decorator) BarOption { |
| 24 | 33 | decorators = inspect(decorators) |
| 25 | 34 | return func(s *bState) { |
| 26 | s.aDecorators = decorators | |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 30 | // PrependDecorators let you inject decorators to the bar's left side. | |
| 31 | func PrependDecorators(decorators ...decor.Decorator) BarOption { | |
| 32 | decorators = inspect(decorators) | |
| 33 | return func(s *bState) { | |
| 34 | s.pDecorators = decorators | |
| 35 | s.populateEwmaDecorators(decorators) | |
| 36 | s.decorators[1] = decorators | |
| 35 | 37 | } |
| 36 | 38 | } |
| 37 | 39 | |
| 111 | 113 | if filler == nil { |
| 112 | 114 | return nil |
| 113 | 115 | } |
| 116 | if f, ok := filler.(BarFillerFunc); ok && f == nil { | |
| 117 | return nil | |
| 118 | } | |
| 114 | 119 | fn := makeExtenderFunc(filler, rev) |
| 115 | 120 | return func(s *bState) { |
| 116 | 121 | s.extender = fn |
| 119 | 124 | |
| 120 | 125 | func makeExtenderFunc(filler BarFiller, rev bool) extenderFunc { |
| 121 | 126 | buf := new(bytes.Buffer) |
| 122 | base := func(rows []io.Reader, stat decor.Statistics) ([]io.Reader, error) { | |
| 127 | base := func(stat decor.Statistics, rows ...io.Reader) ([]io.Reader, error) { | |
| 123 | 128 | err := filler.Fill(buf, stat) |
| 124 | 129 | if err != nil { |
| 125 | 130 | buf.Reset() |
| 126 | 131 | return rows, err |
| 127 | 132 | } |
| 128 | 133 | for { |
| 129 | b, err := buf.ReadBytes('\n') | |
| 134 | line, err := buf.ReadBytes('\n') | |
| 130 | 135 | if err != nil { |
| 136 | buf.Reset() | |
| 131 | 137 | break |
| 132 | 138 | } |
| 133 | rows = append(rows, bytes.NewReader(b)) | |
| 134 | } | |
| 135 | buf.Reset() | |
| 139 | rows = append(rows, bytes.NewReader(line)) | |
| 140 | } | |
| 136 | 141 | return rows, err |
| 137 | 142 | } |
| 138 | ||
| 139 | 143 | if !rev { |
| 140 | 144 | return base |
| 141 | 145 | } |
| 142 | return func(rows []io.Reader, stat decor.Statistics) ([]io.Reader, error) { | |
| 143 | rows, err := base(rows, stat) | |
| 146 | return func(stat decor.Statistics, rows ...io.Reader) ([]io.Reader, error) { | |
| 147 | rows, err := base(stat, rows...) | |
| 144 | 148 | if err != nil { |
| 145 | 149 | return rows, err |
| 146 | 150 | } |
| 19 | 19 | bar := p.AddBar(int64(total)) |
| 20 | 20 | |
| 21 | 21 | if bar.Completed() { |
| 22 | t.Fail() | |
| 22 | t.Error("expected bar not to complete") | |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | 25 | bar.IncrBy(total) |
| 26 | 26 | |
| 27 | 27 | if !bar.Completed() { |
| 28 | t.Error("bar isn't completed after increment") | |
| 28 | t.Error("expected bar to complete") | |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | 31 | p.Wait() |
| 37 | 37 | bar := p.AddBar(int64(total)) |
| 38 | 38 | |
| 39 | 39 | if bar.Aborted() { |
| 40 | t.Fail() | |
| 40 | t.Error("expected bar not to be aborted") | |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | bar.Abort(false) |
| 44 | 44 | |
| 45 | 45 | if !bar.Aborted() { |
| 46 | t.Error("bar isn't aborted after abort call") | |
| 46 | t.Error("expected bar to be aborted") | |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | 49 | p.Wait() |
| 66 | 66 | p.Wait() |
| 67 | 67 | } |
| 68 | 68 | |
| 69 | func TestBarEnableTriggerCompleteZeroBar(t *testing.T) { | |
| 70 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(io.Discard)) | |
| 71 | bar := p.AddBar(0) // never complete bar | |
| 72 | ||
| 73 | if bar.Completed() { | |
| 74 | t.Error("expected bar not to complete") | |
| 75 | } | |
| 76 | ||
| 77 | // Calling bar.SetTotal(0, true) has same effect | |
| 78 | // but this one is more concise and intuitive | |
| 79 | bar.EnableTriggerComplete() | |
| 80 | ||
| 81 | if !bar.Completed() { | |
| 82 | t.Error("expected bar to complete") | |
| 83 | } | |
| 84 | ||
| 85 | p.Wait() | |
| 86 | } | |
| 87 | ||
| 69 | 88 | func TestBarEnableTriggerCompleteAndIncrementBefore(t *testing.T) { |
| 70 | 89 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(io.Discard)) |
| 71 | 90 | bar := p.AddBar(0) // never complete bar |
| 91 | ||
| 92 | targetTotal := int64(80) | |
| 72 | 93 | |
| 73 | 94 | for _, f := range []func(){ |
| 74 | 95 | func() { bar.SetTotal(40, false) }, |
| 75 | 96 | func() { bar.IncrBy(60) }, |
| 76 | func() { bar.SetTotal(80, false) }, | |
| 97 | func() { bar.SetTotal(targetTotal, false) }, | |
| 77 | 98 | func() { bar.IncrBy(20) }, |
| 78 | 99 | } { |
| 79 | 100 | f() |
| 80 | 101 | if bar.Completed() { |
| 81 | t.Fail() | |
| 102 | t.Error("expected bar not to complete") | |
| 82 | 103 | } |
| 83 | 104 | } |
| 84 | 105 | |
| 85 | 106 | bar.EnableTriggerComplete() |
| 86 | 107 | |
| 87 | 108 | if !bar.Completed() { |
| 88 | t.Fail() | |
| 109 | t.Error("expected bar to complete") | |
| 110 | } | |
| 111 | ||
| 112 | if current := bar.Current(); current != targetTotal { | |
| 113 | t.Errorf("Expected current: %d, got: %d", targetTotal, current) | |
| 89 | 114 | } |
| 90 | 115 | |
| 91 | 116 | p.Wait() |
| 94 | 119 | func TestBarEnableTriggerCompleteAndIncrementAfter(t *testing.T) { |
| 95 | 120 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(io.Discard)) |
| 96 | 121 | bar := p.AddBar(0) // never complete bar |
| 122 | ||
| 123 | targetTotal := int64(80) | |
| 97 | 124 | |
| 98 | 125 | for _, f := range []func(){ |
| 99 | 126 | func() { bar.SetTotal(40, false) }, |
| 100 | 127 | func() { bar.IncrBy(60) }, |
| 101 | func() { bar.SetTotal(80, false) }, | |
| 102 | func() { bar.EnableTriggerComplete() }, | |
| 128 | func() { bar.SetTotal(targetTotal, false) }, | |
| 129 | func() { bar.EnableTriggerComplete() }, // disables any next SetTotal | |
| 130 | func() { bar.SetTotal(100, true) }, // nop | |
| 103 | 131 | } { |
| 104 | 132 | f() |
| 105 | 133 | if bar.Completed() { |
| 106 | t.Fail() | |
| 134 | t.Error("expected bar not to complete") | |
| 107 | 135 | } |
| 108 | 136 | } |
| 109 | 137 | |
| 110 | 138 | bar.IncrBy(20) |
| 111 | 139 | |
| 112 | 140 | if !bar.Completed() { |
| 113 | t.Fail() | |
| 141 | t.Error("expected bar to complete") | |
| 142 | } | |
| 143 | ||
| 144 | if current := bar.Current(); current != targetTotal { | |
| 145 | t.Errorf("Expected current: %d, got: %d", targetTotal, current) | |
| 114 | 146 | } |
| 115 | 147 | |
| 116 | 148 | p.Wait() |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "io" |
| 4 | "sync" | |
| 5 | 4 | "testing" |
| 6 | 5 | |
| 7 | 6 | "github.com/vbauerster/mpb/v8" |
| 9 | 8 | |
| 10 | 9 | const total = 1000 |
| 11 | 10 | |
| 12 | func BenchmarkNopStyle1Bar(b *testing.B) { | |
| 11 | func BenchmarkNopStyleB1(b *testing.B) { | |
| 13 | 12 | bench(b, mpb.NopStyle(), false, 1) |
| 14 | 13 | } |
| 15 | 14 | |
| 16 | func BenchmarkNopStyle1BarWithAutoRefresh(b *testing.B) { | |
| 15 | func BenchmarkNopStyleWithAutoRefreshB1(b *testing.B) { | |
| 17 | 16 | bench(b, mpb.NopStyle(), true, 1) |
| 18 | 17 | } |
| 19 | 18 | |
| 20 | func BenchmarkNopStyle2Bars(b *testing.B) { | |
| 19 | func BenchmarkNopStylesB2(b *testing.B) { | |
| 21 | 20 | bench(b, mpb.NopStyle(), false, 2) |
| 22 | 21 | } |
| 23 | 22 | |
| 24 | func BenchmarkNopStyle2BarsWithAutoRefresh(b *testing.B) { | |
| 23 | func BenchmarkNopStylesWithAutoRefreshB2(b *testing.B) { | |
| 25 | 24 | bench(b, mpb.NopStyle(), true, 2) |
| 26 | 25 | } |
| 27 | 26 | |
| 28 | func BenchmarkNopStyle3Bars(b *testing.B) { | |
| 27 | func BenchmarkNopStylesB3(b *testing.B) { | |
| 29 | 28 | bench(b, mpb.NopStyle(), false, 3) |
| 30 | 29 | } |
| 31 | 30 | |
| 32 | func BenchmarkNopStyle3BarsWithAutoRefresh(b *testing.B) { | |
| 31 | func BenchmarkNopStylesWithAutoRefreshB3(b *testing.B) { | |
| 33 | 32 | bench(b, mpb.NopStyle(), true, 3) |
| 34 | 33 | } |
| 35 | 34 | |
| 36 | func BenchmarkBarStyle1Bar(b *testing.B) { | |
| 35 | func BenchmarkBarStyleB1(b *testing.B) { | |
| 37 | 36 | bench(b, mpb.BarStyle(), false, 1) |
| 38 | 37 | } |
| 39 | 38 | |
| 40 | func BenchmarkBarStyle1BarWithAutoRefresh(b *testing.B) { | |
| 39 | func BenchmarkBarStyleWithAutoRefreshB1(b *testing.B) { | |
| 41 | 40 | bench(b, mpb.BarStyle(), true, 1) |
| 42 | 41 | } |
| 43 | 42 | |
| 44 | func BenchmarkBarStyle2Bars(b *testing.B) { | |
| 43 | func BenchmarkBarStylesB2(b *testing.B) { | |
| 45 | 44 | bench(b, mpb.BarStyle(), false, 2) |
| 46 | 45 | } |
| 47 | 46 | |
| 48 | func BenchmarkBarStyle2BarsWithAutoRefresh(b *testing.B) { | |
| 47 | func BenchmarkBarStylesWithAutoRefreshB2(b *testing.B) { | |
| 49 | 48 | bench(b, mpb.BarStyle(), true, 2) |
| 50 | 49 | } |
| 51 | 50 | |
| 52 | func BenchmarkBarStyle3Bars(b *testing.B) { | |
| 51 | func BenchmarkBarStylesB3(b *testing.B) { | |
| 53 | 52 | bench(b, mpb.BarStyle(), false, 3) |
| 54 | 53 | } |
| 55 | 54 | |
| 56 | func BenchmarkBarStyle3BarsWithAutoRefresh(b *testing.B) { | |
| 55 | func BenchmarkBarStylesWithAutoRefreshB3(b *testing.B) { | |
| 57 | 56 | bench(b, mpb.BarStyle(), true, 3) |
| 58 | 57 | } |
| 59 | 58 | |
| 60 | 59 | func bench(b *testing.B, builder mpb.BarFillerBuilder, autoRefresh bool, n int) { |
| 61 | var wg sync.WaitGroup | |
| 62 | 60 | p := mpb.New( |
| 63 | 61 | mpb.WithWidth(100), |
| 64 | 62 | mpb.WithOutput(io.Discard), |
| 65 | 63 | mpb.ContainerOptional(mpb.WithAutoRefresh(), autoRefresh), |
| 66 | 64 | ) |
| 65 | defer p.Wait() | |
| 67 | 66 | b.ResetTimer() |
| 68 | 67 | for i := 0; i < b.N; i++ { |
| 68 | var bars []*mpb.Bar | |
| 69 | 69 | for j := 0; j < n; j++ { |
| 70 | bar := p.New(total, builder) | |
| 70 | bars = append(bars, p.New(total, builder)) | |
| 71 | 71 | switch j { |
| 72 | 72 | case n - 1: |
| 73 | complete(b, bar) | |
| 73 | complete(bars[j]) | |
| 74 | 74 | default: |
| 75 | wg.Add(1) | |
| 76 | go func() { | |
| 77 | complete(b, bar) | |
| 78 | wg.Done() | |
| 79 | }() | |
| 75 | go complete(bars[j]) | |
| 80 | 76 | } |
| 81 | 77 | } |
| 82 | wg.Wait() | |
| 78 | for _, bar := range bars { | |
| 79 | bar.Wait() | |
| 80 | } | |
| 83 | 81 | } |
| 84 | p.Wait() | |
| 85 | 82 | } |
| 86 | 83 | |
| 87 | func complete(b *testing.B, bar *mpb.Bar) { | |
| 84 | func complete(bar *mpb.Bar) { | |
| 88 | 85 | for i := 0; i < total; i++ { |
| 89 | 86 | bar.Increment() |
| 90 | 87 | } |
| 91 | bar.Wait() | |
| 92 | 88 | } |
| 92 | 92 | } |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | // PopCompletedMode will pop completed bars to the top. | |
| 96 | // To stop rendering bar after it has been popped, use | |
| 97 | // mpb.BarRemoveOnComplete() option on that bar. | |
| 95 | // PopCompletedMode pop completed bars out of progress container. | |
| 96 | // In this mode completed bars get moved to the top and stop | |
| 97 | // participating in rendering cycle. | |
| 98 | 98 | func PopCompletedMode() ContainerOption { |
| 99 | 99 | return func(s *pState) { |
| 100 | 100 | s.popCompleted = true |
| 7 | 7 | ) |
| 8 | 8 | |
| 9 | 9 | const ( |
| 10 | // DidentRight bit specifies identation direction. | |
| 10 | // DindentRight sets indentation from right to left. | |
| 11 | 11 | // |
| 12 | // |foo |b | With DidentRight | |
| 13 | // | foo| b| Without DidentRight | |
| 14 | DidentRight = 1 << iota | |
| 12 | // |foo |b | DindentRight is set | |
| 13 | // | foo| b| DindentRight is not set | |
| 14 | DindentRight = 1 << iota | |
| 15 | 15 | |
| 16 | // DextraSpace bit adds extra space, makes sense with DSyncWidth only. | |
| 17 | // When DidentRight bit set, the space will be added to the right, | |
| 18 | // otherwise to the left. | |
| 16 | // DextraSpace bit adds extra indentation space. | |
| 19 | 17 | DextraSpace |
| 20 | 18 | |
| 21 | 19 | // DSyncWidth bit enables same column width synchronization. |
| 22 | 20 | // Effective with multiple bars only. |
| 23 | 21 | DSyncWidth |
| 24 | 22 | |
| 25 | // DSyncWidthR is shortcut for DSyncWidth|DidentRight | |
| 26 | DSyncWidthR = DSyncWidth | DidentRight | |
| 23 | // DSyncWidthR is shortcut for DSyncWidth|DindentRight | |
| 24 | DSyncWidthR = DSyncWidth | DindentRight | |
| 27 | 25 | |
| 28 | 26 | // DSyncSpace is shortcut for DSyncWidth|DextraSpace |
| 29 | 27 | DSyncSpace = DSyncWidth | DextraSpace |
| 30 | 28 | |
| 31 | // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight | |
| 32 | DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight | |
| 29 | // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DindentRight | |
| 30 | DSyncSpaceR = DSyncWidth | DextraSpace | DindentRight | |
| 33 | 31 | ) |
| 34 | 32 | |
| 35 | 33 | // TimeStyle enum. |
| 86 | 84 | // in order to format string according to decor.WC settings. |
| 87 | 85 | // No need to implement manually as long as decor.WC is embedded. |
| 88 | 86 | type Formatter interface { |
| 89 | Format(string) (str string, viewWidth int) | |
| 87 | Format(string) (_ string, width int) | |
| 90 | 88 | } |
| 91 | 89 | |
| 92 | 90 | // Wrapper interface. |
| 139 | 137 | // Format should be called by any Decorator implementation. |
| 140 | 138 | // Returns formatted string and its view (visual) width. |
| 141 | 139 | func (wc WC) Format(str string) (string, int) { |
| 142 | viewWidth := runewidth.StringWidth(str) | |
| 143 | if wc.W > viewWidth { | |
| 144 | viewWidth = wc.W | |
| 140 | width := runewidth.StringWidth(str) | |
| 141 | if wc.W > width { | |
| 142 | width = wc.W | |
| 143 | } else if (wc.C & DextraSpace) != 0 { | |
| 144 | width++ | |
| 145 | 145 | } |
| 146 | 146 | if (wc.C & DSyncWidth) != 0 { |
| 147 | if (wc.C & DextraSpace) != 0 { | |
| 148 | viewWidth++ | |
| 149 | } | |
| 150 | wc.wsync <- viewWidth | |
| 151 | viewWidth = <-wc.wsync | |
| 147 | wc.wsync <- width | |
| 148 | width = <-wc.wsync | |
| 152 | 149 | } |
| 153 | return wc.fill(str, viewWidth), viewWidth | |
| 150 | return wc.fill(str, width), width | |
| 154 | 151 | } |
| 155 | 152 | |
| 156 | 153 | // Init initializes width related config. |
| 157 | 154 | func (wc *WC) Init() WC { |
| 158 | if (wc.C & DidentRight) != 0 { | |
| 155 | if (wc.C & DindentRight) != 0 { | |
| 159 | 156 | wc.fill = runewidth.FillRight |
| 160 | 157 | } else { |
| 161 | 158 | wc.fill = runewidth.FillLeft |
| 16 | 16 | // |
| 17 | 17 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] |
| 18 | 18 | // |
| 19 | // `startTime` start time | |
| 19 | // `start` start time | |
| 20 | 20 | // |
| 21 | 21 | // `wcc` optional WC config |
| 22 | func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator { | |
| 22 | func NewElapsed(style TimeStyle, start time.Time, wcc ...WC) Decorator { | |
| 23 | 23 | var msg string |
| 24 | 24 | producer := chooseTimeProducer(style) |
| 25 | 25 | fn := func(s Statistics) string { |
| 26 | if !s.Completed { | |
| 27 | msg = producer(time.Since(startTime)) | |
| 26 | if !s.Completed && !s.Aborted { | |
| 27 | msg = producer(time.Since(start)) | |
| 28 | 28 | } |
| 29 | 29 | return msg |
| 30 | 30 | } |
| 32 | 32 | // decorator to work correctly you have to measure each iteration's duration |
| 33 | 33 | // and pass it to one of the (*Bar).EwmaIncr... family methods. |
| 34 | 34 | func EwmaETA(style TimeStyle, age float64, wcc ...WC) Decorator { |
| 35 | return EwmaNormalizedETA(style, age, nil, wcc...) | |
| 36 | } | |
| 37 | ||
| 38 | // EwmaNormalizedETA same as EwmaETA but with TimeNormalizer option. | |
| 39 | func EwmaNormalizedETA(style TimeStyle, age float64, normalizer TimeNormalizer, wcc ...WC) Decorator { | |
| 35 | 40 | var average ewma.MovingAverage |
| 36 | 41 | if age == 0 { |
| 37 | 42 | average = ewma.NewMovingAverage() |
| 38 | 43 | } else { |
| 39 | 44 | average = ewma.NewMovingAverage(age) |
| 40 | 45 | } |
| 41 | return MovingAverageETA(style, NewThreadSafeMovingAverage(average), nil, wcc...) | |
| 46 | return MovingAverageETA(style, average, normalizer, wcc...) | |
| 42 | 47 | } |
| 43 | 48 | |
| 44 | 49 | // MovingAverageETA decorator relies on MovingAverage implementation to calculate its average. |
| 51 | 56 | // |
| 52 | 57 | // `wcc` optional WC config |
| 53 | 58 | func MovingAverageETA(style TimeStyle, average ewma.MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator { |
| 59 | if average == nil { | |
| 60 | average = NewMedian() | |
| 61 | } | |
| 54 | 62 | d := &movingAverageETA{ |
| 55 | 63 | WC: initWC(wcc...), |
| 64 | producer: chooseTimeProducer(style), | |
| 56 | 65 | average: average, |
| 57 | 66 | normalizer: normalizer, |
| 58 | producer: chooseTimeProducer(style), | |
| 59 | 67 | } |
| 60 | 68 | return d |
| 61 | 69 | } |
| 62 | 70 | |
| 63 | 71 | type movingAverageETA struct { |
| 64 | 72 | WC |
| 73 | producer func(time.Duration) string | |
| 65 | 74 | average ewma.MovingAverage |
| 66 | 75 | normalizer TimeNormalizer |
| 67 | producer func(time.Duration) string | |
| 76 | zDur time.Duration | |
| 68 | 77 | } |
| 69 | 78 | |
| 70 | 79 | func (d *movingAverageETA) Decor(s Statistics) (string, int) { |
| 77 | 86 | } |
| 78 | 87 | |
| 79 | 88 | func (d *movingAverageETA) EwmaUpdate(n int64, dur time.Duration) { |
| 80 | durPerItem := float64(dur) / float64(n) | |
| 89 | if n <= 0 { | |
| 90 | d.zDur += dur | |
| 91 | return | |
| 92 | } | |
| 93 | durPerItem := float64(d.zDur+dur) / float64(n) | |
| 81 | 94 | if math.IsInf(durPerItem, 0) || math.IsNaN(durPerItem) { |
| 95 | d.zDur += dur | |
| 82 | 96 | return |
| 83 | 97 | } |
| 98 | d.zDur = 0 | |
| 84 | 99 | d.average.Add(durPerItem) |
| 85 | 100 | } |
| 86 | 101 | |
| 97 | 112 | // |
| 98 | 113 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] |
| 99 | 114 | // |
| 100 | // `startTime` start time | |
| 115 | // `start` start time | |
| 101 | 116 | // |
| 102 | 117 | // `normalizer` available implementations are [FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer] |
| 103 | 118 | // |
| 104 | 119 | // `wcc` optional WC config |
| 105 | func NewAverageETA(style TimeStyle, startTime time.Time, normalizer TimeNormalizer, wcc ...WC) Decorator { | |
| 120 | func NewAverageETA(style TimeStyle, start time.Time, normalizer TimeNormalizer, wcc ...WC) Decorator { | |
| 106 | 121 | d := &averageETA{ |
| 107 | 122 | WC: initWC(wcc...), |
| 108 | startTime: startTime, | |
| 123 | start: start, | |
| 109 | 124 | normalizer: normalizer, |
| 110 | 125 | producer: chooseTimeProducer(style), |
| 111 | 126 | } |
| 114 | 129 | |
| 115 | 130 | type averageETA struct { |
| 116 | 131 | WC |
| 117 | startTime time.Time | |
| 132 | start time.Time | |
| 118 | 133 | normalizer TimeNormalizer |
| 119 | 134 | producer func(time.Duration) string |
| 120 | 135 | } |
| 122 | 137 | func (d *averageETA) Decor(s Statistics) (string, int) { |
| 123 | 138 | var remaining time.Duration |
| 124 | 139 | if s.Current != 0 { |
| 125 | durPerItem := float64(time.Since(d.startTime)) / float64(s.Current) | |
| 140 | durPerItem := float64(time.Since(d.start)) / float64(s.Current) | |
| 126 | 141 | durPerItem = math.Round(durPerItem) |
| 127 | 142 | remaining = time.Duration((s.Total - s.Current) * int64(durPerItem)) |
| 128 | 143 | if d.normalizer != nil { |
| 132 | 147 | return d.Format(d.producer(remaining)) |
| 133 | 148 | } |
| 134 | 149 | |
| 135 | func (d *averageETA) AverageAdjust(startTime time.Time) { | |
| 136 | d.startTime = startTime | |
| 150 | func (d *averageETA) AverageAdjust(start time.Time) { | |
| 151 | d.start = start | |
| 137 | 152 | } |
| 138 | 153 | |
| 139 | 154 | // MaxTolerateTimeNormalizer returns implementation of TimeNormalizer. |
| 69 | 69 | |
| 70 | 70 | // NewMedian is fixed last 3 samples median MovingAverage. |
| 71 | 71 | func NewMedian() ewma.MovingAverage { |
| 72 | return NewThreadSafeMovingAverage(new(medianWindow)) | |
| 72 | return new(medianWindow) | |
| 73 | 73 | } |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | 57 | func (d onAbortMetaWrapper) Decor(s Statistics) (string, int) { |
| 58 | if s.Completed { | |
| 58 | if s.Aborted { | |
| 59 | 59 | str, width := d.Decorator.Decor(s) |
| 60 | 60 | return d.fn(str), width |
| 61 | 61 | } |
| 60 | 60 | format = "% d" |
| 61 | 61 | } |
| 62 | 62 | f := func(s Statistics) string { |
| 63 | p := internal.Percentage(s.Total, s.Current, 100) | |
| 63 | p := internal.Percentage(uint(s.Total), uint(s.Current), 100) | |
| 64 | 64 | return fmt.Sprintf(format, percentageType(p)) |
| 65 | 65 | } |
| 66 | 66 | return Any(f, wcc...) |
| 55 | 55 | }) |
| 56 | 56 | } |
| 57 | 57 | } |
| 58 | ||
| 59 | func TestPercentageDecor(t *testing.T) { | |
| 60 | cases := []struct { | |
| 61 | name string | |
| 62 | fmt string | |
| 63 | current int64 | |
| 64 | total int64 | |
| 65 | expected string | |
| 66 | }{ | |
| 67 | { | |
| 68 | name: "tot:100 cur:0 fmt:none", | |
| 69 | fmt: "", | |
| 70 | current: 0, | |
| 71 | total: 100, | |
| 72 | expected: "0 %", | |
| 73 | }, | |
| 74 | { | |
| 75 | name: "tot:100 cur:10 fmt:none", | |
| 76 | fmt: "", | |
| 77 | current: 10, | |
| 78 | total: 100, | |
| 79 | expected: "10 %", | |
| 80 | }, | |
| 81 | { | |
| 82 | name: "tot:100 cur:10 fmt:%.2f", | |
| 83 | fmt: "%.2f", | |
| 84 | current: 10, | |
| 85 | total: 100, | |
| 86 | expected: "10.00%", | |
| 87 | }, | |
| 88 | { | |
| 89 | name: "tot:99 cur:10 fmt:%.2f", | |
| 90 | fmt: "%.2f", | |
| 91 | current: 11, | |
| 92 | total: 99, | |
| 93 | expected: "11.11%", | |
| 94 | }, | |
| 95 | } | |
| 96 | for _, tc := range cases { | |
| 97 | t.Run(tc.name, func(t *testing.T) { | |
| 98 | decor := NewPercentage(tc.fmt) | |
| 99 | stat := Statistics{ | |
| 100 | Total: tc.total, | |
| 101 | Current: tc.current, | |
| 102 | } | |
| 103 | res, _ := decor.Decor(stat) | |
| 104 | if res != tc.expected { | |
| 105 | t.Fatalf("expected: %q, got: %q\n", tc.expected, res) | |
| 106 | } | |
| 107 | }) | |
| 108 | } | |
| 109 | } |
| 20 | 20 | // |
| 21 | 21 | // fmt.Printf("%.1f", FmtAsSpeed(SizeB1024(2048))) |
| 22 | 22 | func FmtAsSpeed(input fmt.Formatter) fmt.Formatter { |
| 23 | return speedFormatter{input} | |
| 23 | return &speedFormatter{input} | |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | 26 | type speedFormatter struct { |
| 27 | 27 | fmt.Formatter |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | func (self speedFormatter) Format(st fmt.State, verb rune) { | |
| 31 | self.Formatter.Format(st, verb) | |
| 30 | func (s *speedFormatter) Format(st fmt.State, verb rune) { | |
| 31 | s.Formatter.Format(st, verb) | |
| 32 | 32 | _, err := io.WriteString(st, "/s") |
| 33 | 33 | if err != nil { |
| 34 | 34 | panic(err) |
| 45 | 45 | } else { |
| 46 | 46 | average = ewma.NewMovingAverage(age) |
| 47 | 47 | } |
| 48 | return MovingAverageSpeed(unit, format, NewThreadSafeMovingAverage(average), wcc...) | |
| 48 | return MovingAverageSpeed(unit, format, average, wcc...) | |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | 51 | // MovingAverageSpeed decorator relies on MovingAverage implementation |
| 68 | 68 | func MovingAverageSpeed(unit interface{}, format string, average ewma.MovingAverage, wcc ...WC) Decorator { |
| 69 | 69 | d := &movingAverageSpeed{ |
| 70 | 70 | WC: initWC(wcc...), |
| 71 | producer: chooseSpeedProducer(unit, format), | |
| 71 | 72 | average: average, |
| 72 | producer: chooseSpeedProducer(unit, format), | |
| 73 | 73 | } |
| 74 | 74 | return d |
| 75 | 75 | } |
| 78 | 78 | WC |
| 79 | 79 | producer func(float64) string |
| 80 | 80 | average ewma.MovingAverage |
| 81 | msg string | |
| 81 | zDur time.Duration | |
| 82 | 82 | } |
| 83 | 83 | |
| 84 | func (d *movingAverageSpeed) Decor(s Statistics) (string, int) { | |
| 85 | if !s.Completed { | |
| 86 | var speed float64 | |
| 87 | if v := d.average.Value(); v > 0 { | |
| 88 | speed = 1 / v | |
| 89 | } | |
| 90 | d.msg = d.producer(speed * 1e9) | |
| 84 | func (d *movingAverageSpeed) Decor(_ Statistics) (string, int) { | |
| 85 | var str string | |
| 86 | // ewma implementation may return 0 before accumulating certain number of samples | |
| 87 | if v := d.average.Value(); v != 0 { | |
| 88 | str = d.producer(1e9 / v) | |
| 89 | } else { | |
| 90 | str = d.producer(0) | |
| 91 | 91 | } |
| 92 | return d.Format(d.msg) | |
| 92 | return d.Format(str) | |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | 95 | func (d *movingAverageSpeed) EwmaUpdate(n int64, dur time.Duration) { |
| 96 | durPerByte := float64(dur) / float64(n) | |
| 97 | if math.IsInf(durPerByte, 0) || math.IsNaN(durPerByte) { | |
| 96 | if n <= 0 { | |
| 97 | d.zDur += dur | |
| 98 | 98 | return |
| 99 | 99 | } |
| 100 | durPerByte := float64(d.zDur+dur) / float64(n) | |
| 101 | if math.IsInf(durPerByte, 0) || math.IsNaN(durPerByte) { | |
| 102 | d.zDur += dur | |
| 103 | return | |
| 104 | } | |
| 105 | d.zDur = 0 | |
| 100 | 106 | d.average.Add(durPerByte) |
| 101 | 107 | } |
| 102 | 108 | |
| 113 | 119 | // |
| 114 | 120 | // `format` printf compatible verb for value, like "%f" or "%d" |
| 115 | 121 | // |
| 116 | // `startTime` start time | |
| 122 | // `start` start time | |
| 117 | 123 | // |
| 118 | 124 | // `wcc` optional WC config |
| 119 | 125 | // |
| 123 | 129 | // unit=SizeB1024(0), format="% .1f" output: "1.0 MiB/s" |
| 124 | 130 | // unit=SizeB1000(0), format="%.1f" output: "1.0MB/s" |
| 125 | 131 | // unit=SizeB1000(0), format="% .1f" output: "1.0 MB/s" |
| 126 | func NewAverageSpeed(unit interface{}, format string, startTime time.Time, wcc ...WC) Decorator { | |
| 132 | func NewAverageSpeed(unit interface{}, format string, start time.Time, wcc ...WC) Decorator { | |
| 127 | 133 | d := &averageSpeed{ |
| 128 | WC: initWC(wcc...), | |
| 129 | startTime: startTime, | |
| 130 | producer: chooseSpeedProducer(unit, format), | |
| 134 | WC: initWC(wcc...), | |
| 135 | start: start, | |
| 136 | producer: chooseSpeedProducer(unit, format), | |
| 131 | 137 | } |
| 132 | 138 | return d |
| 133 | 139 | } |
| 134 | 140 | |
| 135 | 141 | type averageSpeed struct { |
| 136 | 142 | WC |
| 137 | startTime time.Time | |
| 138 | producer func(float64) string | |
| 139 | msg string | |
| 143 | start time.Time | |
| 144 | producer func(float64) string | |
| 145 | msg string | |
| 140 | 146 | } |
| 141 | 147 | |
| 142 | 148 | func (d *averageSpeed) Decor(s Statistics) (string, int) { |
| 143 | 149 | if !s.Completed { |
| 144 | speed := float64(s.Current) / float64(time.Since(d.startTime)) | |
| 150 | speed := float64(s.Current) / float64(time.Since(d.start)) | |
| 145 | 151 | d.msg = d.producer(speed * 1e9) |
| 146 | 152 | } |
| 147 | 153 | return d.Format(d.msg) |
| 148 | 154 | } |
| 149 | 155 | |
| 150 | func (d *averageSpeed) AverageAdjust(startTime time.Time) { | |
| 151 | d.startTime = startTime | |
| 156 | func (d *averageSpeed) AverageAdjust(start time.Time) { | |
| 157 | d.start = start | |
| 152 | 158 | } |
| 153 | 159 | |
| 154 | 160 | func chooseSpeedProducer(unit interface{}, format string) func(float64) string { |
| 24 | 24 | want: " Test", |
| 25 | 25 | }, |
| 26 | 26 | { |
| 27 | decorator: decor.Name("Test", decor.WC{W: 10, C: decor.DidentRight}), | |
| 27 | decorator: decor.Name("Test", decor.WC{W: 10, C: decor.DindentRight}), | |
| 28 | 28 | want: "Test ", |
| 29 | 29 | }, |
| 30 | 30 | } |
| 87 | 87 | testDecoratorConcurrently(t, testCases) |
| 88 | 88 | } |
| 89 | 89 | |
| 90 | func TestPercentageDwidthSyncDidentRight(t *testing.T) { | |
| 90 | func TestPercentageDwidthSyncDindentRight(t *testing.T) { | |
| 91 | 91 | |
| 92 | 92 | testCases := [][]step{ |
| 93 | 93 | { |
| 621 | 621 | want: " [>-----------------------------------------------------------------------------------------------] ", |
| 622 | 622 | }, |
| 623 | 623 | { |
| 624 | style: BarStyle().Tip(""), | |
| 625 | name: "t,c{100,1}empty_tip", | |
| 626 | total: 100, | |
| 627 | current: 1, | |
| 628 | want: " [=-----------------------------------------------------------------------------------------------] ", | |
| 629 | }, | |
| 630 | { | |
| 624 | 631 | style: BarStyle(), |
| 625 | 632 | name: "t,c{100,1}trim", |
| 626 | 633 | total: 100, |
| 634 | 641 | total: 100, |
| 635 | 642 | current: 99, |
| 636 | 643 | want: " [==============================================================================================>-] ", |
| 644 | }, | |
| 645 | { | |
| 646 | style: BarStyle().Tip(""), | |
| 647 | name: "t,c{100,99}empty_tip", | |
| 648 | total: 100, | |
| 649 | current: 99, | |
| 650 | want: " [===============================================================================================-] ", | |
| 637 | 651 | }, |
| 638 | 652 | { |
| 639 | 653 | style: BarStyle(), |
| 766 | 780 | var tmpBuf bytes.Buffer |
| 767 | 781 | for tw, cases := range testSuite { |
| 768 | 782 | for _, tc := range cases { |
| 769 | s := newTestState(tc.style.Build()) | |
| 770 | s.reqWidth = tc.barWidth | |
| 771 | s.total = tc.total | |
| 783 | ps := pState{reqWidth: tc.barWidth} | |
| 784 | s := ps.makeBarState(tc.total, tc.style.Build()) | |
| 772 | 785 | s.current = tc.current |
| 773 | 786 | s.trimSpace = tc.trim |
| 774 | 787 | s.refill = tc.refill |
| 775 | s.completed = tc.total > 0 && tc.current >= tc.total | |
| 788 | r, err := s.draw(s.newStatistics(tw)) | |
| 789 | if err != nil { | |
| 790 | t.Fatalf("tw: %d case %q draw error: %s", tw, tc.name, err.Error()) | |
| 791 | } | |
| 776 | 792 | tmpBuf.Reset() |
| 777 | r, err := s.draw(newStatistics(tw, s)) | |
| 778 | if err != nil { | |
| 779 | t.FailNow() | |
| 780 | } | |
| 781 | 793 | _, err = tmpBuf.ReadFrom(r) |
| 782 | 794 | if err != nil { |
| 783 | 795 | t.FailNow() |
| 784 | 796 | } |
| 785 | 797 | by := tmpBuf.Bytes() |
| 786 | ||
| 787 | 798 | got := string(by[:len(by)-1]) |
| 788 | 799 | if !utf8.ValidString(got) { |
| 789 | 800 | t.Fail() |
| 1221 | 1232 | var tmpBuf bytes.Buffer |
| 1222 | 1233 | for tw, cases := range testSuite { |
| 1223 | 1234 | for _, tc := range cases { |
| 1224 | s := newTestState(tc.style.Build()) | |
| 1225 | s.reqWidth = tc.barWidth | |
| 1226 | s.total = tc.total | |
| 1235 | ps := pState{reqWidth: tc.barWidth} | |
| 1236 | s := ps.makeBarState(tc.total, tc.style.Build()) | |
| 1227 | 1237 | s.current = tc.current |
| 1228 | 1238 | s.trimSpace = tc.trim |
| 1229 | 1239 | s.refill = tc.refill |
| 1230 | s.completed = tc.total > 0 && tc.current >= tc.total | |
| 1240 | r, err := s.draw(s.newStatistics(tw)) | |
| 1241 | if err != nil { | |
| 1242 | t.Fatalf("tw: %d case %q draw error: %s", tw, tc.name, err.Error()) | |
| 1243 | } | |
| 1231 | 1244 | tmpBuf.Reset() |
| 1232 | r, err := s.draw(newStatistics(tw, s)) | |
| 1233 | if err != nil { | |
| 1234 | t.FailNow() | |
| 1235 | } | |
| 1236 | 1245 | _, err = tmpBuf.ReadFrom(r) |
| 1237 | 1246 | if err != nil { |
| 1238 | 1247 | t.FailNow() |
| 1398 | 1407 | var tmpBuf bytes.Buffer |
| 1399 | 1408 | for tw, cases := range testSuite { |
| 1400 | 1409 | for _, tc := range cases { |
| 1401 | s := newTestState(tc.style.Build()) | |
| 1402 | s.reqWidth = tc.barWidth | |
| 1403 | s.total = tc.total | |
| 1410 | ps := pState{reqWidth: tc.barWidth} | |
| 1411 | s := ps.makeBarState(tc.total, tc.style.Build()) | |
| 1404 | 1412 | s.current = tc.current |
| 1405 | 1413 | s.trimSpace = tc.trim |
| 1406 | 1414 | s.refill = tc.refill |
| 1407 | s.completed = tc.total > 0 && tc.current >= tc.total | |
| 1415 | r, err := s.draw(s.newStatistics(tw)) | |
| 1416 | if err != nil { | |
| 1417 | t.Fatalf("tw: %d case %q draw error: %s", tw, tc.name, err.Error()) | |
| 1418 | } | |
| 1408 | 1419 | tmpBuf.Reset() |
| 1409 | r, err := s.draw(newStatistics(tw, s)) | |
| 1410 | if err != nil { | |
| 1411 | t.FailNow() | |
| 1412 | } | |
| 1413 | 1420 | _, err = tmpBuf.ReadFrom(r) |
| 1414 | 1421 | if err != nil { |
| 1415 | 1422 | t.FailNow() |
| 1426 | 1433 | } |
| 1427 | 1434 | } |
| 1428 | 1435 | } |
| 1429 | ||
| 1430 | func newTestState(filler BarFiller) *bState { | |
| 1431 | bs := &bState{ | |
| 1432 | filler: filler, | |
| 1433 | } | |
| 1434 | for i := 0; i < len(bs.buffers); i++ { | |
| 1435 | bs.buffers[i] = bytes.NewBuffer(make([]byte, 0, 512)) | |
| 1436 | } | |
| 1437 | return bs | |
| 1438 | } | |
| 21 | 21 | mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), |
| 22 | 22 | mpb.PrependDecorators( |
| 23 | 23 | // display our name with one space on the right |
| 24 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), | |
| 24 | decor.Name(name, decor.WC{C: decor.DindentRight | decor.DextraSpace}), | |
| 25 | 25 | // replace ETA decorator with "done" message, OnComplete event |
| 26 | decor.OnComplete( | |
| 27 | decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done", | |
| 28 | ), | |
| 26 | decor.OnComplete(decor.AverageETA(decor.ET_STYLE_GO), "done"), | |
| 29 | 27 | ), |
| 30 | 28 | mpb.AppendDecorators(decor.Percentage()), |
| 31 | 29 | ) |
| 2 | 2 | require ( |
| 3 | 3 | github.com/VividCortex/ewma v1.2.0 |
| 4 | 4 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d |
| 5 | github.com/mattn/go-runewidth v0.0.15 | |
| 6 | golang.org/x/sys v0.11.0 | |
| 5 | github.com/mattn/go-runewidth v0.0.16 | |
| 6 | golang.org/x/sys v0.24.0 | |
| 7 | 7 | ) |
| 8 | 8 | |
| 9 | require github.com/rivo/uniseg v0.4.4 // indirect | |
| 9 | require github.com/rivo/uniseg v0.4.7 // indirect | |
| 10 | 10 | |
| 11 | 11 | go 1.17 |
| 1 | 1 | github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= |
| 2 | 2 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= |
| 3 | 3 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= |
| 4 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | |
| 5 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | |
| 4 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | |
| 5 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | |
| 6 | 6 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= |
| 7 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= | |
| 8 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | |
| 9 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= | |
| 10 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
| 7 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | |
| 8 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | |
| 9 | golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= | |
| 10 | golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
| 2 | 2 | import "math" |
| 3 | 3 | |
| 4 | 4 | // Percentage is a helper function, to calculate percentage. |
| 5 | func Percentage(total, current int64, width uint) float64 { | |
| 6 | if total <= 0 { | |
| 5 | func Percentage(total, current, width uint) float64 { | |
| 6 | if total == 0 { | |
| 7 | 7 | return 0 |
| 8 | 8 | } |
| 9 | 9 | if current >= total { |
| 10 | 10 | return float64(width) |
| 11 | 11 | } |
| 12 | return float64(int64(width)*current) / float64(total) | |
| 12 | return float64(width*current) / float64(total) | |
| 13 | 13 | } |
| 14 | 14 | |
| 15 | 15 | // PercentageRound same as Percentage but with math.Round. |
| 16 | 16 | func PercentageRound(total, current int64, width uint) float64 { |
| 17 | return math.Round(Percentage(total, current, width)) | |
| 17 | if total < 0 || current < 0 { | |
| 18 | return 0 | |
| 19 | } | |
| 20 | return math.Round(Percentage(uint(total), uint(current), width)) | |
| 18 | 21 | } |
| 2 | 2 | // CheckRequestedWidth checks that requested width doesn't overflow |
| 3 | 3 | // available width |
| 4 | 4 | func CheckRequestedWidth(requested, available int) int { |
| 5 | if requested < 1 || requested >= available { | |
| 5 | if requested < 1 || requested > available { | |
| 6 | 6 | return available |
| 7 | 7 | } |
| 8 | 8 | return requested |
| 13 | 13 | "github.com/vbauerster/mpb/v8/decor" |
| 14 | 14 | ) |
| 15 | 15 | |
| 16 | const ( | |
| 17 | defaultRefreshRate = 150 * time.Millisecond | |
| 18 | ) | |
| 19 | ||
| 20 | // DoneError represents an error when `*mpb.Progress` is done but its functionality is requested. | |
| 21 | var DoneError = fmt.Errorf("%T instance can't be reused after it's done", (*Progress)(nil)) | |
| 16 | const defaultRefreshRate = 150 * time.Millisecond | |
| 17 | ||
| 18 | // DoneError represents use after `(*Progress).Wait()` error. | |
| 19 | var DoneError = fmt.Errorf("%T instance can't be reused after %[1]T.Wait()", (*Progress)(nil)) | |
| 22 | 20 | |
| 23 | 21 | // Progress represents a container that renders one or more progress bars. |
| 24 | 22 | type Progress struct { |
| 54 | 52 | } |
| 55 | 53 | |
| 56 | 54 | // New creates new Progress container instance. It's not possible to |
| 57 | // reuse instance after (*Progress).Wait method has been called. | |
| 55 | // reuse instance after `(*Progress).Wait` method has been called. | |
| 58 | 56 | func New(options ...ContainerOption) *Progress { |
| 59 | 57 | return NewWithContext(context.Background(), options...) |
| 60 | 58 | } |
| 61 | 59 | |
| 62 | 60 | // NewWithContext creates new Progress container instance with provided |
| 63 | // context. It's not possible to reuse instance after (*Progress).Wait | |
| 61 | // context. It's not possible to reuse instance after `(*Progress).Wait` | |
| 64 | 62 | // method has been called. |
| 65 | 63 | func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { |
| 66 | 64 | if ctx == nil { |
| 73 | 71 | dropS: make(chan struct{}), |
| 74 | 72 | dropD: make(chan struct{}), |
| 75 | 73 | renderReq: make(chan time.Time), |
| 74 | popPriority: math.MinInt32, | |
| 76 | 75 | refreshRate: defaultRefreshRate, |
| 77 | popPriority: math.MinInt32, | |
| 78 | 76 | queueBars: make(map[*Bar]*Bar), |
| 79 | 77 | output: os.Stdout, |
| 80 | 78 | debugOut: io.Discard, |
| 127 | 125 | |
| 128 | 126 | // New creates a bar by calling `Build` method on provided `BarFillerBuilder`. |
| 129 | 127 | func (p *Progress) New(total int64, builder BarFillerBuilder, options ...BarOption) *Bar { |
| 128 | if builder == nil { | |
| 129 | return p.MustAdd(total, nil, options...) | |
| 130 | } | |
| 130 | 131 | return p.MustAdd(total, builder.Build(), options...) |
| 131 | 132 | } |
| 132 | 133 | |
| 133 | 134 | // MustAdd creates a bar which renders itself by provided BarFiller. |
| 134 | 135 | // If `total <= 0` triggering complete event by increment methods is |
| 135 | // disabled. Panics if *Progress instance is done, i.e. called after | |
| 136 | // (*Progress).Wait(). | |
| 136 | // disabled. Panics if called after `(*Progress).Wait()`. | |
| 137 | 137 | func (p *Progress) MustAdd(total int64, filler BarFiller, options ...BarOption) *Bar { |
| 138 | 138 | bar, err := p.Add(total, filler, options...) |
| 139 | 139 | if err != nil { |
| 144 | 144 | |
| 145 | 145 | // Add creates a bar which renders itself by provided BarFiller. |
| 146 | 146 | // If `total <= 0` triggering complete event by increment methods |
| 147 | // is disabled. If *Progress instance is done, i.e. called after | |
| 148 | // (*Progress).Wait(), return error == DoneError. | |
| 147 | // is disabled. If called after `(*Progress).Wait()` then | |
| 148 | // `(nil, DoneError)` is returned. | |
| 149 | 149 | func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) (*Bar, error) { |
| 150 | 150 | if filler == nil { |
| 151 | 151 | filler = NopStyle().Build() |
| 152 | } | |
| 153 | type result struct { | |
| 154 | bar *Bar | |
| 155 | bs *bState | |
| 156 | } | |
| 157 | ch := make(chan result) | |
| 152 | } else if f, ok := filler.(BarFillerFunc); ok && f == nil { | |
| 153 | filler = NopStyle().Build() | |
| 154 | } | |
| 155 | ch := make(chan *Bar) | |
| 158 | 156 | select { |
| 159 | 157 | case p.operateState <- func(ps *pState) { |
| 160 | 158 | bs := ps.makeBarState(total, filler, options...) |
| 165 | 163 | ps.hm.push(bar, true) |
| 166 | 164 | } |
| 167 | 165 | ps.idCount++ |
| 168 | ch <- result{bar, bs} | |
| 166 | ch <- bar | |
| 169 | 167 | }: |
| 170 | res := <-ch | |
| 171 | bar, bs := res.bar, res.bs | |
| 172 | bar.TraverseDecorators(func(d decor.Decorator) { | |
| 173 | if d, ok := d.(decor.AverageDecorator); ok { | |
| 174 | bs.averageDecorators = append(bs.averageDecorators, d) | |
| 175 | } | |
| 176 | if d, ok := d.(decor.EwmaDecorator); ok { | |
| 177 | bs.ewmaDecorators = append(bs.ewmaDecorators, d) | |
| 178 | } | |
| 179 | if d, ok := d.(decor.ShutdownListener); ok { | |
| 180 | bs.shutdownListeners = append(bs.shutdownListeners, d) | |
| 181 | } | |
| 182 | }) | |
| 183 | return bar, nil | |
| 168 | return <-ch, nil | |
| 184 | 169 | case <-p.done: |
| 185 | 170 | return nil, DoneError |
| 186 | 171 | } |
| 191 | 176 | select { |
| 192 | 177 | case p.operateState <- func(s *pState) { s.hm.iter(iter, drop) }: |
| 193 | 178 | for b := range iter { |
| 194 | if cb(b) { | |
| 179 | if !cb(b) { | |
| 195 | 180 | close(drop) |
| 196 | 181 | break |
| 197 | 182 | } |
| 202 | 187 | |
| 203 | 188 | // UpdateBarPriority either immediately or lazy. |
| 204 | 189 | // With lazy flag order is updated after the next refresh cycle. |
| 205 | // If you don't care about laziness just use *Bar.SetPriority(int). | |
| 190 | // If you don't care about laziness just use `(*Bar).SetPriority(int)`. | |
| 206 | 191 | func (p *Progress) UpdateBarPriority(b *Bar, priority int, lazy bool) { |
| 207 | 192 | if b == nil { |
| 208 | 193 | return |
| 214 | 199 | } |
| 215 | 200 | |
| 216 | 201 | // Write is implementation of io.Writer. |
| 217 | // Writing to `*mpb.Progress` will print lines above a running bar. | |
| 202 | // Writing to `*Progress` will print lines above a running bar. | |
| 218 | 203 | // Writes aren't flushed immediately, but at next refresh cycle. |
| 219 | // If Write is called after `*mpb.Progress` is done, `mpb.DoneError` | |
| 204 | // If called after `(*Progress).Wait()` then `(0, DoneError)` | |
| 220 | 205 | // is returned. |
| 221 | 206 | func (p *Progress) Write(b []byte) (int, error) { |
| 222 | 207 | type result struct { |
| 237 | 222 | } |
| 238 | 223 | |
| 239 | 224 | // Wait waits for all bars to complete and finally shutdowns container. After |
| 240 | // this method has been called, there is no way to reuse (*Progress) instance. | |
| 225 | // this method has been called, there is no way to reuse `*Progress` instance. | |
| 241 | 226 | func (p *Progress) Wait() { |
| 227 | p.bwg.Wait() | |
| 228 | p.Shutdown() | |
| 242 | 229 | // wait for user wg, if any |
| 243 | 230 | if p.uwg != nil { |
| 244 | 231 | p.uwg.Wait() |
| 245 | 232 | } |
| 246 | ||
| 247 | p.bwg.Wait() | |
| 248 | p.Shutdown() | |
| 249 | } | |
| 250 | ||
| 251 | // Shutdown cancels any running bar immediately and then shutdowns (*Progress) | |
| 233 | } | |
| 234 | ||
| 235 | // Shutdown cancels any running bar immediately and then shutdowns `*Progress` | |
| 252 | 236 | // instance. Normally this method shouldn't be called unless you know what you |
| 253 | // are doing. Proper way to shutdown is to call (*Progress).Wait() instead. | |
| 237 | // are doing. Proper way to shutdown is to call `(*Progress).Wait()` instead. | |
| 254 | 238 | func (p *Progress) Shutdown() { |
| 255 | 239 | p.cancel() |
| 256 | 240 | p.pwg.Wait() |
| 258 | 242 | |
| 259 | 243 | func (p *Progress) serve(s *pState, cw *cwriter.Writer) { |
| 260 | 244 | defer p.pwg.Done() |
| 261 | render := func() error { return s.render(cw) } | |
| 262 | 245 | var err error |
| 246 | var w *cwriter.Writer | |
| 247 | renderReq := s.renderReq | |
| 248 | operateState := p.operateState | |
| 249 | interceptIO := p.interceptIO | |
| 250 | ||
| 251 | if s.delayRC != nil { | |
| 252 | w = cwriter.New(io.Discard) | |
| 253 | } else { | |
| 254 | w, cw = cw, nil | |
| 255 | } | |
| 263 | 256 | |
| 264 | 257 | for { |
| 265 | 258 | select { |
| 266 | case op := <-p.operateState: | |
| 259 | case <-s.delayRC: | |
| 260 | w, cw = cw, nil | |
| 261 | s.delayRC = nil | |
| 262 | case op := <-operateState: | |
| 267 | 263 | op(s) |
| 268 | case fn := <-p.interceptIO: | |
| 269 | fn(cw) | |
| 270 | case <-s.renderReq: | |
| 271 | e := render() | |
| 272 | if e != nil { | |
| 264 | case fn := <-interceptIO: | |
| 265 | fn(w) | |
| 266 | case <-renderReq: | |
| 267 | err = s.render(w) | |
| 268 | if err != nil { | |
| 269 | // (*pState).(autoRefreshListener|manualRefreshListener) may block | |
| 270 | // if not launching following short lived goroutine | |
| 271 | go func() { | |
| 272 | for { | |
| 273 | select { | |
| 274 | case <-s.renderReq: | |
| 275 | case <-p.done: | |
| 276 | return | |
| 277 | } | |
| 278 | } | |
| 279 | }() | |
| 273 | 280 | p.cancel() // cancel all bars |
| 274 | render = func() error { return nil } | |
| 275 | err = e | |
| 281 | renderReq = nil | |
| 282 | operateState = nil | |
| 283 | interceptIO = nil | |
| 276 | 284 | } |
| 277 | 285 | case <-p.done: |
| 278 | update := make(chan bool) | |
| 279 | for s.autoRefresh && err == nil { | |
| 280 | s.hm.state(update) | |
| 281 | if <-update { | |
| 282 | err = render() | |
| 283 | } else { | |
| 284 | break | |
| 285 | } | |
| 286 | } | |
| 287 | 286 | if err != nil { |
| 288 | 287 | _, _ = fmt.Fprintln(s.debugOut, err.Error()) |
| 288 | } else if s.autoRefresh { | |
| 289 | update := make(chan bool) | |
| 290 | for i := 0; i == 0 || <-update; i++ { | |
| 291 | if err := s.render(w); err != nil { | |
| 292 | _, _ = fmt.Fprintln(s.debugOut, err.Error()) | |
| 293 | break | |
| 294 | } | |
| 295 | s.hm.state(update) | |
| 296 | } | |
| 289 | 297 | } |
| 290 | 298 | s.hm.end(s.shutdownNotifier) |
| 291 | 299 | return |
| 293 | 301 | } |
| 294 | 302 | } |
| 295 | 303 | |
| 296 | func (s pState) autoRefreshListener(done chan struct{}) { | |
| 297 | if s.delayRC != nil { | |
| 298 | <-s.delayRC | |
| 299 | } | |
| 304 | func (s *pState) autoRefreshListener(done chan struct{}) { | |
| 300 | 305 | ticker := time.NewTicker(s.refreshRate) |
| 301 | 306 | defer ticker.Stop() |
| 302 | 307 | for { |
| 310 | 315 | } |
| 311 | 316 | } |
| 312 | 317 | |
| 313 | func (s pState) manualRefreshListener(done chan struct{}) { | |
| 318 | func (s *pState) manualRefreshListener(done chan struct{}) { | |
| 314 | 319 | for { |
| 315 | 320 | select { |
| 316 | 321 | case x := <-s.manualRC: |
| 342 | 347 | if s.reqWidth > 0 { |
| 343 | 348 | width = s.reqWidth |
| 344 | 349 | } else { |
| 345 | width = 100 | |
| 346 | } | |
| 347 | height = 100 | |
| 350 | width = 80 | |
| 351 | } | |
| 352 | height = width | |
| 348 | 353 | } |
| 349 | 354 | |
| 350 | 355 | for b := range iter { |
| 356 | 361 | |
| 357 | 362 | func (s *pState) flush(cw *cwriter.Writer, height int) error { |
| 358 | 363 | var wg sync.WaitGroup |
| 359 | defer wg.Wait() // waiting for all s.hm.push to complete | |
| 364 | defer wg.Wait() // waiting for all s.push to complete | |
| 360 | 365 | |
| 361 | 366 | var popCount int |
| 362 | 367 | var rows []io.Reader |
| 380 | 385 | _, _ = io.Copy(io.Discard, row) |
| 381 | 386 | } |
| 382 | 387 | } |
| 383 | if frame.shutdown != 0 && !frame.done { | |
| 388 | ||
| 389 | switch frame.shutdown { | |
| 390 | case 1: | |
| 391 | b.cancel() | |
| 384 | 392 | if qb, ok := s.queueBars[b]; ok { |
| 385 | b.cancel() | |
| 386 | 393 | delete(s.queueBars, b) |
| 387 | 394 | qb.priority = b.priority |
| 388 | 395 | wg.Add(1) |
| 389 | go func(b *Bar) { | |
| 390 | s.hm.push(b, true) | |
| 391 | wg.Done() | |
| 392 | }(qb) | |
| 396 | go s.push(&wg, qb, true) | |
| 397 | } else if s.popCompleted && !frame.noPop { | |
| 398 | b.priority = s.popPriority | |
| 399 | s.popPriority++ | |
| 400 | wg.Add(1) | |
| 401 | go s.push(&wg, b, false) | |
| 402 | } else if !frame.rmOnComplete { | |
| 403 | wg.Add(1) | |
| 404 | go s.push(&wg, b, false) | |
| 405 | } | |
| 406 | case 2: | |
| 407 | if s.popCompleted && !frame.noPop { | |
| 408 | popCount += usedRows | |
| 393 | 409 | continue |
| 394 | 410 | } |
| 395 | if s.popCompleted && !frame.noPop { | |
| 396 | switch frame.shutdown { | |
| 397 | case 1: | |
| 398 | b.priority = s.popPriority | |
| 399 | s.popPriority++ | |
| 400 | default: | |
| 401 | b.cancel() | |
| 402 | popCount += usedRows | |
| 403 | continue | |
| 404 | } | |
| 405 | } else if frame.rmOnComplete { | |
| 406 | b.cancel() | |
| 407 | continue | |
| 408 | } else { | |
| 409 | b.cancel() | |
| 410 | } | |
| 411 | } | |
| 412 | wg.Add(1) | |
| 413 | go func(b *Bar) { | |
| 414 | s.hm.push(b, false) | |
| 415 | wg.Done() | |
| 416 | }(b) | |
| 411 | fallthrough | |
| 412 | default: | |
| 413 | wg.Add(1) | |
| 414 | go s.push(&wg, b, false) | |
| 415 | } | |
| 417 | 416 | } |
| 418 | 417 | |
| 419 | 418 | for i := len(rows) - 1; i >= 0; i-- { |
| 424 | 423 | } |
| 425 | 424 | |
| 426 | 425 | return cw.Flush(len(rows) - popCount) |
| 426 | } | |
| 427 | ||
| 428 | func (s *pState) push(wg *sync.WaitGroup, b *Bar, sync bool) { | |
| 429 | s.hm.push(b, sync) | |
| 430 | wg.Done() | |
| 427 | 431 | } |
| 428 | 432 | |
| 429 | 433 | func (s pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState { |
| 435 | 439 | filler: filler, |
| 436 | 440 | renderReq: s.renderReq, |
| 437 | 441 | autoRefresh: s.autoRefresh, |
| 442 | extender: func(_ decor.Statistics, rows ...io.Reader) ([]io.Reader, error) { | |
| 443 | return rows, nil | |
| 444 | }, | |
| 438 | 445 | } |
| 439 | 446 | |
| 440 | 447 | if total > 0 { |
| 447 | 454 | } |
| 448 | 455 | } |
| 449 | 456 | |
| 450 | for i := 0; i < len(bs.buffers); i++ { | |
| 451 | bs.buffers[i] = bytes.NewBuffer(make([]byte, 0, 512)) | |
| 452 | } | |
| 457 | bs.buffers[0] = bytes.NewBuffer(make([]byte, 0, 128)) // prepend | |
| 458 | bs.buffers[1] = bytes.NewBuffer(make([]byte, 0, 128)) // append | |
| 459 | bs.buffers[2] = bytes.NewBuffer(make([]byte, 0, 256)) // filler | |
| 453 | 460 | |
| 454 | 461 | return bs |
| 455 | 462 | } |
| 68 | 68 | if rc, ok := r.(io.ReadCloser); ok { |
| 69 | 69 | return rc |
| 70 | 70 | } |
| 71 | return toNopReadCloser(r) | |
| 71 | return io.NopCloser(r) | |
| 72 | 72 | } |
| 73 | ||
| 74 | func toNopReadCloser(r io.Reader) io.ReadCloser { | |
| 75 | if _, ok := r.(io.WriterTo); ok { | |
| 76 | return nopReadCloserWriterTo{r} | |
| 77 | } | |
| 78 | return nopReadCloser{r} | |
| 79 | } | |
| 80 | ||
| 81 | type nopReadCloser struct { | |
| 82 | io.Reader | |
| 83 | } | |
| 84 | ||
| 85 | func (nopReadCloser) Close() error { return nil } | |
| 86 | ||
| 87 | type nopReadCloserWriterTo struct { | |
| 88 | io.Reader | |
| 89 | } | |
| 90 | ||
| 91 | func (nopReadCloserWriterTo) Close() error { return nil } | |
| 92 | ||
| 93 | func (c nopReadCloserWriterTo) WriteTo(w io.Writer) (int64, error) { | |
| 94 | return c.Reader.(io.WriterTo).WriteTo(w) | |
| 95 | } |