Update upstream source from tag 'upstream/7.3.2'
Update to upstream version '7.3.2'
with Debian dir 0cf71810348a1d54b63ec662601c0870df254d1b
Andreas Tille
4 years ago
| 0 | name: Test | |
| 1 | ||
| 2 | on: [push, pull_request] | |
| 3 | ||
| 4 | jobs: | |
| 5 | test: | |
| 6 | strategy: | |
| 7 | matrix: | |
| 8 | go-version: [1.16, 1.17] | |
| 9 | os: [ubuntu-latest, macos-latest, windows-latest] | |
| 10 | runs-on: ${{ matrix.os }} | |
| 11 | steps: | |
| 12 | - name: Setup Go | |
| 13 | uses: actions/setup-go@v2 | |
| 14 | with: | |
| 15 | go-version: ${{ matrix.go-version }} | |
| 16 | - name: Checkout code | |
| 17 | uses: actions/checkout@v2 | |
| 18 | - uses: actions/cache@v2 | |
| 19 | with: | |
| 20 | # In order: | |
| 21 | # * Module download cache | |
| 22 | # * Build cache (Linux) | |
| 23 | # * Build cache (Mac) | |
| 24 | # * Build cache (Windows) | |
| 25 | path: | | |
| 26 | ~/go/pkg/mod | |
| 27 | ~/.cache/go-build | |
| 28 | ~/Library/Caches/go-build | |
| 29 | %LocalAppData%\go-build | |
| 30 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
| 31 | restore-keys: | | |
| 32 | ${{ runner.os }}-go- | |
| 33 | - name: Test | |
| 34 | run: go test -race ./... |
| 0 | language: go | |
| 1 | ||
| 2 | go: | |
| 3 | - 1.14.x | |
| 4 | ||
| 5 | script: | |
| 6 | - go test -race ./... | |
| 7 | - for i in _examples/*/; do go build $i/*.go || exit 1; done |
| 0 | 0 | # Multi Progress Bar |
| 1 | 1 | |
| 2 | [](https://godoc.org/github.com/vbauerster/mpb) | |
| 3 | [](https://travis-ci.org/vbauerster/mpb) | |
| 4 | [](https://goreportcard.com/report/github.com/vbauerster/mpb) | |
| 2 | [](https://pkg.go.dev/github.com/vbauerster/mpb/v7) | |
| 3 | [](https://github.com/vbauerster/mpb/actions/workflows/test.yml) | |
| 4 | [](https://www.paypal.me/vbauerster) | |
| 5 | 5 | |
| 6 | 6 | **mpb** is a Go lib for rendering progress bars in terminal applications. |
| 7 | 7 | |
| 8 | 8 | ## Features |
| 9 | 9 | |
| 10 | * __Multiple Bars__: Multiple progress bars are supported | |
| 11 | * __Dynamic Total__: Set total while bar is running | |
| 12 | * __Dynamic Add/Remove__: Dynamically add or remove bars | |
| 13 | * __Cancellation__: Cancel whole rendering process | |
| 14 | * __Predefined Decorators__: Elapsed time, [ewma](https://github.com/VividCortex/ewma) based ETA, Percentage, Bytes counter | |
| 15 | * __Decorator's width sync__: Synchronized decorator's width among multiple bars | |
| 10 | - **Multiple Bars**: Multiple progress bars are supported | |
| 11 | - **Dynamic Total**: Set total while bar is running | |
| 12 | - **Dynamic Add/Remove**: Dynamically add or remove bars | |
| 13 | - **Cancellation**: Cancel whole rendering process | |
| 14 | - **Predefined Decorators**: Elapsed time, [ewma](https://github.com/VividCortex/ewma) based ETA, Percentage, Bytes counter | |
| 15 | - **Decorator's width sync**: Synchronized decorator's width among multiple bars | |
| 16 | 16 | |
| 17 | 17 | ## Usage |
| 18 | 18 | |
| 19 | 19 | #### [Rendering single bar](_examples/singleBar/main.go) |
| 20 | ||
| 20 | 21 | ```go |
| 21 | 22 | package main |
| 22 | 23 | |
| 24 | 25 | "math/rand" |
| 25 | 26 | "time" |
| 26 | 27 | |
| 27 | "github.com/vbauerster/mpb/v5" | |
| 28 | "github.com/vbauerster/mpb/v5/decor" | |
| 28 | "github.com/vbauerster/mpb/v7" | |
| 29 | "github.com/vbauerster/mpb/v7/decor" | |
| 29 | 30 | ) |
| 30 | 31 | |
| 31 | 32 | func main() { |
| 34 | 35 | |
| 35 | 36 | total := 100 |
| 36 | 37 | name := "Single Bar:" |
| 37 | // adding a single bar, which will inherit container's width | |
| 38 | bar := p.AddBar(int64(total), | |
| 39 | // override DefaultBarStyle, which is "[=>-]<+" | |
| 40 | mpb.BarStyle("╢▌▌░╟"), | |
| 38 | // create a single bar, which will inherit container's width | |
| 39 | bar := p.New(int64(total), | |
| 40 | // BarFillerBuilder with custom style | |
| 41 | mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), | |
| 41 | 42 | mpb.PrependDecorators( |
| 42 | 43 | // display our name with one space on the right |
| 43 | 44 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 60 | 61 | ``` |
| 61 | 62 | |
| 62 | 63 | #### [Rendering multiple bars](_examples/multiBars/main.go) |
| 64 | ||
| 63 | 65 | ```go |
| 64 | 66 | var wg sync.WaitGroup |
| 65 | // pass &wg (optional), so p will wait for it eventually | |
| 67 | // passed wg will be accounted at p.Wait() call | |
| 66 | 68 | p := mpb.New(mpb.WithWaitGroup(&wg)) |
| 67 | 69 | total, numBars := 100, 3 |
| 68 | 70 | wg.Add(numBars) |
| 80 | 82 | // replace ETA decorator with "done" message, OnComplete event |
| 81 | 83 | decor.OnComplete( |
| 82 | 84 | // ETA decorator with ewma age of 60 |
| 83 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 85 | decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncWidth), "done", | |
| 84 | 86 | ), |
| 85 | 87 | ), |
| 86 | 88 | ) |
| 100 | 102 | } |
| 101 | 103 | }() |
| 102 | 104 | } |
| 103 | // Waiting for passed &wg and for all bars to complete and flush | |
| 105 | // wait for passed wg and for all bars to complete and flush | |
| 104 | 106 | p.Wait() |
| 105 | 107 | ``` |
| 106 | 108 | |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 6 | 6 | "sync" |
| 7 | 7 | "time" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v7" | |
| 10 | "github.com/vbauerster/mpb/v7/decor" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | func main() { |
| 14 | 14 | var wg sync.WaitGroup |
| 15 | // passed wg will be accounted at p.Wait() call | |
| 15 | 16 | p := mpb.New(mpb.WithWaitGroup(&wg)) |
| 16 | 17 | total, numBars := 100, 3 |
| 17 | 18 | wg.Add(numBars) |
| 18 | 19 | |
| 19 | 20 | for i := 0; i < numBars; i++ { |
| 20 | 21 | name := fmt.Sprintf("Bar#%d:", i) |
| 21 | efn := func(w io.Writer, width int, s *decor.Statistics) { | |
| 22 | efn := func(w io.Writer, _ int, s decor.Statistics) { | |
| 22 | 23 | if s.Completed { |
| 23 | 24 | fmt.Fprintf(w, "Bar id: %d has been completed\n", s.ID) |
| 24 | 25 | } |
| 54 | 55 | } |
| 55 | 56 | }() |
| 56 | 57 | } |
| 57 | // wait for all bars to complete and flush | |
| 58 | // wait for passed wg and for all bars to complete and flush | |
| 58 | 59 | p.Wait() |
| 59 | 60 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 6 | 6 | "sync" |
| 7 | 7 | "time" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v7" | |
| 10 | "github.com/vbauerster/mpb/v7/decor" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | func main() { |
| 15 | 15 | defer cancel() |
| 16 | 16 | |
| 17 | 17 | var wg sync.WaitGroup |
| 18 | // passed wg will be accounted at p.Wait() call | |
| 18 | 19 | p := mpb.NewWithContext(ctx, mpb.WithWaitGroup(&wg)) |
| 19 | 20 | total := 300 |
| 20 | 21 | numBars := 3 |
| 48 | 49 | } |
| 49 | 50 | }() |
| 50 | 51 | } |
| 51 | ||
| 52 | // wait for passed wg and for all bars to complete and flush | |
| 52 | 53 | p.Wait() |
| 53 | 54 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func init() { |
| 15 | 15 | |
| 16 | 16 | func main() { |
| 17 | 17 | doneWg := new(sync.WaitGroup) |
| 18 | p := mpb.New(mpb.WithWidth(64), mpb.WithWaitGroup(doneWg)) | |
| 18 | // passed doneWg will be accounted at p.Wait() call | |
| 19 | p := mpb.New(mpb.WithWaitGroup(doneWg)) | |
| 19 | 20 | numBars := 4 |
| 20 | 21 | |
| 21 | 22 | var bars []*mpb.Bar |
| 43 | 44 | i := i |
| 44 | 45 | go func() { |
| 45 | 46 | task := fmt.Sprintf("Task#%02d:", i) |
| 46 | job := "\x1b[31;1;4minstalling\x1b[0m" | |
| 47 | // ANSI escape sequences are not supported on Windows OS | |
| 48 | job := "\x1b[31;1;4mつのだ☆HIRO\x1b[0m" | |
| 47 | 49 | // preparing delayed bars |
| 48 | 50 | b := p.AddBar(rand.Int63n(101)+100, |
| 49 | 51 | mpb.BarQueueAfter(bars[i]), |
| 62 | 64 | go newTask(doneWg, b, numBars-i) |
| 63 | 65 | }() |
| 64 | 66 | } |
| 65 | ||
| 67 | // wait for passed doneWg and for all bars to complete and flush | |
| 66 | 68 | p.Wait() |
| 67 | 69 | } |
| 68 | 70 | |
| 0 | module github.com/vbauerster/mpb/_examples/decoratorsOnTop | |
| 1 | ||
| 2 | go 1.14 | |
| 3 | ||
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 0 | package main | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "math/rand" | |
| 5 | "time" | |
| 6 | ||
| 7 | "github.com/vbauerster/mpb/v7" | |
| 8 | "github.com/vbauerster/mpb/v7/decor" | |
| 9 | ) | |
| 10 | ||
| 11 | func main() { | |
| 12 | p := mpb.New() | |
| 13 | ||
| 14 | total := 100 | |
| 15 | bar := p.New(int64(total), | |
| 16 | mpb.NopStyle(), // make main bar style nop, so there are just decorators | |
| 17 | mpb.BarExtender(extended(mpb.BarStyle())), // extend wtih normal bar on the next line | |
| 18 | mpb.PrependDecorators( | |
| 19 | decor.Name("Percentage: "), | |
| 20 | decor.NewPercentage("%d"), | |
| 21 | ), | |
| 22 | mpb.AppendDecorators( | |
| 23 | decor.Name("ETA: "), | |
| 24 | decor.OnComplete( | |
| 25 | decor.AverageETA(decor.ET_STYLE_GO), "done", | |
| 26 | ), | |
| 27 | ), | |
| 28 | ) | |
| 29 | // simulating some work | |
| 30 | max := 100 * time.Millisecond | |
| 31 | for i := 0; i < total; i++ { | |
| 32 | time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) | |
| 33 | bar.Increment() | |
| 34 | } | |
| 35 | // wait for our bar to complete and flush | |
| 36 | p.Wait() | |
| 37 | } | |
| 38 | ||
| 39 | func extended(builder mpb.BarFillerBuilder) mpb.BarFiller { | |
| 40 | filler := builder.Build() | |
| 41 | return mpb.BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) { | |
| 42 | filler.Fill(w, reqWidth, st) | |
| 43 | w.Write([]byte("\n")) | |
| 44 | }) | |
| 45 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 13 | 13 | var wg sync.WaitGroup |
| 14 | // passed wg will be accounted at p.Wait() call | |
| 14 | 15 | p := mpb.New( |
| 15 | 16 | mpb.WithWaitGroup(&wg), |
| 16 | // container's width. | |
| 17 | 17 | mpb.WithWidth(60), |
| 18 | 18 | ) |
| 19 | 19 | total, numBars := 100, 3 |
| 23 | 23 | name := fmt.Sprintf("Bar#%d:", i) |
| 24 | 24 | bar := p.AddBar(int64(total), |
| 25 | 25 | // set BarWidth 40 for bar 1 and 2 |
| 26 | mpb.BarOptOn(mpb.BarWidth(40), func() bool { return i > 0 }), | |
| 26 | mpb.BarOptional(mpb.BarWidth(40), i > 0), | |
| 27 | 27 | mpb.PrependDecorators( |
| 28 | 28 | // simple name decorator |
| 29 | 29 | decor.Name(name), |
| 54 | 54 | } |
| 55 | 55 | }() |
| 56 | 56 | } |
| 57 | // wait for all bars to complete and flush | |
| 57 | // wait for passed wg and for all bars to complete and flush | |
| 58 | 58 | p.Wait() |
| 59 | 59 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 4 | 4 | "math/rand" |
| 5 | 5 | "time" |
| 6 | 6 | |
| 7 | "github.com/vbauerster/mpb/v5" | |
| 8 | "github.com/vbauerster/mpb/v5/decor" | |
| 7 | "github.com/vbauerster/mpb/v7" | |
| 8 | "github.com/vbauerster/mpb/v7/decor" | |
| 9 | 9 | ) |
| 10 | 10 | |
| 11 | 11 | func init() { |
| 16 | 16 | p := mpb.New(mpb.WithWidth(64)) |
| 17 | 17 | |
| 18 | 18 | var total int64 |
| 19 | // new bar with 'trigger complete event' disabled, because total is zero | |
| 19 | 20 | bar := p.AddBar(total, |
| 20 | 21 | mpb.PrependDecorators(decor.Counters(decor.UnitKiB, "% .1f / % .1f")), |
| 21 | 22 | mpb.AppendDecorators(decor.Percentage()), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "io/ioutil" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 18 | 18 | mpb.WithRefreshRate(180*time.Millisecond), |
| 19 | 19 | ) |
| 20 | 20 | |
| 21 | bar := p.AddBar(total, mpb.BarStyle("[=>-|"), | |
| 21 | bar := p.New(total, | |
| 22 | mpb.BarStyle().Rbound("|"), | |
| 22 | 23 | mpb.PrependDecorators( |
| 23 | 24 | decor.CountersKibiByte("% .2f / % .2f"), |
| 24 | 25 | ), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 13 | 13 | var wg sync.WaitGroup |
| 14 | // pass &wg (optional), so p will wait for it eventually | |
| 14 | // passed wg will be accounted at p.Wait() call | |
| 15 | 15 | p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(60)) |
| 16 | 16 | total, numBars := 100, 3 |
| 17 | 17 | wg.Add(numBars) |
| 26 | 26 | "done", |
| 27 | 27 | ), |
| 28 | 28 | decor.WCSyncSpace, // Placeholder |
| 29 | decor.WCSyncSpace, // Placeholder | |
| 29 | 30 | ), |
| 30 | 31 | ) |
| 31 | 32 | } else { |
| 32 | 33 | pdecorators = mpb.PrependDecorators( |
| 33 | 34 | decor.CountersNoUnit("% .1d / % .1d", decor.WCSyncSpace), |
| 35 | decor.OnComplete(decor.Spinner(nil, decor.WCSyncSpace), "done"), | |
| 34 | 36 | decor.OnComplete(decor.Spinner(nil, decor.WCSyncSpace), "done"), |
| 35 | 37 | ) |
| 36 | 38 | } |
| 56 | 58 | } |
| 57 | 59 | }() |
| 58 | 60 | } |
| 59 | // Waiting for passed &wg and for all bars to complete and flush | |
| 61 | // wait for passed wg and for all bars to complete and flush | |
| 60 | 62 | p.Wait() |
| 61 | 63 | } |
| 62 | 64 | |
| 63 | 65 | func newVariadicSpinner(wc decor.WC) decor.Decorator { |
| 64 | 66 | spinner := decor.Spinner(nil) |
| 65 | f := func(s *decor.Statistics) string { | |
| 67 | fn := func(s decor.Statistics) string { | |
| 66 | 68 | return strings.Repeat(spinner.Decor(s), int(s.Current/3)) |
| 67 | 69 | } |
| 68 | return decor.Any(f, wc) | |
| 70 | return decor.Any(fn, wc) | |
| 69 | 71 | } |
| 0 | module github.com/vbauerster/mpb/_examples/mexicanBar | |
| 1 | ||
| 2 | go 1.14 | |
| 3 | ||
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 0 | package main | |
| 1 | ||
| 2 | import ( | |
| 3 | "math/rand" | |
| 4 | "time" | |
| 5 | ||
| 6 | "github.com/vbauerster/mpb/v7" | |
| 7 | "github.com/vbauerster/mpb/v7/decor" | |
| 8 | ) | |
| 9 | ||
| 10 | func main() { | |
| 11 | // initialize progress container, with custom width | |
| 12 | p := mpb.New(mpb.WithWidth(80)) | |
| 13 | ||
| 14 | total := 100 | |
| 15 | name := "Complex Filler:" | |
| 16 | bs := mpb.BarStyle() | |
| 17 | bs.Lbound("[\u001b[36;1m") | |
| 18 | bs.Filler("_") | |
| 19 | bs.Tip("\u001b[0m⛵\u001b[36;1m") | |
| 20 | bs.Padding("_") | |
| 21 | bs.Rbound("\u001b[0m]") | |
| 22 | bar := p.New(int64(total), bs, | |
| 23 | mpb.PrependDecorators(decor.Name(name)), | |
| 24 | mpb.AppendDecorators(decor.Percentage()), | |
| 25 | ) | |
| 26 | // simulating some work | |
| 27 | max := 100 * time.Millisecond | |
| 28 | for i := 0; i < total; i++ { | |
| 29 | time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) | |
| 30 | bar.Increment() | |
| 31 | } | |
| 32 | // wait for our bar to complete | |
| 33 | p.Wait() | |
| 34 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 13 | 13 | var wg sync.WaitGroup |
| 14 | // pass &wg (optional), so p will wait for it eventually | |
| 14 | // passed wg will be accounted at p.Wait() call | |
| 15 | 15 | p := mpb.New(mpb.WithWaitGroup(&wg)) |
| 16 | 16 | total, numBars := 100, 3 |
| 17 | 17 | wg.Add(numBars) |
| 29 | 29 | // replace ETA decorator with "done" message, OnComplete event |
| 30 | 30 | decor.OnComplete( |
| 31 | 31 | // ETA decorator with ewma age of 60 |
| 32 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 32 | decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncWidth), "done", | |
| 33 | 33 | ), |
| 34 | 34 | ), |
| 35 | 35 | ) |
| 49 | 49 | } |
| 50 | 50 | }() |
| 51 | 51 | } |
| 52 | // Waiting for passed &wg and for all bars to complete and flush | |
| 52 | // wait for passed wg and for all bars to complete and flush | |
| 53 | 53 | p.Wait() |
| 54 | 54 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | 4 | "os" |
| 5 | "strings" | |
| 5 | 6 | "sync" |
| 6 | 7 | "time" |
| 7 | 8 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v7" | |
| 10 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 11 | ) |
| 11 | 12 | |
| 12 | 13 | func main() { |
| 13 | 14 | var wg sync.WaitGroup |
| 14 | p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithDebugOutput(os.Stderr)) | |
| 15 | // passed wg will be accounted at p.Wait() call | |
| 16 | p := mpb.New( | |
| 17 | mpb.WithWaitGroup(&wg), | |
| 18 | mpb.WithDebugOutput(os.Stderr), | |
| 19 | ) | |
| 15 | 20 | |
| 16 | wantPanic := "Some really long panic panic panic panic panic panic panic, really it is very long" | |
| 21 | wantPanic := strings.Repeat("Panic ", 64) | |
| 17 | 22 | numBars := 3 |
| 18 | 23 | wg.Add(numBars) |
| 19 | 24 | |
| 29 | 34 | } |
| 30 | 35 | }() |
| 31 | 36 | } |
| 32 | ||
| 37 | // wait for passed wg and for all bars to complete and flush | |
| 33 | 38 | p.Wait() |
| 34 | 39 | } |
| 35 | 40 | |
| 36 | 41 | func panicDecorator(name, panicMsg string) decor.Decorator { |
| 37 | return decor.Any(func(s *decor.Statistics) string { | |
| 38 | if s.ID == 1 && s.Current >= 42 { | |
| 42 | return decor.Any(func(st decor.Statistics) string { | |
| 43 | if st.ID == 1 && st.Current >= 42 { | |
| 39 | 44 | panic(panicMsg) |
| 40 | 45 | } |
| 41 | 46 | return name |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | "io" | |
| 5 | 4 | "math/rand" |
| 6 | "sync" | |
| 7 | 5 | "time" |
| 8 | 6 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 7 | "github.com/vbauerster/mpb/v7" | |
| 8 | "github.com/vbauerster/mpb/v7/decor" | |
| 11 | 9 | ) |
| 12 | 10 | |
| 13 | 11 | func main() { |
| 14 | 12 | p := mpb.New(mpb.PopCompletedMode()) |
| 15 | 13 | |
| 16 | total, numBars := 100, 2 | |
| 14 | total, numBars := 100, 4 | |
| 17 | 15 | for i := 0; i < numBars; i++ { |
| 18 | 16 | name := fmt.Sprintf("Bar#%d:", i) |
| 19 | 17 | bar := p.AddBar(int64(total), |
| 20 | mpb.BarNoPop(), | |
| 18 | mpb.BarFillerOnComplete(fmt.Sprintf("%s has been completed", name)), | |
| 19 | mpb.BarFillerTrim(), | |
| 21 | 20 | mpb.PrependDecorators( |
| 22 | decor.Name(name), | |
| 23 | decor.Percentage(decor.WCSyncSpace), | |
| 21 | decor.OnComplete(decor.Name(name), ""), | |
| 22 | decor.OnComplete(decor.NewPercentage(" % d "), ""), | |
| 24 | 23 | ), |
| 25 | 24 | mpb.AppendDecorators( |
| 26 | decor.OnComplete( | |
| 27 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done!", | |
| 28 | ), | |
| 25 | decor.OnComplete(decor.Name(" "), ""), | |
| 26 | decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 60), ""), | |
| 29 | 27 | ), |
| 30 | 28 | ) |
| 31 | 29 | // simulating some work |
| 32 | go func() { | |
| 33 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 34 | max := 100 * time.Millisecond | |
| 35 | for i := 0; i < total; i++ { | |
| 36 | // start variable is solely for EWMA calculation | |
| 37 | // EWMA's unit of measure is an iteration's duration | |
| 38 | start := time.Now() | |
| 39 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) | |
| 40 | bar.Increment() | |
| 41 | // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract | |
| 42 | bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 43 | } | |
| 44 | }() | |
| 30 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 31 | max := 100 * time.Millisecond | |
| 32 | for i := 0; i < total; i++ { | |
| 33 | // start variable is solely for EWMA calculation | |
| 34 | // EWMA's unit of measure is an iteration's duration | |
| 35 | start := time.Now() | |
| 36 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) | |
| 37 | bar.Increment() | |
| 38 | // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract | |
| 39 | bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 40 | } | |
| 45 | 41 | } |
| 46 | 42 | |
| 47 | var wg sync.WaitGroup | |
| 48 | wg.Add(1) | |
| 49 | go func() { | |
| 50 | defer wg.Done() | |
| 51 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 52 | max := 3000 * time.Millisecond | |
| 53 | for i := 0; i < 10; i++ { | |
| 54 | filler := makeLogBar(fmt.Sprintf("some log: %d", i)) | |
| 55 | p.Add(0, filler).SetTotal(0, true) | |
| 56 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) | |
| 57 | } | |
| 58 | }() | |
| 59 | ||
| 60 | wg.Wait() | |
| 61 | 43 | p.Wait() |
| 62 | 44 | } |
| 63 | ||
| 64 | func makeLogBar(msg string) mpb.BarFiller { | |
| 65 | limit := "%%.%ds" | |
| 66 | return mpb.BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { | |
| 67 | fmt.Fprintf(w, fmt.Sprintf(limit, width), msg) | |
| 68 | }) | |
| 69 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 6 | 6 | "sync" |
| 7 | 7 | "time" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v7" | |
| 10 | "github.com/vbauerster/mpb/v7/decor" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | var quietMode bool |
| 19 | 19 | func main() { |
| 20 | 20 | flag.Parse() |
| 21 | 21 | var wg sync.WaitGroup |
| 22 | // pass &wg (optional), so p will wait for it eventually | |
| 22 | // passed wg will be accounted at p.Wait() call | |
| 23 | 23 | p := mpb.New( |
| 24 | 24 | mpb.WithWaitGroup(&wg), |
| 25 | mpb.ContainerOptOn( | |
| 25 | mpb.ContainerOptional( | |
| 26 | 26 | // setting to nil will: |
| 27 | // set output to ioutil.Discard and disable internal refresh rate | |
| 28 | // cycling, in order to not consume much CPU, hovewer a single refresh | |
| 29 | // still will be triggered on bar complete event, per each bar. | |
| 27 | // set output to ioutil.Discard and disable refresh rate cycle, in | |
| 28 | // order not to consume much CPU. Hovewer a single refresh still will | |
| 29 | // be triggered on bar complete event, per each bar. | |
| 30 | 30 | mpb.WithOutput(nil), |
| 31 | func() bool { return quietMode }, | |
| 31 | quietMode, | |
| 32 | 32 | ), |
| 33 | 33 | ) |
| 34 | 34 | total, numBars := 100, 3 |
| 67 | 67 | } |
| 68 | 68 | }() |
| 69 | 69 | } |
| 70 | // Waiting for passed &wg and for all bars to complete and flush | |
| 70 | // wait for passed wg and for all bars to complete and flush | |
| 71 | 71 | p.Wait() |
| 72 | 72 | fmt.Println("done") |
| 73 | 73 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 13 | 13 | var wg sync.WaitGroup |
| 14 | // passed wg will be accounted at p.Wait() call | |
| 14 | 15 | p := mpb.New(mpb.WithWaitGroup(&wg)) |
| 15 | 16 | total := 100 |
| 16 | 17 | numBars := 3 |
| 20 | 21 | name := fmt.Sprintf("Bar#%d:", i) |
| 21 | 22 | bar := p.AddBar(int64(total), |
| 22 | 23 | mpb.BarID(i), |
| 23 | mpb.BarOptOn(mpb.BarRemoveOnComplete(), func() bool { return i == 0 }), | |
| 24 | mpb.BarOptional(mpb.BarRemoveOnComplete(), i == 0), | |
| 24 | 25 | mpb.PrependDecorators( |
| 25 | 26 | decor.Name(name), |
| 26 | decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), | |
| 27 | 27 | ), |
| 28 | mpb.AppendDecorators(decor.Percentage()), | |
| 28 | mpb.AppendDecorators( | |
| 29 | decor.Any(func(s decor.Statistics) string { | |
| 30 | return fmt.Sprintf("completed: %v", s.Completed) | |
| 31 | }, decor.WCSyncSpaceR), | |
| 32 | decor.Any(func(s decor.Statistics) string { | |
| 33 | return fmt.Sprintf("aborted: %v", s.Aborted) | |
| 34 | }, decor.WCSyncSpaceR), | |
| 35 | decor.OnComplete(decor.NewPercentage("%d", decor.WCSyncSpace), "done"), | |
| 36 | decor.OnAbort(decor.NewPercentage("%d", decor.WCSyncSpace), "ohno"), | |
| 37 | ), | |
| 29 | 38 | ) |
| 30 | 39 | go func() { |
| 31 | 40 | defer wg.Done() |
| 32 | 41 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) |
| 33 | 42 | max := 100 * time.Millisecond |
| 34 | 43 | for i := 0; !bar.Completed(); i++ { |
| 35 | // start variable is solely for EWMA calculation | |
| 36 | // EWMA's unit of measure is an iteration's duration | |
| 37 | start := time.Now() | |
| 38 | 44 | if bar.ID() == 2 && i >= 42 { |
| 39 | // aborting and removing while bar is running | |
| 40 | bar.Abort(true) | |
| 45 | bar.Abort(false) | |
| 41 | 46 | } |
| 42 | 47 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) |
| 43 | 48 | bar.Increment() |
| 44 | // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract | |
| 45 | bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 46 | 49 | } |
| 47 | 50 | }() |
| 48 | 51 | } |
| 49 | ||
| 52 | // wait for passed wg and for all bars to complete and flush | |
| 50 | 53 | p.Wait() |
| 51 | 54 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 13 | 13 | var wg sync.WaitGroup |
| 14 | // pass &wg (optional), so p will wait for it eventually | |
| 14 | // passed wg will be accounted at p.Wait() call | |
| 15 | 15 | p := mpb.New(mpb.WithWaitGroup(&wg)) |
| 16 | 16 | total, numBars := 100, 3 |
| 17 | 17 | wg.Add(numBars) |
| 18 | 18 | |
| 19 | 19 | for i := 0; i < numBars; i++ { |
| 20 | 20 | name := fmt.Sprintf("Bar#%d:", i) |
| 21 | bar := p.AddBar(int64(total), | |
| 22 | // reverse Bar#1 | |
| 23 | mpb.BarOptOn(mpb.BarReverse(), func() bool { return i == 1 }), | |
| 21 | bar := p.New(int64(total), condBuilder(i == 1), | |
| 24 | 22 | mpb.PrependDecorators( |
| 25 | 23 | // simple name decorator |
| 26 | 24 | decor.Name(name), |
| 51 | 49 | } |
| 52 | 50 | }() |
| 53 | 51 | } |
| 54 | // Waiting for passed &wg and for all bars to complete and flush | |
| 52 | // wait for passed wg and for all bars to complete and flush | |
| 55 | 53 | p.Wait() |
| 56 | 54 | } |
| 55 | ||
| 56 | func condBuilder(cond bool) mpb.BarFillerBuilder { | |
| 57 | return mpb.BarFillerBuilderFunc(func() mpb.BarFiller { | |
| 58 | bs := mpb.BarStyle() | |
| 59 | if cond { | |
| 60 | // reverse Bar on cond | |
| 61 | bs = bs.Tip("<").Reverse() | |
| 62 | } | |
| 63 | return bs.Build() | |
| 64 | }) | |
| 65 | } | |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 3 | 3 | "math/rand" |
| 4 | 4 | "time" |
| 5 | 5 | |
| 6 | "github.com/vbauerster/mpb/v5" | |
| 7 | "github.com/vbauerster/mpb/v5/decor" | |
| 6 | "github.com/vbauerster/mpb/v7" | |
| 7 | "github.com/vbauerster/mpb/v7/decor" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | 10 | func main() { |
| 13 | 13 | |
| 14 | 14 | total := 100 |
| 15 | 15 | name := "Single Bar:" |
| 16 | // adding a single bar, which will inherit container's width | |
| 17 | bar := p.AddBar(int64(total), | |
| 18 | // override DefaultBarStyle, which is "[=>-]<+" | |
| 19 | mpb.BarStyle("╢▌▌░╟"), | |
| 16 | // create a single bar, which will inherit container's width | |
| 17 | bar := p.New(int64(total), | |
| 18 | // BarFillerBuilder with custom style | |
| 19 | mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), | |
| 20 | 20 | mpb.PrependDecorators( |
| 21 | 21 | // display our name with one space on the right |
| 22 | 22 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 0 | module github.com/vbauerster/mpb/_examples/spinTipBar | |
| 1 | ||
| 2 | go 1.14 | |
| 3 | ||
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 0 | package main | |
| 1 | ||
| 2 | import ( | |
| 3 | "math/rand" | |
| 4 | "time" | |
| 5 | ||
| 6 | "github.com/vbauerster/mpb/v7" | |
| 7 | "github.com/vbauerster/mpb/v7/decor" | |
| 8 | ) | |
| 9 | ||
| 10 | func main() { | |
| 11 | // initialize progress container, with custom width | |
| 12 | p := mpb.New(mpb.WithWidth(80)) | |
| 13 | ||
| 14 | total := 100 | |
| 15 | name := "Single Bar:" | |
| 16 | bar := p.New(int64(total), | |
| 17 | mpb.BarStyle().Tip(`-`, `\`, `|`, `/`), | |
| 18 | mpb.PrependDecorators(decor.Name(name)), | |
| 19 | mpb.AppendDecorators(decor.Percentage()), | |
| 20 | ) | |
| 21 | // simulating some work | |
| 22 | max := 100 * time.Millisecond | |
| 23 | for i := 0; i < total; i++ { | |
| 24 | time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) | |
| 25 | bar.Increment() | |
| 26 | } | |
| 27 | // wait for our bar to complete and flush | |
| 28 | p.Wait() | |
| 29 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 13 | 13 | var wg sync.WaitGroup |
| 14 | // passed wg will be accounted at p.Wait() call | |
| 14 | 15 | p := mpb.New( |
| 15 | 16 | mpb.WithWaitGroup(&wg), |
| 16 | mpb.WithWidth(13), | |
| 17 | mpb.WithWidth(16), | |
| 17 | 18 | ) |
| 18 | 19 | total, numBars := 101, 3 |
| 19 | 20 | wg.Add(numBars) |
| 20 | 21 | |
| 21 | 22 | for i := 0; i < numBars; i++ { |
| 22 | 23 | name := fmt.Sprintf("Bar#%d:", i) |
| 23 | var bar *mpb.Bar | |
| 24 | if i == 0 { | |
| 25 | bar = p.AddBar(int64(total), | |
| 26 | // override mpb.DefaultBarStyle, which is "[=>-]<+" | |
| 27 | mpb.BarStyle("╢▌▌░╟"), | |
| 28 | mpb.PrependDecorators( | |
| 29 | // simple name decorator | |
| 30 | decor.Name(name), | |
| 24 | bar := p.New(int64(total), condBuilder(i != 0), | |
| 25 | mpb.PrependDecorators( | |
| 26 | // simple name decorator | |
| 27 | decor.Name(name), | |
| 28 | ), | |
| 29 | mpb.AppendDecorators( | |
| 30 | // replace ETA decorator with "done" message, OnComplete event | |
| 31 | decor.OnComplete( | |
| 32 | // ETA decorator with ewma age of 60 | |
| 33 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 31 | 34 | ), |
| 32 | mpb.AppendDecorators( | |
| 33 | // replace ETA decorator with "done" message, OnComplete event | |
| 34 | decor.OnComplete( | |
| 35 | // ETA decorator with ewma age of 60 | |
| 36 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 37 | ), | |
| 38 | ), | |
| 39 | ) | |
| 40 | } else { | |
| 41 | bar = p.AddSpinner(int64(total), mpb.SpinnerOnMiddle, | |
| 42 | // override mpb.DefaultSpinnerStyle | |
| 43 | mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}), | |
| 44 | mpb.PrependDecorators( | |
| 45 | // simple name decorator | |
| 46 | decor.Name(name), | |
| 47 | ), | |
| 48 | mpb.AppendDecorators( | |
| 49 | // replace ETA decorator with "done" message, OnComplete event | |
| 50 | decor.OnComplete( | |
| 51 | // ETA decorator with ewma age of 60 | |
| 52 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 53 | ), | |
| 54 | ), | |
| 55 | ) | |
| 56 | } | |
| 57 | ||
| 35 | ), | |
| 36 | ) | |
| 58 | 37 | // simulating some work |
| 59 | 38 | go func() { |
| 60 | 39 | defer wg.Done() |
| 71 | 50 | } |
| 72 | 51 | }() |
| 73 | 52 | } |
| 74 | // wait for all bars to complete and flush | |
| 53 | // wait for passed wg and for all bars to complete and flush | |
| 75 | 54 | p.Wait() |
| 76 | 55 | } |
| 56 | ||
| 57 | func condBuilder(cond bool) mpb.BarFillerBuilder { | |
| 58 | return mpb.BarFillerBuilderFunc(func() mpb.BarFiller { | |
| 59 | if cond { | |
| 60 | // spinner Bar on cond | |
| 61 | frames := []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"} | |
| 62 | return mpb.SpinnerStyle(frames...).Build() | |
| 63 | } | |
| 64 | return mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟").Build() | |
| 65 | }) | |
| 66 | } | |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 13 | 13 | var wg sync.WaitGroup |
| 14 | // pass &wg (optional), so p will wait for it eventually | |
| 14 | // passed wg will be accounted at p.Wait() call | |
| 15 | 15 | p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(64)) |
| 16 | 16 | total, numBars := 100, 3 |
| 17 | 17 | wg.Add(numBars) |
| 43 | 43 | } |
| 44 | 44 | }() |
| 45 | 45 | } |
| 46 | // Waiting for passed &wg and for all bars to complete and flush | |
| 46 | // wait for passed wg and for all bars to complete and flush | |
| 47 | 47 | p.Wait() |
| 48 | 48 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v7" | |
| 9 | "github.com/vbauerster/mpb/v7/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | const ( |
| 15 | 15 | |
| 16 | 16 | func main() { |
| 17 | 17 | var wg sync.WaitGroup |
| 18 | p := mpb.New( | |
| 19 | mpb.WithWaitGroup(&wg), | |
| 20 | mpb.WithRefreshRate(50*time.Millisecond), | |
| 21 | ) | |
| 18 | // passed wg will be accounted at p.Wait() call | |
| 19 | p := mpb.New(mpb.WithWaitGroup(&wg)) | |
| 22 | 20 | wg.Add(totalBars) |
| 23 | 21 | |
| 24 | 22 | for i := 0; i < totalBars; i++ { |
| 46 | 44 | } |
| 47 | 45 | }() |
| 48 | 46 | } |
| 49 | ||
| 47 | // wait for passed wg and for all bars to complete and flush | |
| 50 | 48 | p.Wait() |
| 51 | 49 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require ( | |
| 5 | github.com/mattn/go-runewidth v0.0.13 | |
| 6 | github.com/vbauerster/mpb/v7 v7.3.2 | |
| 7 | ) |
| 7 | 7 | "sync" |
| 8 | 8 | "time" |
| 9 | 9 | |
| 10 | "github.com/vbauerster/mpb/v5" | |
| 11 | "github.com/vbauerster/mpb/v5/decor" | |
| 10 | "github.com/mattn/go-runewidth" | |
| 11 | "github.com/vbauerster/mpb/v7" | |
| 12 | "github.com/vbauerster/mpb/v7/decor" | |
| 12 | 13 | ) |
| 13 | 14 | |
| 14 | 15 | func main() { |
| 17 | 18 | total := 100 |
| 18 | 19 | msgCh := make(chan string) |
| 19 | 20 | resumeCh := make(chan struct{}) |
| 20 | filler, nextCh := newCustomFiller(msgCh, resumeCh) | |
| 21 | bar := p.Add(int64(total), filler, | |
| 22 | mpb.PrependDecorators( | |
| 23 | decor.Name("my bar:"), | |
| 24 | ), | |
| 25 | mpb.AppendDecorators( | |
| 26 | newCustomPercentage(nextCh), | |
| 27 | ), | |
| 21 | nextCh := make(chan struct{}, 1) | |
| 22 | bar := p.AddBar(int64(total), | |
| 23 | mpb.BarFillerMiddleware(func(base mpb.BarFiller) mpb.BarFiller { | |
| 24 | var msg *string | |
| 25 | return mpb.BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) { | |
| 26 | select { | |
| 27 | case m := <-msgCh: | |
| 28 | defer func() { | |
| 29 | msg = &m | |
| 30 | }() | |
| 31 | nextCh <- struct{}{} | |
| 32 | case <-resumeCh: | |
| 33 | msg = nil | |
| 34 | default: | |
| 35 | } | |
| 36 | if msg != nil { | |
| 37 | io.WriteString(w, runewidth.Truncate(*msg, st.AvailableWidth, "…")) | |
| 38 | nextCh <- struct{}{} | |
| 39 | } else { | |
| 40 | base.Fill(w, reqWidth, st) | |
| 41 | } | |
| 42 | }) | |
| 43 | }), | |
| 44 | mpb.PrependDecorators(decor.Name("my bar:")), | |
| 45 | mpb.AppendDecorators(newCustomPercentage(nextCh)), | |
| 28 | 46 | ) |
| 29 | 47 | ew := &errorWrapper{} |
| 30 | 48 | time.AfterFunc(2*time.Second, func() { |
| 75 | 93 | ew.Unlock() |
| 76 | 94 | } |
| 77 | 95 | |
| 78 | type myBarFiller struct { | |
| 79 | mpb.BarFiller | |
| 80 | base mpb.BarFiller | |
| 81 | } | |
| 82 | ||
| 83 | func (cf *myBarFiller) Base() mpb.BarFiller { | |
| 84 | return cf.base | |
| 85 | } | |
| 86 | ||
| 87 | func newCustomFiller(ch <-chan string, resume <-chan struct{}) (mpb.BarFiller, <-chan struct{}) { | |
| 88 | base := mpb.NewBarFiller(mpb.DefaultBarStyle, false) | |
| 89 | nextCh := make(chan struct{}, 1) | |
| 90 | var msg *string | |
| 91 | filler := mpb.BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { | |
| 96 | func newCustomPercentage(nextCh <-chan struct{}) decor.Decorator { | |
| 97 | base := decor.Percentage() | |
| 98 | fn := func(s decor.Statistics) string { | |
| 92 | 99 | select { |
| 93 | case m := <-ch: | |
| 94 | defer func() { | |
| 95 | msg = &m | |
| 96 | }() | |
| 97 | nextCh <- struct{}{} | |
| 98 | case <-resume: | |
| 99 | msg = nil | |
| 100 | default: | |
| 101 | } | |
| 102 | if msg != nil { | |
| 103 | limitFmt := fmt.Sprintf("%%.%ds", width) | |
| 104 | fmt.Fprintf(w, limitFmt, *msg) | |
| 105 | nextCh <- struct{}{} | |
| 106 | } else { | |
| 107 | base.Fill(w, width, st) | |
| 108 | } | |
| 109 | }) | |
| 110 | cf := &myBarFiller{ | |
| 111 | BarFiller: filler, | |
| 112 | base: base, | |
| 113 | } | |
| 114 | return cf, nextCh | |
| 115 | } | |
| 116 | ||
| 117 | func newCustomPercentage(ch <-chan struct{}) decor.Decorator { | |
| 118 | base := decor.Percentage() | |
| 119 | f := func(s *decor.Statistics) string { | |
| 120 | select { | |
| 121 | case <-ch: | |
| 100 | case <-nextCh: | |
| 122 | 101 | return "" |
| 123 | 102 | default: |
| 124 | 103 | return base.Decor(s) |
| 125 | 104 | } |
| 126 | 105 | } |
| 127 | return decor.Any(f) | |
| 106 | return decor.Any(fn) | |
| 128 | 107 | } |
| 0 | module github.com/vbauerster/mpb/_examples/tipOnComplete | |
| 1 | ||
| 2 | go 1.14 | |
| 3 | ||
| 4 | require github.com/vbauerster/mpb/v7 v7.3.2 |
| 0 | package main | |
| 1 | ||
| 2 | import ( | |
| 3 | "math/rand" | |
| 4 | "time" | |
| 5 | ||
| 6 | "github.com/vbauerster/mpb/v7" | |
| 7 | "github.com/vbauerster/mpb/v7/decor" | |
| 8 | ) | |
| 9 | ||
| 10 | func main() { | |
| 11 | // initialize progress container, with custom width | |
| 12 | p := mpb.New(mpb.WithWidth(80)) | |
| 13 | ||
| 14 | total := 100 | |
| 15 | name := "Single Bar:" | |
| 16 | bar := p.New(int64(total), | |
| 17 | mpb.BarStyle().TipOnComplete(">"), | |
| 18 | mpb.PrependDecorators(decor.Name(name)), | |
| 19 | mpb.AppendDecorators(decor.Percentage()), | |
| 20 | ) | |
| 21 | // simulating some work | |
| 22 | max := 100 * time.Millisecond | |
| 23 | for i := 0; i < total; i++ { | |
| 24 | time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) | |
| 25 | bar.Increment() | |
| 26 | } | |
| 27 | // wait for our bar to complete and flush | |
| 28 | p.Wait() | |
| 29 | } |
| 4 | 4 | "context" |
| 5 | 5 | "fmt" |
| 6 | 6 | "io" |
| 7 | "log" | |
| 7 | "runtime/debug" | |
| 8 | 8 | "strings" |
| 9 | "sync" | |
| 9 | 10 | "time" |
| 10 | "unicode/utf8" | |
| 11 | ||
| 12 | "github.com/vbauerster/mpb/v5/decor" | |
| 11 | ||
| 12 | "github.com/acarl005/stripansi" | |
| 13 | "github.com/mattn/go-runewidth" | |
| 14 | "github.com/vbauerster/mpb/v7/decor" | |
| 13 | 15 | ) |
| 14 | 16 | |
| 15 | // BarFiller interface. | |
| 16 | // Bar renders itself by calling BarFiller's Fill method. You can | |
| 17 | // literally have any bar kind, by implementing this interface and | |
| 18 | // passing it to the *Progress.Add(...) *Bar method. | |
| 19 | type BarFiller interface { | |
| 20 | Fill(w io.Writer, width int, stat *decor.Statistics) | |
| 21 | } | |
| 22 | ||
| 23 | // BarFillerFunc is function type adapter to convert function into Filler. | |
| 24 | type BarFillerFunc func(w io.Writer, width int, stat *decor.Statistics) | |
| 25 | ||
| 26 | func (f BarFillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 27 | f(w, width, stat) | |
| 28 | } | |
| 29 | ||
| 30 | // Bar represents a progress Bar. | |
| 17 | // Bar represents a progress bar. | |
| 31 | 18 | type Bar struct { |
| 32 | 19 | priority int // used by heap |
| 33 | 20 | index int // used by heap |
| 34 | 21 | |
| 35 | extendedLines int | |
| 36 | 22 | toShutdown bool |
| 37 | 23 | toDrop bool |
| 38 | 24 | noPop bool |
| 39 | 25 | hasEwmaDecorators bool |
| 40 | 26 | operateState chan func(*bState) |
| 41 | frameCh chan io.Reader | |
| 42 | syncTableCh chan [][]chan int | |
| 43 | completed chan bool | |
| 27 | frameCh chan *frame | |
| 44 | 28 | |
| 45 | 29 | // cancel is called either by user or on complete event |
| 46 | 30 | cancel func() |
| 47 | 31 | // done is closed after cacheState is assigned |
| 48 | 32 | done chan struct{} |
| 49 | // cacheState is populated, right after close(shutdown) | |
| 33 | // cacheState is populated, right after close(b.done) | |
| 50 | 34 | cacheState *bState |
| 51 | 35 | |
| 52 | 36 | container *Progress |
| 53 | dlogger *log.Logger | |
| 54 | 37 | recoveredPanic interface{} |
| 55 | 38 | } |
| 56 | 39 | |
| 57 | type extFunc func(in io.Reader, tw int, st *decor.Statistics) (out io.Reader, lines int) | |
| 58 | ||
| 40 | type extenderFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int) | |
| 41 | ||
| 42 | // bState is actual bar state. It gets passed to *Bar.serve(...) monitor | |
| 43 | // goroutine. | |
| 59 | 44 | type bState struct { |
| 60 | baseF BarFiller | |
| 61 | filler BarFiller | |
| 62 | 45 | id int |
| 63 | width int | |
| 46 | priority int | |
| 47 | reqWidth int | |
| 64 | 48 | total int64 |
| 65 | 49 | current int64 |
| 66 | lastN int64 | |
| 67 | iterated bool | |
| 50 | refill int64 | |
| 51 | lastIncrement int64 | |
| 68 | 52 | trimSpace bool |
| 69 | toComplete bool | |
| 53 | completed bool | |
| 70 | 54 | completeFlushed bool |
| 55 | aborted bool | |
| 56 | triggerComplete bool | |
| 57 | dropOnComplete bool | |
| 71 | 58 | noPop bool |
| 72 | 59 | aDecorators []decor.Decorator |
| 73 | 60 | pDecorators []decor.Decorator |
| 74 | 61 | averageDecorators []decor.AverageDecorator |
| 75 | 62 | ewmaDecorators []decor.EwmaDecorator |
| 76 | 63 | shutdownListeners []decor.ShutdownListener |
| 77 | bufP, bufB, bufA *bytes.Buffer | |
| 78 | extender extFunc | |
| 79 | ||
| 80 | // priority overrides *Bar's priority, if set | |
| 81 | priority int | |
| 82 | // dropOnComplete propagates to *Bar | |
| 83 | dropOnComplete bool | |
| 64 | buffers [3]*bytes.Buffer | |
| 65 | filler BarFiller | |
| 66 | middleware func(BarFiller) BarFiller | |
| 67 | extender extenderFunc | |
| 68 | ||
| 84 | 69 | // runningBar is a key for *pState.parkedBars |
| 85 | 70 | runningBar *Bar |
| 86 | 71 | |
| 87 | 72 | debugOut io.Writer |
| 88 | 73 | } |
| 89 | 74 | |
| 75 | type frame struct { | |
| 76 | reader io.Reader | |
| 77 | lines int | |
| 78 | } | |
| 79 | ||
| 90 | 80 | func newBar(container *Progress, bs *bState) *Bar { |
| 91 | logPrefix := fmt.Sprintf("%sbar#%02d ", container.dlogger.Prefix(), bs.id) | |
| 92 | 81 | ctx, cancel := context.WithCancel(container.ctx) |
| 93 | 82 | |
| 94 | 83 | bar := &Bar{ |
| 97 | 86 | toDrop: bs.dropOnComplete, |
| 98 | 87 | noPop: bs.noPop, |
| 99 | 88 | operateState: make(chan func(*bState)), |
| 100 | frameCh: make(chan io.Reader, 1), | |
| 101 | syncTableCh: make(chan [][]chan int), | |
| 102 | completed: make(chan bool, 1), | |
| 89 | frameCh: make(chan *frame, 1), | |
| 103 | 90 | done: make(chan struct{}), |
| 104 | 91 | cancel: cancel, |
| 105 | dlogger: log.New(bs.debugOut, logPrefix, log.Lshortfile), | |
| 106 | 92 | } |
| 107 | 93 | |
| 108 | 94 | go bar.serve(ctx, bs) |
| 115 | 101 | if r == nil { |
| 116 | 102 | panic("expected non nil io.Reader") |
| 117 | 103 | } |
| 118 | return newProxyReader(r, b) | |
| 104 | return b.newProxyReader(r) | |
| 119 | 105 | } |
| 120 | 106 | |
| 121 | 107 | // ID returs id of the bar. |
| 140 | 126 | } |
| 141 | 127 | } |
| 142 | 128 | |
| 143 | // SetRefill fills bar with refill rune up to amount argument. | |
| 144 | // Given default bar style is "[=>-]<+", refill rune is '+'. | |
| 145 | // To set bar style use mpb.BarStyle(string) BarOption. | |
| 129 | // SetRefill sets refill flag with specified amount. | |
| 130 | // The underlying BarFiller will change its visual representation, to | |
| 131 | // indicate refill event. Refill event may be referred to some retry | |
| 132 | // operation for example. | |
| 146 | 133 | func (b *Bar) SetRefill(amount int64) { |
| 147 | type refiller interface { | |
| 148 | SetRefill(int64) | |
| 149 | } | |
| 150 | b.operateState <- func(s *bState) { | |
| 151 | if f, ok := s.baseF.(refiller); ok { | |
| 152 | f.SetRefill(amount) | |
| 153 | } | |
| 134 | select { | |
| 135 | case b.operateState <- func(s *bState) { | |
| 136 | s.refill = amount | |
| 137 | }: | |
| 138 | case <-b.done: | |
| 154 | 139 | } |
| 155 | 140 | } |
| 156 | 141 | |
| 157 | 142 | // TraverseDecorators traverses all available decorators and calls cb func on each. |
| 158 | 143 | func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) { |
| 159 | b.operateState <- func(s *bState) { | |
| 144 | done := make(chan struct{}) | |
| 145 | select { | |
| 146 | case b.operateState <- func(s *bState) { | |
| 160 | 147 | for _, decorators := range [...][]decor.Decorator{ |
| 161 | 148 | s.pDecorators, |
| 162 | 149 | s.aDecorators, |
| 165 | 152 | cb(extractBaseDecorator(d)) |
| 166 | 153 | } |
| 167 | 154 | } |
| 155 | close(done) | |
| 156 | }: | |
| 157 | <-done | |
| 158 | case <-b.done: | |
| 168 | 159 | } |
| 169 | 160 | } |
| 170 | 161 | |
| 171 | 162 | // SetTotal sets total dynamically. |
| 172 | // If total is less or equal to zero it takes progress' current value. | |
| 173 | // If complete is true, complete event will be triggered. | |
| 174 | func (b *Bar) SetTotal(total int64, complete bool) { | |
| 175 | select { | |
| 176 | case b.operateState <- func(s *bState) { | |
| 177 | if total <= 0 { | |
| 163 | // If total is negative it takes progress' current value. | |
| 164 | func (b *Bar) SetTotal(total int64, triggerComplete bool) { | |
| 165 | select { | |
| 166 | case b.operateState <- func(s *bState) { | |
| 167 | s.triggerComplete = triggerComplete | |
| 168 | if total < 0 { | |
| 178 | 169 | s.total = s.current |
| 179 | 170 | } else { |
| 180 | 171 | s.total = total |
| 181 | 172 | } |
| 182 | if complete && !s.toComplete { | |
| 173 | if s.triggerComplete && !s.completed { | |
| 183 | 174 | s.current = s.total |
| 184 | s.toComplete = true | |
| 185 | go b.refreshTillShutdown() | |
| 175 | s.completed = true | |
| 176 | go b.forceRefreshIfLastUncompleted() | |
| 186 | 177 | } |
| 187 | 178 | }: |
| 188 | 179 | case <-b.done: |
| 190 | 181 | } |
| 191 | 182 | |
| 192 | 183 | // SetCurrent sets progress' current to an arbitrary value. |
| 184 | // Setting a negative value will cause a panic. | |
| 193 | 185 | func (b *Bar) SetCurrent(current int64) { |
| 194 | 186 | select { |
| 195 | 187 | case b.operateState <- func(s *bState) { |
| 196 | s.iterated = true | |
| 197 | s.lastN = current - s.current | |
| 188 | s.lastIncrement = current - s.current | |
| 198 | 189 | s.current = current |
| 199 | if s.total > 0 && s.current >= s.total { | |
| 190 | if s.triggerComplete && s.current >= s.total { | |
| 200 | 191 | s.current = s.total |
| 201 | s.toComplete = true | |
| 202 | go b.refreshTillShutdown() | |
| 192 | s.completed = true | |
| 193 | go b.forceRefreshIfLastUncompleted() | |
| 203 | 194 | } |
| 204 | 195 | }: |
| 205 | 196 | case <-b.done: |
| 218 | 209 | |
| 219 | 210 | // IncrInt64 increments progress by amount of n. |
| 220 | 211 | func (b *Bar) IncrInt64(n int64) { |
| 221 | select { | |
| 222 | case b.operateState <- func(s *bState) { | |
| 223 | s.iterated = true | |
| 224 | s.lastN = n | |
| 212 | if n <= 0 { | |
| 213 | return | |
| 214 | } | |
| 215 | select { | |
| 216 | case b.operateState <- func(s *bState) { | |
| 217 | s.lastIncrement = n | |
| 225 | 218 | s.current += n |
| 226 | if s.total > 0 && s.current >= s.total { | |
| 219 | if s.triggerComplete && s.current >= s.total { | |
| 227 | 220 | s.current = s.total |
| 228 | s.toComplete = true | |
| 229 | go b.refreshTillShutdown() | |
| 221 | s.completed = true | |
| 222 | go b.forceRefreshIfLastUncompleted() | |
| 230 | 223 | } |
| 231 | 224 | }: |
| 232 | 225 | case <-b.done: |
| 240 | 233 | func (b *Bar) DecoratorEwmaUpdate(dur time.Duration) { |
| 241 | 234 | select { |
| 242 | 235 | case b.operateState <- func(s *bState) { |
| 243 | ewmaIterationUpdate(false, s, dur) | |
| 244 | }: | |
| 245 | case <-b.done: | |
| 246 | ewmaIterationUpdate(true, b.cacheState, dur) | |
| 236 | if s.lastIncrement > 0 { | |
| 237 | s.decoratorEwmaUpdate(dur) | |
| 238 | s.lastIncrement = 0 | |
| 239 | } else { | |
| 240 | panic("increment required before ewma iteration update") | |
| 241 | } | |
| 242 | }: | |
| 243 | case <-b.done: | |
| 244 | if b.cacheState.lastIncrement > 0 { | |
| 245 | b.cacheState.decoratorEwmaUpdate(dur) | |
| 246 | b.cacheState.lastIncrement = 0 | |
| 247 | } | |
| 247 | 248 | } |
| 248 | 249 | } |
| 249 | 250 | |
| 253 | 254 | func (b *Bar) DecoratorAverageAdjust(start time.Time) { |
| 254 | 255 | select { |
| 255 | 256 | case b.operateState <- func(s *bState) { |
| 256 | for _, d := range s.averageDecorators { | |
| 257 | d.AverageAdjust(start) | |
| 258 | } | |
| 257 | s.decoratorAverageAdjust(start) | |
| 259 | 258 | }: |
| 260 | 259 | case <-b.done: |
| 261 | 260 | } |
| 265 | 264 | // priority, i.e. bar will be on top. If you don't need to set priority |
| 266 | 265 | // dynamically, better use BarPriority option. |
| 267 | 266 | func (b *Bar) SetPriority(priority int) { |
| 268 | select { | |
| 269 | case <-b.done: | |
| 270 | default: | |
| 271 | b.container.setBarPriority(b, priority) | |
| 272 | } | |
| 273 | } | |
| 274 | ||
| 275 | // Abort interrupts bar's running goroutine. Call this, if you'd like | |
| 276 | // to stop/remove bar before completion event. It has no effect after | |
| 277 | // completion event. If drop is true bar will be removed as well. | |
| 267 | b.container.UpdateBarPriority(b, priority) | |
| 268 | } | |
| 269 | ||
| 270 | // Abort interrupts bar's running goroutine. Abort won't be engaged | |
| 271 | // if bar is already in complete state. If drop is true bar will be | |
| 272 | // removed as well. | |
| 278 | 273 | func (b *Bar) Abort(drop bool) { |
| 279 | select { | |
| 280 | case <-b.done: | |
| 281 | default: | |
| 282 | if drop { | |
| 283 | b.container.dropBar(b) | |
| 284 | } | |
| 274 | done := make(chan struct{}) | |
| 275 | select { | |
| 276 | case b.operateState <- func(s *bState) { | |
| 277 | if s.completed { | |
| 278 | close(done) | |
| 279 | return | |
| 280 | } | |
| 281 | s.aborted = true | |
| 285 | 282 | b.cancel() |
| 283 | // container must be run during lifetime of this inner goroutine | |
| 284 | // we control this by done channel declared above | |
| 285 | go func() { | |
| 286 | if drop { | |
| 287 | b.container.dropBar(b) | |
| 288 | } else { | |
| 289 | var uncompleted int | |
| 290 | b.container.traverseBars(func(bar *Bar) bool { | |
| 291 | if b != bar && !bar.Completed() { | |
| 292 | uncompleted++ | |
| 293 | return false | |
| 294 | } | |
| 295 | return true | |
| 296 | }) | |
| 297 | if uncompleted == 0 { | |
| 298 | b.container.refreshCh <- time.Now() | |
| 299 | } | |
| 300 | } | |
| 301 | close(done) // release hold of Abort | |
| 302 | }() | |
| 303 | }: | |
| 304 | // guarantee: container is alive during lifetime of this hold | |
| 305 | <-done | |
| 306 | case <-b.done: | |
| 286 | 307 | } |
| 287 | 308 | } |
| 288 | 309 | |
| 289 | 310 | // Completed reports whether the bar is in completed state. |
| 290 | 311 | func (b *Bar) Completed() bool { |
| 291 | select { | |
| 292 | case b.operateState <- func(s *bState) { b.completed <- s.toComplete }: | |
| 293 | return <-b.completed | |
| 312 | result := make(chan bool) | |
| 313 | select { | |
| 314 | case b.operateState <- func(s *bState) { result <- s.completed }: | |
| 315 | return <-result | |
| 294 | 316 | case <-b.done: |
| 295 | 317 | return true |
| 296 | 318 | } |
| 303 | 325 | case op := <-b.operateState: |
| 304 | 326 | op(s) |
| 305 | 327 | case <-ctx.Done(): |
| 328 | s.decoratorShutdownNotify() | |
| 306 | 329 | b.cacheState = s |
| 307 | 330 | close(b.done) |
| 308 | // Notifying decorators about shutdown event | |
| 309 | for _, sl := range s.shutdownListeners { | |
| 310 | sl.Shutdown() | |
| 311 | } | |
| 312 | 331 | return |
| 313 | 332 | } |
| 314 | 333 | } |
| 315 | 334 | } |
| 316 | 335 | |
| 317 | 336 | func (b *Bar) render(tw int) { |
| 318 | if b.recoveredPanic != nil { | |
| 319 | b.toShutdown = false | |
| 320 | b.frameCh <- b.panicToFrame(tw) | |
| 321 | return | |
| 322 | } | |
| 323 | select { | |
| 324 | case b.operateState <- func(s *bState) { | |
| 337 | select { | |
| 338 | case b.operateState <- func(s *bState) { | |
| 339 | stat := newStatistics(tw, s) | |
| 325 | 340 | defer func() { |
| 326 | 341 | // recovering if user defined decorator panics for example |
| 327 | 342 | if p := recover(); p != nil { |
| 328 | b.dlogger.Println(p) | |
| 329 | b.recoveredPanic = p | |
| 330 | b.toShutdown = !s.completeFlushed | |
| 331 | b.frameCh <- b.panicToFrame(tw) | |
| 343 | if b.recoveredPanic == nil { | |
| 344 | if s.debugOut != nil { | |
| 345 | fmt.Fprintln(s.debugOut, p) | |
| 346 | _, _ = s.debugOut.Write(debug.Stack()) | |
| 347 | } | |
| 348 | s.extender = makePanicExtender(p) | |
| 349 | b.toShutdown = !b.toShutdown | |
| 350 | b.recoveredPanic = p | |
| 351 | } | |
| 352 | reader, lines := s.extender(nil, s.reqWidth, stat) | |
| 353 | b.frameCh <- &frame{reader, lines + 1} | |
| 332 | 354 | } |
| 355 | s.completeFlushed = s.completed | |
| 333 | 356 | }() |
| 334 | ||
| 335 | st := newStatistics(s) | |
| 336 | frame := s.draw(tw, st) | |
| 337 | frame, b.extendedLines = s.extender(frame, tw, st) | |
| 338 | ||
| 339 | b.toShutdown = s.toComplete && !s.completeFlushed | |
| 340 | s.completeFlushed = s.toComplete | |
| 341 | b.frameCh <- frame | |
| 357 | reader, lines := s.extender(s.draw(stat), s.reqWidth, stat) | |
| 358 | b.toShutdown = s.completed && !s.completeFlushed | |
| 359 | b.frameCh <- &frame{reader, lines + 1} | |
| 342 | 360 | }: |
| 343 | 361 | case <-b.done: |
| 344 | 362 | s := b.cacheState |
| 345 | st := newStatistics(s) | |
| 346 | frame := s.draw(tw, st) | |
| 347 | frame, b.extendedLines = s.extender(frame, tw, st) | |
| 348 | b.frameCh <- frame | |
| 349 | } | |
| 350 | } | |
| 351 | ||
| 352 | func (b *Bar) panicToFrame(termWidth int) io.Reader { | |
| 353 | return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%dv\n", termWidth), b.recoveredPanic)) | |
| 363 | stat := newStatistics(tw, s) | |
| 364 | var r io.Reader | |
| 365 | if b.recoveredPanic == nil { | |
| 366 | r = s.draw(stat) | |
| 367 | } | |
| 368 | reader, lines := s.extender(r, s.reqWidth, stat) | |
| 369 | b.frameCh <- &frame{reader, lines + 1} | |
| 370 | } | |
| 354 | 371 | } |
| 355 | 372 | |
| 356 | 373 | func (b *Bar) subscribeDecorators() { |
| 368 | 385 | shutdownListeners = append(shutdownListeners, d) |
| 369 | 386 | } |
| 370 | 387 | }) |
| 371 | b.operateState <- func(s *bState) { | |
| 388 | b.hasEwmaDecorators = len(ewmaDecorators) != 0 | |
| 389 | select { | |
| 390 | case b.operateState <- func(s *bState) { | |
| 372 | 391 | s.averageDecorators = averageDecorators |
| 373 | 392 | s.ewmaDecorators = ewmaDecorators |
| 374 | 393 | s.shutdownListeners = shutdownListeners |
| 375 | } | |
| 376 | b.hasEwmaDecorators = len(ewmaDecorators) != 0 | |
| 377 | } | |
| 378 | ||
| 379 | func (b *Bar) refreshTillShutdown() { | |
| 380 | for { | |
| 381 | select { | |
| 382 | case b.container.refreshCh <- time.Now(): | |
| 383 | case <-b.done: | |
| 384 | return | |
| 394 | }: | |
| 395 | case <-b.done: | |
| 396 | } | |
| 397 | } | |
| 398 | ||
| 399 | func (b *Bar) forceRefreshIfLastUncompleted() { | |
| 400 | var uncompleted int | |
| 401 | b.container.traverseBars(func(bar *Bar) bool { | |
| 402 | if b != bar && !bar.Completed() { | |
| 403 | uncompleted++ | |
| 404 | return false | |
| 405 | } | |
| 406 | return true | |
| 407 | }) | |
| 408 | if uncompleted == 0 { | |
| 409 | for { | |
| 410 | select { | |
| 411 | case b.container.refreshCh <- time.Now(): | |
| 412 | case <-b.done: | |
| 413 | return | |
| 414 | } | |
| 385 | 415 | } |
| 386 | 416 | } |
| 387 | 417 | } |
| 388 | 418 | |
| 389 | 419 | func (b *Bar) wSyncTable() [][]chan int { |
| 390 | select { | |
| 391 | case b.operateState <- func(s *bState) { b.syncTableCh <- s.wSyncTable() }: | |
| 392 | return <-b.syncTableCh | |
| 420 | result := make(chan [][]chan int) | |
| 421 | select { | |
| 422 | case b.operateState <- func(s *bState) { result <- s.wSyncTable() }: | |
| 423 | return <-result | |
| 393 | 424 | case <-b.done: |
| 394 | 425 | return b.cacheState.wSyncTable() |
| 395 | 426 | } |
| 396 | 427 | } |
| 397 | 428 | |
| 398 | func (s *bState) draw(termWidth int, stat *decor.Statistics) io.Reader { | |
| 429 | func (s *bState) draw(stat decor.Statistics) io.Reader { | |
| 430 | bufP, bufB, bufA := s.buffers[0], s.buffers[1], s.buffers[2] | |
| 431 | nlr := strings.NewReader("\n") | |
| 432 | tw := stat.AvailableWidth | |
| 399 | 433 | for _, d := range s.pDecorators { |
| 400 | s.bufP.WriteString(d.Decor(stat)) | |
| 401 | } | |
| 402 | ||
| 434 | str := d.Decor(stat) | |
| 435 | stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str)) | |
| 436 | bufP.WriteString(str) | |
| 437 | } | |
| 438 | if stat.AvailableWidth < 1 { | |
| 439 | trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(bufP.String()), tw, "…")) | |
| 440 | bufP.Reset() | |
| 441 | return io.MultiReader(trunc, nlr) | |
| 442 | } | |
| 443 | ||
| 444 | if !s.trimSpace && stat.AvailableWidth > 1 { | |
| 445 | stat.AvailableWidth -= 2 | |
| 446 | bufB.WriteByte(' ') | |
| 447 | defer bufB.WriteByte(' ') | |
| 448 | } | |
| 449 | ||
| 450 | tw = stat.AvailableWidth | |
| 403 | 451 | for _, d := range s.aDecorators { |
| 404 | s.bufA.WriteString(d.Decor(stat)) | |
| 405 | } | |
| 406 | ||
| 407 | s.bufA.WriteByte('\n') | |
| 408 | ||
| 409 | prependCount := utf8.RuneCount(s.bufP.Bytes()) | |
| 410 | appendCount := utf8.RuneCount(s.bufA.Bytes()) - 1 | |
| 411 | ||
| 412 | if fitWidth := s.width; termWidth > 1 { | |
| 413 | if !s.trimSpace { | |
| 414 | // reserve space for edge spaces | |
| 415 | termWidth -= 2 | |
| 416 | s.bufB.WriteByte(' ') | |
| 417 | defer s.bufB.WriteByte(' ') | |
| 418 | } | |
| 419 | if prependCount+s.width+appendCount > termWidth { | |
| 420 | fitWidth = termWidth - prependCount - appendCount | |
| 421 | } | |
| 422 | s.filler.Fill(s.bufB, fitWidth, stat) | |
| 423 | } | |
| 424 | ||
| 425 | return io.MultiReader(s.bufP, s.bufB, s.bufA) | |
| 452 | str := d.Decor(stat) | |
| 453 | stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str)) | |
| 454 | bufA.WriteString(str) | |
| 455 | } | |
| 456 | if stat.AvailableWidth < 1 { | |
| 457 | trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(bufA.String()), tw, "…")) | |
| 458 | bufA.Reset() | |
| 459 | return io.MultiReader(bufP, bufB, trunc, nlr) | |
| 460 | } | |
| 461 | ||
| 462 | s.filler.Fill(bufB, s.reqWidth, stat) | |
| 463 | ||
| 464 | return io.MultiReader(bufP, bufB, bufA, nlr) | |
| 426 | 465 | } |
| 427 | 466 | |
| 428 | 467 | func (s *bState) wSyncTable() [][]chan int { |
| 447 | 486 | return table |
| 448 | 487 | } |
| 449 | 488 | |
| 450 | func newStatistics(s *bState) *decor.Statistics { | |
| 451 | return &decor.Statistics{ | |
| 452 | ID: s.id, | |
| 453 | Completed: s.completeFlushed, | |
| 454 | Total: s.total, | |
| 455 | Current: s.current, | |
| 489 | func (s bState) decoratorEwmaUpdate(dur time.Duration) { | |
| 490 | wg := new(sync.WaitGroup) | |
| 491 | for i := 0; i < len(s.ewmaDecorators); i++ { | |
| 492 | switch d := s.ewmaDecorators[i]; i { | |
| 493 | case len(s.ewmaDecorators) - 1: | |
| 494 | d.EwmaUpdate(s.lastIncrement, dur) | |
| 495 | default: | |
| 496 | wg.Add(1) | |
| 497 | go func() { | |
| 498 | d.EwmaUpdate(s.lastIncrement, dur) | |
| 499 | wg.Done() | |
| 500 | }() | |
| 501 | } | |
| 502 | } | |
| 503 | wg.Wait() | |
| 504 | } | |
| 505 | ||
| 506 | func (s bState) decoratorAverageAdjust(start time.Time) { | |
| 507 | wg := new(sync.WaitGroup) | |
| 508 | for i := 0; i < len(s.averageDecorators); i++ { | |
| 509 | switch d := s.averageDecorators[i]; i { | |
| 510 | case len(s.averageDecorators) - 1: | |
| 511 | d.AverageAdjust(start) | |
| 512 | default: | |
| 513 | wg.Add(1) | |
| 514 | go func() { | |
| 515 | d.AverageAdjust(start) | |
| 516 | wg.Done() | |
| 517 | }() | |
| 518 | } | |
| 519 | } | |
| 520 | wg.Wait() | |
| 521 | } | |
| 522 | ||
| 523 | func (s bState) decoratorShutdownNotify() { | |
| 524 | wg := new(sync.WaitGroup) | |
| 525 | for i := 0; i < len(s.shutdownListeners); i++ { | |
| 526 | switch d := s.shutdownListeners[i]; i { | |
| 527 | case len(s.shutdownListeners) - 1: | |
| 528 | d.Shutdown() | |
| 529 | default: | |
| 530 | wg.Add(1) | |
| 531 | go func() { | |
| 532 | d.Shutdown() | |
| 533 | wg.Done() | |
| 534 | }() | |
| 535 | } | |
| 536 | } | |
| 537 | wg.Wait() | |
| 538 | } | |
| 539 | ||
| 540 | func newStatistics(tw int, s *bState) decor.Statistics { | |
| 541 | return decor.Statistics{ | |
| 542 | ID: s.id, | |
| 543 | AvailableWidth: tw, | |
| 544 | Total: s.total, | |
| 545 | Current: s.current, | |
| 546 | Refill: s.refill, | |
| 547 | Completed: s.completeFlushed, | |
| 548 | Aborted: s.aborted, | |
| 456 | 549 | } |
| 457 | 550 | } |
| 458 | 551 | |
| 463 | 556 | return d |
| 464 | 557 | } |
| 465 | 558 | |
| 466 | func ewmaIterationUpdate(done bool, s *bState, dur time.Duration) { | |
| 467 | if !done && !s.iterated { | |
| 468 | panic("increment required before ewma iteration update") | |
| 469 | } else { | |
| 470 | s.iterated = false | |
| 471 | } | |
| 472 | for _, d := range s.ewmaDecorators { | |
| 473 | d.EwmaUpdate(s.lastN, dur) | |
| 474 | } | |
| 475 | } | |
| 559 | func makePanicExtender(p interface{}) extenderFunc { | |
| 560 | pstr := fmt.Sprint(p) | |
| 561 | return func(_ io.Reader, _ int, st decor.Statistics) (io.Reader, int) { | |
| 562 | mr := io.MultiReader( | |
| 563 | strings.NewReader(runewidth.Truncate(pstr, st.AvailableWidth, "…")), | |
| 564 | strings.NewReader("\n"), | |
| 565 | ) | |
| 566 | return mr, 0 | |
| 567 | } | |
| 568 | } | |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "io" |
| 4 | "unicode/utf8" | |
| 5 | 4 | |
| 6 | "github.com/vbauerster/mpb/v5/decor" | |
| 7 | "github.com/vbauerster/mpb/v5/internal" | |
| 5 | "github.com/vbauerster/mpb/v7/decor" | |
| 8 | 6 | ) |
| 9 | 7 | |
| 10 | const ( | |
| 11 | rLeft = iota | |
| 12 | rFill | |
| 13 | rTip | |
| 14 | rEmpty | |
| 15 | rRight | |
| 16 | rRevTip | |
| 17 | rRefill | |
| 18 | ) | |
| 19 | ||
| 20 | // DefaultBarStyle is a string containing 7 runes. | |
| 21 | // Each rune is a building block of a progress bar. | |
| 8 | // BarFiller interface. | |
| 9 | // Bar (without decorators) renders itself by calling BarFiller's Fill method. | |
| 22 | 10 | // |
| 23 | // '1st rune' stands for left boundary rune | |
| 11 | // reqWidth is requested width set by `func WithWidth(int) ContainerOption`. | |
| 12 | // If not set, it defaults to terminal width. | |
| 24 | 13 | // |
| 25 | // '2nd rune' stands for fill rune | |
| 26 | // | |
| 27 | // '3rd rune' stands for tip rune | |
| 28 | // | |
| 29 | // '4th rune' stands for empty rune | |
| 30 | // | |
| 31 | // '5th rune' stands for right boundary rune | |
| 32 | // | |
| 33 | // '6th rune' stands for reverse tip rune | |
| 34 | // | |
| 35 | // '7th rune' stands for refill rune | |
| 36 | // | |
| 37 | const DefaultBarStyle string = "[=>-]<+" | |
| 38 | ||
| 39 | type barFiller struct { | |
| 40 | format [][]byte | |
| 41 | tip []byte | |
| 42 | refill int64 | |
| 43 | reverse bool | |
| 44 | flush func(w io.Writer, bb [][]byte) | |
| 14 | type BarFiller interface { | |
| 15 | Fill(w io.Writer, reqWidth int, stat decor.Statistics) | |
| 45 | 16 | } |
| 46 | 17 | |
| 47 | // NewBarFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. | |
| 48 | func NewBarFiller(style string, reverse bool) BarFiller { | |
| 49 | if style == "" { | |
| 50 | style = DefaultBarStyle | |
| 51 | } | |
| 52 | bf := &barFiller{ | |
| 53 | format: make([][]byte, utf8.RuneCountInString(style)), | |
| 54 | reverse: reverse, | |
| 55 | } | |
| 56 | bf.SetStyle(style) | |
| 57 | return bf | |
| 18 | // BarFillerBuilder interface. | |
| 19 | // Default implementations are: | |
| 20 | // | |
| 21 | // BarStyle() | |
| 22 | // SpinnerStyle() | |
| 23 | // NopStyle() | |
| 24 | // | |
| 25 | type BarFillerBuilder interface { | |
| 26 | Build() BarFiller | |
| 58 | 27 | } |
| 59 | 28 | |
| 60 | func (s *barFiller) SetStyle(style string) { | |
| 61 | if !utf8.ValidString(style) { | |
| 62 | return | |
| 63 | } | |
| 64 | src := make([][]byte, 0, utf8.RuneCountInString(style)) | |
| 65 | for _, r := range style { | |
| 66 | src = append(src, []byte(string(r))) | |
| 67 | } | |
| 68 | copy(s.format, src) | |
| 69 | s.SetReverse(s.reverse) | |
| 29 | // BarFillerFunc is function type adapter to convert compatible function | |
| 30 | // into BarFiller interface. | |
| 31 | type BarFillerFunc func(w io.Writer, reqWidth int, stat decor.Statistics) | |
| 32 | ||
| 33 | func (f BarFillerFunc) Fill(w io.Writer, reqWidth int, stat decor.Statistics) { | |
| 34 | f(w, reqWidth, stat) | |
| 70 | 35 | } |
| 71 | 36 | |
| 72 | func (s *barFiller) SetReverse(reverse bool) { | |
| 73 | if reverse { | |
| 74 | s.tip = s.format[rRevTip] | |
| 75 | s.flush = reverseFlush | |
| 76 | } else { | |
| 77 | s.tip = s.format[rTip] | |
| 78 | s.flush = normalFlush | |
| 79 | } | |
| 80 | s.reverse = reverse | |
| 37 | // BarFillerBuilderFunc is function type adapter to convert compatible | |
| 38 | // function into BarFillerBuilder interface. | |
| 39 | type BarFillerBuilderFunc func() BarFiller | |
| 40 | ||
| 41 | func (f BarFillerBuilderFunc) Build() BarFiller { | |
| 42 | return f() | |
| 81 | 43 | } |
| 82 | 44 | |
| 83 | func (s *barFiller) SetRefill(amount int64) { | |
| 84 | s.refill = amount | |
| 45 | // NewBarFiller constructs a BarFiller from provided BarFillerBuilder. | |
| 46 | // Deprecated. Prefer using `*Progress.New(...)` directly. | |
| 47 | func NewBarFiller(b BarFillerBuilder) BarFiller { | |
| 48 | return b.Build() | |
| 85 | 49 | } |
| 86 | ||
| 87 | func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 88 | // don't count rLeft and rRight as progress | |
| 89 | width -= 2 | |
| 90 | if width < 2 { | |
| 91 | return | |
| 92 | } | |
| 93 | w.Write(s.format[rLeft]) | |
| 94 | defer w.Write(s.format[rRight]) | |
| 95 | ||
| 96 | bb := make([][]byte, width) | |
| 97 | ||
| 98 | cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width)) | |
| 99 | ||
| 100 | for i := 0; i < cwidth; i++ { | |
| 101 | bb[i] = s.format[rFill] | |
| 102 | } | |
| 103 | ||
| 104 | if s.refill > 0 { | |
| 105 | var rwidth int | |
| 106 | if s.refill > stat.Current { | |
| 107 | rwidth = cwidth | |
| 108 | } else { | |
| 109 | rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width)) | |
| 110 | } | |
| 111 | for i := 0; i < rwidth; i++ { | |
| 112 | bb[i] = s.format[rRefill] | |
| 113 | } | |
| 114 | } | |
| 115 | ||
| 116 | if cwidth > 0 && cwidth < width { | |
| 117 | bb[cwidth-1] = s.tip | |
| 118 | } | |
| 119 | ||
| 120 | for i := cwidth; i < width; i++ { | |
| 121 | bb[i] = s.format[rEmpty] | |
| 122 | } | |
| 123 | ||
| 124 | s.flush(w, bb) | |
| 125 | } | |
| 126 | ||
| 127 | func normalFlush(w io.Writer, bb [][]byte) { | |
| 128 | for i := 0; i < len(bb); i++ { | |
| 129 | w.Write(bb[i]) | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 133 | func reverseFlush(w io.Writer, bb [][]byte) { | |
| 134 | for i := len(bb) - 1; i >= 0; i-- { | |
| 135 | w.Write(bb[i]) | |
| 136 | } | |
| 137 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | ||
| 5 | "github.com/acarl005/stripansi" | |
| 6 | "github.com/mattn/go-runewidth" | |
| 7 | "github.com/vbauerster/mpb/v7/decor" | |
| 8 | "github.com/vbauerster/mpb/v7/internal" | |
| 9 | ) | |
| 10 | ||
| 11 | const ( | |
| 12 | iLbound = iota | |
| 13 | iRbound | |
| 14 | iFiller | |
| 15 | iRefiller | |
| 16 | iPadding | |
| 17 | components | |
| 18 | ) | |
| 19 | ||
| 20 | // BarStyleComposer interface. | |
| 21 | type BarStyleComposer interface { | |
| 22 | BarFillerBuilder | |
| 23 | Lbound(string) BarStyleComposer | |
| 24 | Rbound(string) BarStyleComposer | |
| 25 | Filler(string) BarStyleComposer | |
| 26 | Refiller(string) BarStyleComposer | |
| 27 | Padding(string) BarStyleComposer | |
| 28 | TipOnComplete(string) BarStyleComposer | |
| 29 | Tip(frames ...string) BarStyleComposer | |
| 30 | Reverse() BarStyleComposer | |
| 31 | } | |
| 32 | ||
| 33 | type bFiller struct { | |
| 34 | rev bool | |
| 35 | components [components]*component | |
| 36 | tip struct { | |
| 37 | count uint | |
| 38 | onComplete *component | |
| 39 | frames []*component | |
| 40 | } | |
| 41 | } | |
| 42 | ||
| 43 | type component struct { | |
| 44 | width int | |
| 45 | bytes []byte | |
| 46 | } | |
| 47 | ||
| 48 | type barStyle struct { | |
| 49 | lbound string | |
| 50 | rbound string | |
| 51 | filler string | |
| 52 | refiller string | |
| 53 | padding string | |
| 54 | tipOnComplete string | |
| 55 | tipFrames []string | |
| 56 | rev bool | |
| 57 | } | |
| 58 | ||
| 59 | // BarStyle constructs default bar style which can be altered via | |
| 60 | // BarStyleComposer interface. | |
| 61 | func BarStyle() BarStyleComposer { | |
| 62 | return &barStyle{ | |
| 63 | lbound: "[", | |
| 64 | rbound: "]", | |
| 65 | filler: "=", | |
| 66 | refiller: "+", | |
| 67 | padding: "-", | |
| 68 | tipFrames: []string{">"}, | |
| 69 | } | |
| 70 | } | |
| 71 | ||
| 72 | func (s *barStyle) Lbound(bound string) BarStyleComposer { | |
| 73 | s.lbound = bound | |
| 74 | return s | |
| 75 | } | |
| 76 | ||
| 77 | func (s *barStyle) Rbound(bound string) BarStyleComposer { | |
| 78 | s.rbound = bound | |
| 79 | return s | |
| 80 | } | |
| 81 | ||
| 82 | func (s *barStyle) Filler(filler string) BarStyleComposer { | |
| 83 | s.filler = filler | |
| 84 | return s | |
| 85 | } | |
| 86 | ||
| 87 | func (s *barStyle) Refiller(refiller string) BarStyleComposer { | |
| 88 | s.refiller = refiller | |
| 89 | return s | |
| 90 | } | |
| 91 | ||
| 92 | func (s *barStyle) Padding(padding string) BarStyleComposer { | |
| 93 | s.padding = padding | |
| 94 | return s | |
| 95 | } | |
| 96 | ||
| 97 | func (s *barStyle) TipOnComplete(tip string) BarStyleComposer { | |
| 98 | s.tipOnComplete = tip | |
| 99 | return s | |
| 100 | } | |
| 101 | ||
| 102 | func (s *barStyle) Tip(frames ...string) BarStyleComposer { | |
| 103 | if len(frames) != 0 { | |
| 104 | s.tipFrames = append(s.tipFrames[:0], frames...) | |
| 105 | } | |
| 106 | return s | |
| 107 | } | |
| 108 | ||
| 109 | func (s *barStyle) Reverse() BarStyleComposer { | |
| 110 | s.rev = true | |
| 111 | return s | |
| 112 | } | |
| 113 | ||
| 114 | func (s *barStyle) Build() BarFiller { | |
| 115 | bf := &bFiller{rev: s.rev} | |
| 116 | bf.components[iLbound] = &component{ | |
| 117 | width: runewidth.StringWidth(stripansi.Strip(s.lbound)), | |
| 118 | bytes: []byte(s.lbound), | |
| 119 | } | |
| 120 | bf.components[iRbound] = &component{ | |
| 121 | width: runewidth.StringWidth(stripansi.Strip(s.rbound)), | |
| 122 | bytes: []byte(s.rbound), | |
| 123 | } | |
| 124 | bf.components[iFiller] = &component{ | |
| 125 | width: runewidth.StringWidth(stripansi.Strip(s.filler)), | |
| 126 | bytes: []byte(s.filler), | |
| 127 | } | |
| 128 | bf.components[iRefiller] = &component{ | |
| 129 | width: runewidth.StringWidth(stripansi.Strip(s.refiller)), | |
| 130 | bytes: []byte(s.refiller), | |
| 131 | } | |
| 132 | bf.components[iPadding] = &component{ | |
| 133 | width: runewidth.StringWidth(stripansi.Strip(s.padding)), | |
| 134 | bytes: []byte(s.padding), | |
| 135 | } | |
| 136 | bf.tip.onComplete = &component{ | |
| 137 | width: runewidth.StringWidth(stripansi.Strip(s.tipOnComplete)), | |
| 138 | bytes: []byte(s.tipOnComplete), | |
| 139 | } | |
| 140 | bf.tip.frames = make([]*component, len(s.tipFrames)) | |
| 141 | for i, t := range s.tipFrames { | |
| 142 | bf.tip.frames[i] = &component{ | |
| 143 | width: runewidth.StringWidth(stripansi.Strip(t)), | |
| 144 | bytes: []byte(t), | |
| 145 | } | |
| 146 | } | |
| 147 | return bf | |
| 148 | } | |
| 149 | ||
| 150 | func (s *bFiller) Fill(w io.Writer, width int, stat decor.Statistics) { | |
| 151 | width = internal.CheckRequestedWidth(width, stat.AvailableWidth) | |
| 152 | brackets := s.components[iLbound].width + s.components[iRbound].width | |
| 153 | // don't count brackets as progress | |
| 154 | width -= brackets | |
| 155 | if width < 0 { | |
| 156 | return | |
| 157 | } | |
| 158 | ||
| 159 | ow := optimisticWriter(w) | |
| 160 | ow(s.components[iLbound].bytes) | |
| 161 | defer ow(s.components[iRbound].bytes) | |
| 162 | ||
| 163 | if width == 0 { | |
| 164 | return | |
| 165 | } | |
| 166 | ||
| 167 | var filling [][]byte | |
| 168 | var padding [][]byte | |
| 169 | var tip *component | |
| 170 | var filled int | |
| 171 | var refWidth int | |
| 172 | curWidth := int(internal.PercentageRound(stat.Total, stat.Current, uint(width))) | |
| 173 | ||
| 174 | if stat.Current >= stat.Total { | |
| 175 | tip = s.tip.onComplete | |
| 176 | } else { | |
| 177 | tip = s.tip.frames[s.tip.count%uint(len(s.tip.frames))] | |
| 178 | } | |
| 179 | ||
| 180 | if curWidth > 0 { | |
| 181 | filling = append(filling, tip.bytes) | |
| 182 | filled += tip.width | |
| 183 | s.tip.count++ | |
| 184 | } | |
| 185 | ||
| 186 | if stat.Refill > 0 { | |
| 187 | refWidth = int(internal.PercentageRound(stat.Total, stat.Refill, uint(width))) | |
| 188 | curWidth -= refWidth | |
| 189 | refWidth += curWidth | |
| 190 | } | |
| 191 | ||
| 192 | for filled < curWidth { | |
| 193 | if curWidth-filled >= s.components[iFiller].width { | |
| 194 | filling = append(filling, s.components[iFiller].bytes) | |
| 195 | if s.components[iFiller].width == 0 { | |
| 196 | break | |
| 197 | } | |
| 198 | filled += s.components[iFiller].width | |
| 199 | } else { | |
| 200 | filling = append(filling, []byte("…")) | |
| 201 | filled++ | |
| 202 | } | |
| 203 | } | |
| 204 | ||
| 205 | for filled < refWidth { | |
| 206 | if refWidth-filled >= s.components[iRefiller].width { | |
| 207 | filling = append(filling, s.components[iRefiller].bytes) | |
| 208 | if s.components[iRefiller].width == 0 { | |
| 209 | break | |
| 210 | } | |
| 211 | filled += s.components[iRefiller].width | |
| 212 | } else { | |
| 213 | filling = append(filling, []byte("…")) | |
| 214 | filled++ | |
| 215 | } | |
| 216 | } | |
| 217 | ||
| 218 | padWidth := width - filled | |
| 219 | for padWidth > 0 { | |
| 220 | if padWidth >= s.components[iPadding].width { | |
| 221 | padding = append(padding, s.components[iPadding].bytes) | |
| 222 | if s.components[iPadding].width == 0 { | |
| 223 | break | |
| 224 | } | |
| 225 | padWidth -= s.components[iPadding].width | |
| 226 | } else { | |
| 227 | padding = append(padding, []byte("…")) | |
| 228 | padWidth-- | |
| 229 | } | |
| 230 | } | |
| 231 | ||
| 232 | if s.rev { | |
| 233 | flush(ow, padding, filling) | |
| 234 | } else { | |
| 235 | flush(ow, filling, padding) | |
| 236 | } | |
| 237 | } | |
| 238 | ||
| 239 | func flush(ow func([]byte), filling, padding [][]byte) { | |
| 240 | for i := len(filling) - 1; i >= 0; i-- { | |
| 241 | ow(filling[i]) | |
| 242 | } | |
| 243 | for i := 0; i < len(padding); i++ { | |
| 244 | ow(padding[i]) | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | func optimisticWriter(w io.Writer) func([]byte) { | |
| 249 | return func(p []byte) { | |
| 250 | _, err := w.Write(p) | |
| 251 | if err != nil { | |
| 252 | panic(err) | |
| 253 | } | |
| 254 | } | |
| 255 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | ||
| 5 | "github.com/vbauerster/mpb/v7/decor" | |
| 6 | ) | |
| 7 | ||
| 8 | // NopStyle provides BarFillerBuilder which builds NOP BarFiller. | |
| 9 | func NopStyle() BarFillerBuilder { | |
| 10 | return BarFillerBuilderFunc(func() BarFiller { | |
| 11 | return BarFillerFunc(func(io.Writer, int, decor.Statistics) {}) | |
| 12 | }) | |
| 13 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "strings" | |
| 5 | ||
| 6 | "github.com/acarl005/stripansi" | |
| 7 | "github.com/mattn/go-runewidth" | |
| 8 | "github.com/vbauerster/mpb/v7/decor" | |
| 9 | "github.com/vbauerster/mpb/v7/internal" | |
| 10 | ) | |
| 11 | ||
| 12 | const ( | |
| 13 | positionLeft = 1 + iota | |
| 14 | positionRight | |
| 15 | ) | |
| 16 | ||
| 17 | // SpinnerStyleComposer interface. | |
| 18 | type SpinnerStyleComposer interface { | |
| 19 | BarFillerBuilder | |
| 20 | PositionLeft() SpinnerStyleComposer | |
| 21 | PositionRight() SpinnerStyleComposer | |
| 22 | } | |
| 23 | ||
| 24 | type sFiller struct { | |
| 25 | count uint | |
| 26 | position uint | |
| 27 | frames []string | |
| 28 | } | |
| 29 | ||
| 30 | type spinnerStyle struct { | |
| 31 | position uint | |
| 32 | frames []string | |
| 33 | } | |
| 34 | ||
| 35 | // SpinnerStyle constructs default spinner style which can be altered via | |
| 36 | // SpinnerStyleComposer interface. | |
| 37 | func SpinnerStyle(frames ...string) SpinnerStyleComposer { | |
| 38 | ss := new(spinnerStyle) | |
| 39 | if len(frames) != 0 { | |
| 40 | ss.frames = append(ss.frames, frames...) | |
| 41 | } else { | |
| 42 | ss.frames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 43 | } | |
| 44 | return ss | |
| 45 | } | |
| 46 | ||
| 47 | func (s *spinnerStyle) PositionLeft() SpinnerStyleComposer { | |
| 48 | s.position = positionLeft | |
| 49 | return s | |
| 50 | } | |
| 51 | ||
| 52 | func (s *spinnerStyle) PositionRight() SpinnerStyleComposer { | |
| 53 | s.position = positionRight | |
| 54 | return s | |
| 55 | } | |
| 56 | ||
| 57 | func (s *spinnerStyle) Build() BarFiller { | |
| 58 | sf := &sFiller{ | |
| 59 | position: s.position, | |
| 60 | frames: s.frames, | |
| 61 | } | |
| 62 | return sf | |
| 63 | } | |
| 64 | ||
| 65 | func (s *sFiller) Fill(w io.Writer, width int, stat decor.Statistics) { | |
| 66 | width = internal.CheckRequestedWidth(width, stat.AvailableWidth) | |
| 67 | ||
| 68 | frame := s.frames[s.count%uint(len(s.frames))] | |
| 69 | frameWidth := runewidth.StringWidth(stripansi.Strip(frame)) | |
| 70 | ||
| 71 | if width < frameWidth { | |
| 72 | return | |
| 73 | } | |
| 74 | ||
| 75 | var err error | |
| 76 | rest := width - frameWidth | |
| 77 | switch s.position { | |
| 78 | case positionLeft: | |
| 79 | _, err = io.WriteString(w, frame+strings.Repeat(" ", rest)) | |
| 80 | case positionRight: | |
| 81 | _, err = io.WriteString(w, strings.Repeat(" ", rest)+frame) | |
| 82 | default: | |
| 83 | str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) | |
| 84 | _, err = io.WriteString(w, str) | |
| 85 | } | |
| 86 | if err != nil { | |
| 87 | panic(err) | |
| 88 | } | |
| 89 | s.count++ | |
| 90 | } |
| 3 | 3 | "bytes" |
| 4 | 4 | "io" |
| 5 | 5 | |
| 6 | "github.com/vbauerster/mpb/v5/decor" | |
| 6 | "github.com/vbauerster/mpb/v7/decor" | |
| 7 | 7 | ) |
| 8 | 8 | |
| 9 | // BarOption is a function option which changes the default behavior of a bar. | |
| 9 | // BarOption is a func option to alter default behavior of a bar. | |
| 10 | 10 | type BarOption func(*bState) |
| 11 | ||
| 12 | func skipNil(decorators []decor.Decorator) (filtered []decor.Decorator) { | |
| 13 | for _, d := range decorators { | |
| 14 | if d != nil { | |
| 15 | filtered = append(filtered, d) | |
| 16 | } | |
| 17 | } | |
| 18 | return | |
| 19 | } | |
| 11 | 20 | |
| 12 | 21 | func (s *bState) addDecorators(dest *[]decor.Decorator, decorators ...decor.Decorator) { |
| 13 | 22 | type mergeWrapper interface { |
| 24 | 33 | // AppendDecorators let you inject decorators to the bar's right side. |
| 25 | 34 | func AppendDecorators(decorators ...decor.Decorator) BarOption { |
| 26 | 35 | return func(s *bState) { |
| 27 | s.addDecorators(&s.aDecorators, decorators...) | |
| 36 | s.addDecorators(&s.aDecorators, skipNil(decorators)...) | |
| 28 | 37 | } |
| 29 | 38 | } |
| 30 | 39 | |
| 31 | 40 | // PrependDecorators let you inject decorators to the bar's left side. |
| 32 | 41 | func PrependDecorators(decorators ...decor.Decorator) BarOption { |
| 33 | 42 | return func(s *bState) { |
| 34 | s.addDecorators(&s.pDecorators, decorators...) | |
| 43 | s.addDecorators(&s.pDecorators, skipNil(decorators)...) | |
| 35 | 44 | } |
| 36 | 45 | } |
| 37 | 46 | |
| 45 | 54 | // BarWidth sets bar width independent of the container. |
| 46 | 55 | func BarWidth(width int) BarOption { |
| 47 | 56 | return func(s *bState) { |
| 48 | s.width = width | |
| 57 | s.reqWidth = width | |
| 49 | 58 | } |
| 50 | 59 | } |
| 51 | 60 | |
| 76 | 85 | |
| 77 | 86 | // BarFillerOnComplete replaces bar's filler with message, on complete event. |
| 78 | 87 | func BarFillerOnComplete(message string) BarOption { |
| 79 | return func(s *bState) { | |
| 80 | s.filler = makeBarFillerOnComplete(s.baseF, message) | |
| 81 | } | |
| 88 | return BarFillerMiddleware(func(base BarFiller) BarFiller { | |
| 89 | return BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) { | |
| 90 | if st.Completed { | |
| 91 | _, err := io.WriteString(w, message) | |
| 92 | if err != nil { | |
| 93 | panic(err) | |
| 94 | } | |
| 95 | } else { | |
| 96 | base.Fill(w, reqWidth, st) | |
| 97 | } | |
| 98 | }) | |
| 99 | }) | |
| 82 | 100 | } |
| 83 | 101 | |
| 84 | func makeBarFillerOnComplete(filler BarFiller, message string) BarFiller { | |
| 85 | return BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { | |
| 86 | if st.Completed { | |
| 87 | io.WriteString(w, message) | |
| 88 | } else { | |
| 89 | filler.Fill(w, width, st) | |
| 90 | } | |
| 91 | }) | |
| 102 | // BarFillerMiddleware provides a way to augment the underlying BarFiller. | |
| 103 | func BarFillerMiddleware(middle func(BarFiller) BarFiller) BarOption { | |
| 104 | return func(s *bState) { | |
| 105 | s.middleware = middle | |
| 106 | } | |
| 92 | 107 | } |
| 93 | 108 | |
| 94 | 109 | // BarPriority sets bar's priority. Zero is highest priority, i.e. bar |
| 100 | 115 | } |
| 101 | 116 | } |
| 102 | 117 | |
| 103 | // BarExtender is an option to extend bar to the next new line, with | |
| 104 | // arbitrary output. | |
| 105 | func BarExtender(extender BarFiller) BarOption { | |
| 106 | if extender == nil { | |
| 118 | // BarExtender provides a way to extend bar to the next new line. | |
| 119 | func BarExtender(filler BarFiller) BarOption { | |
| 120 | if filler == nil { | |
| 107 | 121 | return nil |
| 108 | 122 | } |
| 109 | 123 | return func(s *bState) { |
| 110 | s.extender = makeExtFunc(extender) | |
| 124 | s.extender = makeExtenderFunc(filler) | |
| 111 | 125 | } |
| 112 | 126 | } |
| 113 | 127 | |
| 114 | func makeExtFunc(extender BarFiller) extFunc { | |
| 128 | func makeExtenderFunc(filler BarFiller) extenderFunc { | |
| 115 | 129 | buf := new(bytes.Buffer) |
| 116 | nl := []byte("\n") | |
| 117 | return func(r io.Reader, tw int, st *decor.Statistics) (io.Reader, int) { | |
| 118 | extender.Fill(buf, tw, st) | |
| 119 | return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), nl) | |
| 130 | return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) { | |
| 131 | filler.Fill(buf, reqWidth, st) | |
| 132 | return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n")) | |
| 120 | 133 | } |
| 121 | 134 | } |
| 122 | 135 | |
| 123 | // TrimSpace trims bar's edge spaces. | |
| 124 | func TrimSpace() BarOption { | |
| 136 | // BarFillerTrim removes leading and trailing space around the underlying BarFiller. | |
| 137 | func BarFillerTrim() BarOption { | |
| 125 | 138 | return func(s *bState) { |
| 126 | 139 | s.trimSpace = true |
| 127 | } | |
| 128 | } | |
| 129 | ||
| 130 | // BarStyle overrides mpb.DefaultBarStyle which is "[=>-]<+". | |
| 131 | // It's ok to pass string containing just 5 runes, for example "╢▌▌░╟", | |
| 132 | // if you don't need to override '<' (reverse tip) and '+' (refill rune). | |
| 133 | func BarStyle(style string) BarOption { | |
| 134 | if style == "" { | |
| 135 | return nil | |
| 136 | } | |
| 137 | type styleSetter interface { | |
| 138 | SetStyle(string) | |
| 139 | } | |
| 140 | return func(s *bState) { | |
| 141 | if t, ok := s.baseF.(styleSetter); ok { | |
| 142 | t.SetStyle(style) | |
| 143 | } | |
| 144 | 140 | } |
| 145 | 141 | } |
| 146 | 142 | |
| 152 | 148 | } |
| 153 | 149 | } |
| 154 | 150 | |
| 155 | // BarReverse reverse mode, bar will progress from right to left. | |
| 156 | func BarReverse() BarOption { | |
| 157 | type revSetter interface { | |
| 158 | SetReverse(bool) | |
| 159 | } | |
| 160 | return func(s *bState) { | |
| 161 | if t, ok := s.baseF.(revSetter); ok { | |
| 162 | t.SetReverse(true) | |
| 163 | } | |
| 164 | } | |
| 165 | } | |
| 166 | ||
| 167 | // SpinnerStyle sets custom spinner style. | |
| 168 | // Effective when Filler type is spinner. | |
| 169 | func SpinnerStyle(frames []string) BarOption { | |
| 170 | if len(frames) == 0 { | |
| 171 | return nil | |
| 172 | } | |
| 173 | chk := func(filler BarFiller) (interface{}, bool) { | |
| 174 | t, ok := filler.(*spinnerFiller) | |
| 175 | return t, ok | |
| 176 | } | |
| 177 | cb := func(t interface{}) { | |
| 178 | t.(*spinnerFiller).frames = frames | |
| 179 | } | |
| 180 | return MakeFillerTypeSpecificBarOption(chk, cb) | |
| 181 | } | |
| 182 | ||
| 183 | // MakeFillerTypeSpecificBarOption makes BarOption specific to Filler's | |
| 184 | // actual type. If you implement your own Filler, so most probably | |
| 185 | // you'll need this. See BarStyle or SpinnerStyle for example. | |
| 186 | func MakeFillerTypeSpecificBarOption( | |
| 187 | typeChecker func(BarFiller) (interface{}, bool), | |
| 188 | cb func(interface{}), | |
| 189 | ) BarOption { | |
| 190 | return func(s *bState) { | |
| 191 | if t, ok := typeChecker(s.baseF); ok { | |
| 192 | cb(t) | |
| 193 | } | |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | // BarOptOn returns option when condition evaluates to true. | |
| 198 | func BarOptOn(option BarOption, condition func() bool) BarOption { | |
| 199 | if condition() { | |
| 151 | // BarOptional will invoke provided option only when cond is true. | |
| 152 | func BarOptional(option BarOption, cond bool) BarOption { | |
| 153 | if cond { | |
| 200 | 154 | return option |
| 201 | 155 | } |
| 202 | 156 | return nil |
| 203 | 157 | } |
| 158 | ||
| 159 | // BarOptOn will invoke provided option only when higher order predicate | |
| 160 | // evaluates to true. | |
| 161 | func BarOptOn(option BarOption, predicate func() bool) BarOption { | |
| 162 | if predicate() { | |
| 163 | return option | |
| 164 | } | |
| 165 | return nil | |
| 166 | } | |
| 9 | 9 | "time" |
| 10 | 10 | "unicode/utf8" |
| 11 | 11 | |
| 12 | . "github.com/vbauerster/mpb/v5" | |
| 13 | "github.com/vbauerster/mpb/v5/decor" | |
| 12 | "github.com/vbauerster/mpb/v7" | |
| 13 | "github.com/vbauerster/mpb/v7/decor" | |
| 14 | 14 | ) |
| 15 | 15 | |
| 16 | 16 | func TestBarCompleted(t *testing.T) { |
| 17 | p := New(WithOutput(ioutil.Discard)) | |
| 17 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(ioutil.Discard)) | |
| 18 | 18 | total := 80 |
| 19 | 19 | bar := p.AddBar(int64(total)) |
| 20 | 20 | |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | func TestBarID(t *testing.T) { |
| 35 | p := New(WithOutput(ioutil.Discard)) | |
| 35 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(ioutil.Discard)) | |
| 36 | 36 | total := 100 |
| 37 | 37 | wantID := 11 |
| 38 | bar := p.AddBar(int64(total), BarID(wantID)) | |
| 39 | ||
| 40 | go func(total int) { | |
| 38 | bar := p.AddBar(int64(total), mpb.BarID(wantID)) | |
| 39 | ||
| 40 | go func() { | |
| 41 | 41 | for i := 0; i < total; i++ { |
| 42 | 42 | time.Sleep(50 * time.Millisecond) |
| 43 | 43 | bar.Increment() |
| 44 | 44 | } |
| 45 | }(total) | |
| 45 | }() | |
| 46 | 46 | |
| 47 | 47 | gotID := bar.ID() |
| 48 | 48 | if gotID != wantID { |
| 56 | 56 | func TestBarSetRefill(t *testing.T) { |
| 57 | 57 | var buf bytes.Buffer |
| 58 | 58 | |
| 59 | width := 100 | |
| 60 | p := New(WithOutput(&buf), WithWidth(width)) | |
| 59 | p := mpb.New(mpb.WithOutput(&buf), mpb.WithWidth(100)) | |
| 61 | 60 | |
| 62 | 61 | total := 100 |
| 63 | 62 | till := 30 |
| 64 | refillRune, _ := utf8.DecodeLastRuneInString(DefaultBarStyle) | |
| 65 | ||
| 66 | bar := p.AddBar(int64(total), TrimSpace()) | |
| 63 | refiller := "+" | |
| 64 | ||
| 65 | bar := p.New(int64(total), mpb.BarStyle().Refiller(refiller), mpb.BarFillerTrim()) | |
| 67 | 66 | |
| 68 | 67 | bar.SetRefill(int64(till)) |
| 69 | 68 | bar.IncrBy(till) |
| 76 | 75 | p.Wait() |
| 77 | 76 | |
| 78 | 77 | wantBar := fmt.Sprintf("[%s%s]", |
| 79 | strings.Repeat(string(refillRune), till-1), | |
| 78 | strings.Repeat(refiller, till-1), | |
| 80 | 79 | strings.Repeat("=", total-till-1), |
| 81 | 80 | ) |
| 82 | 81 | |
| 90 | 89 | func TestBarHas100PercentWithOnCompleteDecorator(t *testing.T) { |
| 91 | 90 | var buf bytes.Buffer |
| 92 | 91 | |
| 93 | p := New(WithOutput(&buf)) | |
| 92 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(&buf)) | |
| 94 | 93 | |
| 95 | 94 | total := 50 |
| 96 | 95 | |
| 97 | 96 | bar := p.AddBar(int64(total), |
| 98 | AppendDecorators( | |
| 97 | mpb.AppendDecorators( | |
| 99 | 98 | decor.OnComplete( |
| 100 | 99 | decor.Percentage(), "done", |
| 101 | 100 | ), |
| 118 | 117 | func TestBarHas100PercentWithBarRemoveOnComplete(t *testing.T) { |
| 119 | 118 | var buf bytes.Buffer |
| 120 | 119 | |
| 121 | p := New(WithOutput(&buf)) | |
| 120 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(&buf)) | |
| 122 | 121 | |
| 123 | 122 | total := 50 |
| 124 | 123 | |
| 125 | 124 | bar := p.AddBar(int64(total), |
| 126 | BarRemoveOnComplete(), | |
| 127 | AppendDecorators(decor.Percentage()), | |
| 125 | mpb.BarRemoveOnComplete(), | |
| 126 | mpb.AppendDecorators(decor.Percentage()), | |
| 128 | 127 | ) |
| 129 | 128 | |
| 130 | 129 | for i := 0; i < total; i++ { |
| 143 | 142 | func TestBarStyle(t *testing.T) { |
| 144 | 143 | var buf bytes.Buffer |
| 145 | 144 | customFormat := "╢▌▌░╟" |
| 146 | p := New(WithOutput(&buf)) | |
| 145 | runes := []rune(customFormat) | |
| 147 | 146 | total := 80 |
| 148 | bar := p.AddBar(int64(total), BarStyle(customFormat), TrimSpace()) | |
| 149 | ||
| 150 | for i := 0; i < total; i++ { | |
| 151 | bar.Increment() | |
| 152 | time.Sleep(10 * time.Millisecond) | |
| 153 | } | |
| 154 | ||
| 155 | p.Wait() | |
| 156 | ||
| 157 | runes := []rune(customFormat) | |
| 158 | wantBar := fmt.Sprintf("%s%s%s", | |
| 147 | p := mpb.New(mpb.WithWidth(total), mpb.WithOutput(&buf)) | |
| 148 | bs := mpb.BarStyle() | |
| 149 | bs.Lbound(string(runes[0])) | |
| 150 | bs.Filler(string(runes[1])) | |
| 151 | bs.Tip(string(runes[2])) | |
| 152 | bs.Padding(string(runes[3])) | |
| 153 | bs.Rbound(string(runes[4])) | |
| 154 | bar := p.New(int64(total), bs, mpb.BarFillerTrim()) | |
| 155 | ||
| 156 | for i := 0; i < total; i++ { | |
| 157 | bar.Increment() | |
| 158 | time.Sleep(10 * time.Millisecond) | |
| 159 | } | |
| 160 | ||
| 161 | p.Wait() | |
| 162 | ||
| 163 | wantBar := fmt.Sprintf("%s%s%s%s", | |
| 159 | 164 | string(runes[0]), |
| 160 | strings.Repeat(string(runes[1]), total-2), | |
| 161 | string(runes[len(runes)-1]), | |
| 165 | strings.Repeat(string(runes[1]), total-3), | |
| 166 | string(runes[2]), | |
| 167 | string(runes[4]), | |
| 162 | 168 | ) |
| 163 | 169 | got := string(getLastLine(buf.Bytes())) |
| 164 | 170 | |
| 169 | 175 | |
| 170 | 176 | func TestBarPanicBeforeComplete(t *testing.T) { |
| 171 | 177 | var buf bytes.Buffer |
| 172 | p := New(WithDebugOutput(&buf), WithOutput(ioutil.Discard)) | |
| 178 | p := mpb.New( | |
| 179 | mpb.WithWidth(80), | |
| 180 | mpb.WithDebugOutput(&buf), | |
| 181 | mpb.WithOutput(ioutil.Discard), | |
| 182 | ) | |
| 173 | 183 | |
| 174 | 184 | total := 100 |
| 175 | 185 | panicMsg := "Upps!!!" |
| 176 | 186 | var pCount uint32 |
| 177 | 187 | bar := p.AddBar(int64(total), |
| 178 | PrependDecorators(panicDecorator(panicMsg, | |
| 179 | func(st *decor.Statistics) bool { | |
| 188 | mpb.PrependDecorators(panicDecorator(panicMsg, | |
| 189 | func(st decor.Statistics) bool { | |
| 180 | 190 | if st.Current >= 42 { |
| 181 | 191 | atomic.AddUint32(&pCount, 1) |
| 182 | 192 | return true |
| 205 | 215 | |
| 206 | 216 | func TestBarPanicAfterComplete(t *testing.T) { |
| 207 | 217 | var buf bytes.Buffer |
| 208 | p := New(WithDebugOutput(&buf), WithOutput(ioutil.Discard)) | |
| 218 | p := mpb.New( | |
| 219 | mpb.WithWidth(80), | |
| 220 | mpb.WithDebugOutput(&buf), | |
| 221 | mpb.WithOutput(ioutil.Discard), | |
| 222 | ) | |
| 209 | 223 | |
| 210 | 224 | total := 100 |
| 211 | 225 | panicMsg := "Upps!!!" |
| 212 | 226 | var pCount uint32 |
| 213 | 227 | bar := p.AddBar(int64(total), |
| 214 | PrependDecorators(panicDecorator(panicMsg, | |
| 215 | func(st *decor.Statistics) bool { | |
| 228 | mpb.PrependDecorators(panicDecorator(panicMsg, | |
| 229 | func(st decor.Statistics) bool { | |
| 216 | 230 | if st.Completed { |
| 217 | 231 | atomic.AddUint32(&pCount, 1) |
| 218 | 232 | return true |
| 229 | 243 | |
| 230 | 244 | p.Wait() |
| 231 | 245 | |
| 232 | if pCount != 1 { | |
| 233 | t.Errorf("Decor called after panic %d times\n", pCount-1) | |
| 246 | if pCount > 2 { | |
| 247 | t.Error("Decor called after panic more than 2 times\n") | |
| 234 | 248 | } |
| 235 | 249 | |
| 236 | 250 | barStr := buf.String() |
| 239 | 253 | } |
| 240 | 254 | } |
| 241 | 255 | |
| 242 | func panicDecorator(panicMsg string, cond func(*decor.Statistics) bool) decor.Decorator { | |
| 243 | d := &decorator{ | |
| 244 | panicMsg: panicMsg, | |
| 245 | cond: cond, | |
| 246 | } | |
| 247 | d.Init() | |
| 248 | return d | |
| 249 | } | |
| 250 | ||
| 251 | type decorator struct { | |
| 252 | decor.WC | |
| 253 | panicMsg string | |
| 254 | cond func(*decor.Statistics) bool | |
| 255 | } | |
| 256 | ||
| 257 | func (d *decorator) Decor(st *decor.Statistics) string { | |
| 258 | if d.cond(st) { | |
| 259 | panic(d.panicMsg) | |
| 260 | } | |
| 261 | return d.FormatMsg("") | |
| 262 | } | |
| 256 | func TestDecorStatisticsAvailableWidth(t *testing.T) { | |
| 257 | total := 100 | |
| 258 | down := make(chan struct{}) | |
| 259 | checkDone := make(chan struct{}) | |
| 260 | td1 := func(s decor.Statistics) string { | |
| 261 | if s.AvailableWidth != 80 { | |
| 262 | t.Errorf("expected AvailableWidth %d got %d\n", 80, s.AvailableWidth) | |
| 263 | } | |
| 264 | return fmt.Sprintf("\x1b[31;1;4m%s\x1b[0m", strings.Repeat("0", 20)) | |
| 265 | } | |
| 266 | td2 := func(s decor.Statistics) string { | |
| 267 | defer func() { | |
| 268 | select { | |
| 269 | case checkDone <- struct{}{}: | |
| 270 | default: | |
| 271 | } | |
| 272 | }() | |
| 273 | if s.AvailableWidth != 40 { | |
| 274 | t.Errorf("expected AvailableWidth %d got %d\n", 40, s.AvailableWidth) | |
| 275 | } | |
| 276 | return "" | |
| 277 | } | |
| 278 | p := mpb.New( | |
| 279 | mpb.WithWidth(100), | |
| 280 | mpb.WithShutdownNotifier(down), | |
| 281 | mpb.WithOutput(ioutil.Discard), | |
| 282 | ) | |
| 283 | bar := p.AddBar(int64(total), | |
| 284 | mpb.BarFillerTrim(), | |
| 285 | mpb.PrependDecorators( | |
| 286 | decor.Name(strings.Repeat("0", 20)), | |
| 287 | decor.Any(td1), | |
| 288 | ), | |
| 289 | mpb.AppendDecorators( | |
| 290 | decor.Name(strings.Repeat("0", 20)), | |
| 291 | decor.Any(td2), | |
| 292 | ), | |
| 293 | ) | |
| 294 | go func() { | |
| 295 | for { | |
| 296 | select { | |
| 297 | case <-checkDone: | |
| 298 | bar.Abort(true) | |
| 299 | case <-down: | |
| 300 | return | |
| 301 | } | |
| 302 | } | |
| 303 | }() | |
| 304 | for !bar.Completed() { | |
| 305 | bar.Increment() | |
| 306 | } | |
| 307 | p.Wait() | |
| 308 | } | |
| 309 | ||
| 310 | func panicDecorator(panicMsg string, cond func(decor.Statistics) bool) decor.Decorator { | |
| 311 | return decor.Any(func(st decor.Statistics) string { | |
| 312 | if cond(st) { | |
| 313 | panic(panicMsg) | |
| 314 | } | |
| 315 | return "" | |
| 316 | }) | |
| 317 | } | |
| 0 | 0 | package mpb |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | "io/ioutil" | |
| 3 | "sync" | |
| 4 | 4 | "testing" |
| 5 | ||
| 6 | "github.com/vbauerster/mpb/v5/decor" | |
| 7 | 5 | ) |
| 8 | 6 | |
| 9 | func BenchmarkIncrSingleBar(b *testing.B) { | |
| 10 | p := New(WithOutput(ioutil.Discard)) | |
| 11 | bar := p.AddBar(int64(b.N)) | |
| 12 | for i := 0; i < b.N; i++ { | |
| 13 | bar.Increment() | |
| 14 | } | |
| 7 | const total = 1000 | |
| 8 | ||
| 9 | func BenchmarkIncrementOneBar(b *testing.B) { | |
| 10 | benchBody(1, b) | |
| 15 | 11 | } |
| 16 | 12 | |
| 17 | func BenchmarkIncrSingleBarWhileIsNotCompleted(b *testing.B) { | |
| 18 | p := New(WithOutput(ioutil.Discard)) | |
| 19 | bar := p.AddBar(int64(b.N)) | |
| 20 | for !bar.Completed() { | |
| 21 | bar.Increment() | |
| 22 | } | |
| 13 | func BenchmarkIncrementTwoBars(b *testing.B) { | |
| 14 | benchBody(2, b) | |
| 23 | 15 | } |
| 24 | 16 | |
| 25 | func BenchmarkIncrSingleBarWithNameDecorator(b *testing.B) { | |
| 26 | p := New(WithOutput(ioutil.Discard)) | |
| 27 | bar := p.AddBar(int64(b.N), PrependDecorators(decor.Name("test"))) | |
| 28 | for i := 0; i < b.N; i++ { | |
| 29 | bar.Increment() | |
| 30 | } | |
| 17 | func BenchmarkIncrementThreeBars(b *testing.B) { | |
| 18 | benchBody(3, b) | |
| 31 | 19 | } |
| 32 | 20 | |
| 33 | func BenchmarkIncrSingleBarWithNameAndEwmaETADecorator(b *testing.B) { | |
| 34 | p := New(WithOutput(ioutil.Discard)) | |
| 35 | bar := p.AddBar(int64(b.N), | |
| 36 | PrependDecorators(decor.Name("test")), | |
| 37 | AppendDecorators(decor.EwmaETA(decor.ET_STYLE_GO, 60)), | |
| 38 | ) | |
| 21 | func BenchmarkIncrementFourBars(b *testing.B) { | |
| 22 | benchBody(4, b) | |
| 23 | } | |
| 24 | ||
| 25 | func benchBody(n int, b *testing.B) { | |
| 26 | p := New(WithOutput(nil), WithWidth(80)) | |
| 27 | wg := new(sync.WaitGroup) | |
| 28 | b.ResetTimer() | |
| 39 | 29 | for i := 0; i < b.N; i++ { |
| 40 | bar.Increment() | |
| 30 | for j := 0; j < n; j++ { | |
| 31 | switch j { | |
| 32 | case n - 1: | |
| 33 | bar := p.AddBar(total) | |
| 34 | for c := 0; c < total; c++ { | |
| 35 | bar.Increment() | |
| 36 | } | |
| 37 | if !bar.Completed() { | |
| 38 | b.Fail() | |
| 39 | } | |
| 40 | default: | |
| 41 | wg.Add(1) | |
| 42 | go func() { | |
| 43 | bar := p.AddBar(total) | |
| 44 | for c := 0; c < total; c++ { | |
| 45 | bar.Increment() | |
| 46 | } | |
| 47 | if !bar.Completed() { | |
| 48 | b.Fail() | |
| 49 | } | |
| 50 | wg.Done() | |
| 51 | }() | |
| 52 | } | |
| 53 | } | |
| 54 | wg.Wait() | |
| 41 | 55 | } |
| 56 | p.Wait() | |
| 42 | 57 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "io/ioutil" | |
| 5 | "sync" | |
| 6 | "time" | |
| 7 | ) | |
| 8 | ||
| 9 | // ContainerOption is a func option to alter default behavior of a bar | |
| 10 | // container. Container term refers to a Progress struct which can | |
| 11 | // hold one or more Bars. | |
| 12 | type ContainerOption func(*pState) | |
| 13 | ||
| 14 | // WithWaitGroup provides means to have a single joint point. If | |
| 15 | // *sync.WaitGroup is provided, you can safely call just p.Wait() | |
| 16 | // without calling Wait() on provided *sync.WaitGroup. Makes sense | |
| 17 | // when there are more than one bar to render. | |
| 18 | func WithWaitGroup(wg *sync.WaitGroup) ContainerOption { | |
| 19 | return func(s *pState) { | |
| 20 | s.uwg = wg | |
| 21 | } | |
| 22 | } | |
| 23 | ||
| 24 | // WithWidth sets container width. If not set it defaults to terminal | |
| 25 | // width. A bar added to the container will inherit its width, unless | |
| 26 | // overridden by `func BarWidth(int) BarOption`. | |
| 27 | func WithWidth(width int) ContainerOption { | |
| 28 | return func(s *pState) { | |
| 29 | s.reqWidth = width | |
| 30 | } | |
| 31 | } | |
| 32 | ||
| 33 | // WithRefreshRate overrides default 120ms refresh rate. | |
| 34 | func WithRefreshRate(d time.Duration) ContainerOption { | |
| 35 | return func(s *pState) { | |
| 36 | s.rr = d | |
| 37 | } | |
| 38 | } | |
| 39 | ||
| 40 | // WithManualRefresh disables internal auto refresh time.Ticker. | |
| 41 | // Refresh will occur upon receive value from provided ch. | |
| 42 | func WithManualRefresh(ch <-chan interface{}) ContainerOption { | |
| 43 | return func(s *pState) { | |
| 44 | s.externalRefresh = ch | |
| 45 | } | |
| 46 | } | |
| 47 | ||
| 48 | // WithRenderDelay delays rendering. By default rendering starts as | |
| 49 | // soon as bar is added, with this option it's possible to delay | |
| 50 | // rendering process by keeping provided chan unclosed. In other words | |
| 51 | // rendering will start as soon as provided chan is closed. | |
| 52 | func WithRenderDelay(ch <-chan struct{}) ContainerOption { | |
| 53 | return func(s *pState) { | |
| 54 | s.renderDelay = ch | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | // WithShutdownNotifier provided chanel will be closed, after all bars | |
| 59 | // have been rendered. | |
| 60 | func WithShutdownNotifier(ch chan struct{}) ContainerOption { | |
| 61 | return func(s *pState) { | |
| 62 | select { | |
| 63 | case <-ch: | |
| 64 | default: | |
| 65 | s.shutdownNotifier = ch | |
| 66 | } | |
| 67 | } | |
| 68 | } | |
| 69 | ||
| 70 | // WithOutput overrides default os.Stdout output. Setting it to nil | |
| 71 | // will effectively disable auto refresh rate and discard any output, | |
| 72 | // useful if you want to disable progress bars with little overhead. | |
| 73 | func WithOutput(w io.Writer) ContainerOption { | |
| 74 | return func(s *pState) { | |
| 75 | if w == nil { | |
| 76 | s.output = ioutil.Discard | |
| 77 | s.outputDiscarded = true | |
| 78 | return | |
| 79 | } | |
| 80 | s.output = w | |
| 81 | } | |
| 82 | } | |
| 83 | ||
| 84 | // WithDebugOutput sets debug output. | |
| 85 | func WithDebugOutput(w io.Writer) ContainerOption { | |
| 86 | if w == nil { | |
| 87 | return nil | |
| 88 | } | |
| 89 | return func(s *pState) { | |
| 90 | s.debugOut = w | |
| 91 | } | |
| 92 | } | |
| 93 | ||
| 94 | // PopCompletedMode will pop and stop rendering completed bars. | |
| 95 | func PopCompletedMode() ContainerOption { | |
| 96 | return func(s *pState) { | |
| 97 | s.popCompleted = true | |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | // ContainerOptional will invoke provided option only when cond is true. | |
| 102 | func ContainerOptional(option ContainerOption, cond bool) ContainerOption { | |
| 103 | if cond { | |
| 104 | return option | |
| 105 | } | |
| 106 | return nil | |
| 107 | } | |
| 108 | ||
| 109 | // ContainerOptOn will invoke provided option only when higher order | |
| 110 | // predicate evaluates to true. | |
| 111 | func ContainerOptOn(option ContainerOption, predicate func() bool) ContainerOption { | |
| 112 | if predicate() { | |
| 113 | return option | |
| 114 | } | |
| 115 | return nil | |
| 116 | } |
| 0 | package cwriter | |
| 1 | ||
| 2 | import ( | |
| 3 | "bytes" | |
| 4 | "fmt" | |
| 5 | "io/ioutil" | |
| 6 | "strconv" | |
| 7 | "testing" | |
| 8 | ) | |
| 9 | ||
| 10 | func BenchmarkWithFprintf(b *testing.B) { | |
| 11 | cuuAndEd := "\x1b[%dA\x1b[J" | |
| 12 | for i := 0; i < b.N; i++ { | |
| 13 | fmt.Fprintf(ioutil.Discard, cuuAndEd, 4) | |
| 14 | } | |
| 15 | } | |
| 16 | ||
| 17 | func BenchmarkWithJoin(b *testing.B) { | |
| 18 | bCuuAndEd := [][]byte{[]byte("\x1b["), []byte("A\x1b[J")} | |
| 19 | for i := 0; i < b.N; i++ { | |
| 20 | _, _ = ioutil.Discard.Write(bytes.Join(bCuuAndEd, []byte(strconv.Itoa(4)))) | |
| 21 | } | |
| 22 | } | |
| 23 | ||
| 24 | func BenchmarkWithAppend(b *testing.B) { | |
| 25 | escOpen := []byte("\x1b[") | |
| 26 | cuuAndEd := []byte("A\x1b[J") | |
| 27 | for i := 0; i < b.N; i++ { | |
| 28 | _, _ = ioutil.Discard.Write(append(strconv.AppendInt(escOpen, 4, 10), cuuAndEd...)) | |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | func BenchmarkWithCopy(b *testing.B) { | |
| 33 | w := New(ioutil.Discard) | |
| 34 | w.lines = 4 | |
| 35 | for i := 0; i < b.N; i++ { | |
| 36 | _ = w.ansiCuuAndEd() | |
| 37 | } | |
| 38 | } |
| 0 | // +build darwin dragonfly freebsd netbsd openbsd | |
| 1 | ||
| 2 | package cwriter | |
| 3 | ||
| 4 | import "golang.org/x/sys/unix" | |
| 5 | ||
| 6 | const ioctlReadTermios = unix.TIOCGETA |
| 0 | // +build aix linux | |
| 1 | ||
| 2 | package cwriter | |
| 3 | ||
| 4 | import "golang.org/x/sys/unix" | |
| 5 | ||
| 6 | const ioctlReadTermios = unix.TCGETS |
| 0 | // +build solaris | |
| 1 | ||
| 2 | package cwriter | |
| 3 | ||
| 4 | import "golang.org/x/sys/unix" | |
| 5 | ||
| 6 | const ioctlReadTermios = unix.TCGETA |
| 0 | // +build zos | |
| 1 | ||
| 2 | package cwriter | |
| 3 | ||
| 4 | import "golang.org/x/sys/unix" | |
| 5 | ||
| 6 | const ioctlReadTermios = unix.TCGETS |
| 2 | 2 | import ( |
| 3 | 3 | "bytes" |
| 4 | 4 | "errors" |
| 5 | "fmt" | |
| 6 | 5 | "io" |
| 7 | 6 | "os" |
| 8 | ||
| 9 | "golang.org/x/crypto/ssh/terminal" | |
| 7 | "strconv" | |
| 10 | 8 | ) |
| 11 | 9 | |
| 12 | // NotATTY not a TeleTYpewriter error. | |
| 13 | var NotATTY = errors.New("not a terminal") | |
| 10 | // ErrNotTTY not a TeleTYpewriter error. | |
| 11 | var ErrNotTTY = errors.New("not a terminal") | |
| 14 | 12 | |
| 15 | var cuuAndEd = fmt.Sprintf("%c[%%dA%[1]c[J", 27) | |
| 13 | // http://ascii-table.com/ansi-escape-sequences.php | |
| 14 | const ( | |
| 15 | escOpen = "\x1b[" | |
| 16 | cuuAndEd = "A\x1b[J" | |
| 17 | ) | |
| 16 | 18 | |
| 17 | 19 | // Writer is a buffered the writer that updates the terminal. The |
| 18 | 20 | // contents of writer will be flushed when Flush is called. |
| 19 | 21 | type Writer struct { |
| 20 | 22 | out io.Writer |
| 21 | 23 | buf bytes.Buffer |
| 22 | lineCount int | |
| 23 | fd uintptr | |
| 24 | lines int | |
| 25 | fd int | |
| 24 | 26 | isTerminal bool |
| 25 | 27 | } |
| 26 | 28 | |
| 28 | 30 | func New(out io.Writer) *Writer { |
| 29 | 31 | w := &Writer{out: out} |
| 30 | 32 | if f, ok := out.(*os.File); ok { |
| 31 | w.fd = f.Fd() | |
| 32 | w.isTerminal = terminal.IsTerminal(int(w.fd)) | |
| 33 | w.fd = int(f.Fd()) | |
| 34 | w.isTerminal = IsTerminal(w.fd) | |
| 33 | 35 | } |
| 34 | 36 | return w |
| 35 | 37 | } |
| 36 | 38 | |
| 37 | 39 | // Flush flushes the underlying buffer. |
| 38 | func (w *Writer) Flush(lineCount int) (err error) { | |
| 39 | if w.lineCount > 0 { | |
| 40 | w.clearLines() | |
| 40 | func (w *Writer) Flush(lines int) (err error) { | |
| 41 | // some terminals interpret 'cursor up 0' as 'cursor up 1' | |
| 42 | if w.lines > 0 { | |
| 43 | err = w.clearLines() | |
| 44 | if err != nil { | |
| 45 | return | |
| 46 | } | |
| 41 | 47 | } |
| 42 | w.lineCount = lineCount | |
| 48 | w.lines = lines | |
| 43 | 49 | _, err = w.buf.WriteTo(w.out) |
| 44 | 50 | return |
| 45 | 51 | } |
| 62 | 68 | |
| 63 | 69 | // GetWidth returns width of underlying terminal. |
| 64 | 70 | func (w *Writer) GetWidth() (int, error) { |
| 65 | if w.isTerminal { | |
| 66 | tw, _, err := terminal.GetSize(int(w.fd)) | |
| 67 | return tw, err | |
| 71 | if !w.isTerminal { | |
| 72 | return -1, ErrNotTTY | |
| 68 | 73 | } |
| 69 | return -1, NotATTY | |
| 74 | tw, _, err := GetSize(w.fd) | |
| 75 | return tw, err | |
| 70 | 76 | } |
| 77 | ||
| 78 | func (w *Writer) ansiCuuAndEd() error { | |
| 79 | buf := make([]byte, 8) | |
| 80 | buf = strconv.AppendInt(buf[:copy(buf, escOpen)], int64(w.lines), 10) | |
| 81 | _, err := w.out.Write(append(buf, cuuAndEd...)) | |
| 82 | return err | |
| 83 | } | |
| 1 | 1 | |
| 2 | 2 | package cwriter |
| 3 | 3 | |
| 4 | import "fmt" | |
| 4 | import ( | |
| 5 | "golang.org/x/sys/unix" | |
| 6 | ) | |
| 5 | 7 | |
| 6 | func (w *Writer) clearLines() { | |
| 7 | fmt.Fprintf(w.out, cuuAndEd, w.lineCount) | |
| 8 | func (w *Writer) clearLines() error { | |
| 9 | return w.ansiCuuAndEd() | |
| 8 | 10 | } |
| 11 | ||
| 12 | // GetSize returns the dimensions of the given terminal. | |
| 13 | func GetSize(fd int) (width, height int, err error) { | |
| 14 | ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) | |
| 15 | if err != nil { | |
| 16 | return -1, -1, err | |
| 17 | } | |
| 18 | return int(ws.Col), int(ws.Row), nil | |
| 19 | } | |
| 20 | ||
| 21 | // IsTerminal returns whether the given file descriptor is a terminal. | |
| 22 | func IsTerminal(fd int) bool { | |
| 23 | _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) | |
| 24 | return err == nil | |
| 25 | } |
| 2 | 2 | package cwriter |
| 3 | 3 | |
| 4 | 4 | import ( |
| 5 | "fmt" | |
| 6 | "syscall" | |
| 7 | 5 | "unsafe" |
| 6 | ||
| 7 | "golang.org/x/sys/windows" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") | |
| 10 | var kernel32 = windows.NewLazySystemDLL("kernel32.dll") | |
| 11 | 11 | |
| 12 | 12 | var ( |
| 13 | procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") | |
| 14 | 13 | procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") |
| 15 | 14 | procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") |
| 16 | procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") | |
| 17 | 15 | ) |
| 18 | 16 | |
| 19 | type coord struct { | |
| 20 | x int16 | |
| 21 | y int16 | |
| 17 | func (w *Writer) clearLines() error { | |
| 18 | if !w.isTerminal { | |
| 19 | // hope it's cygwin or similar | |
| 20 | return w.ansiCuuAndEd() | |
| 21 | } | |
| 22 | ||
| 23 | var info windows.ConsoleScreenBufferInfo | |
| 24 | if err := windows.GetConsoleScreenBufferInfo(windows.Handle(w.fd), &info); err != nil { | |
| 25 | return err | |
| 26 | } | |
| 27 | ||
| 28 | info.CursorPosition.Y -= int16(w.lines) | |
| 29 | if info.CursorPosition.Y < 0 { | |
| 30 | info.CursorPosition.Y = 0 | |
| 31 | } | |
| 32 | _, _, _ = procSetConsoleCursorPosition.Call( | |
| 33 | uintptr(w.fd), | |
| 34 | uintptr(uint32(uint16(info.CursorPosition.Y))<<16|uint32(uint16(info.CursorPosition.X))), | |
| 35 | ) | |
| 36 | ||
| 37 | // clear the lines | |
| 38 | cursor := &windows.Coord{ | |
| 39 | X: info.Window.Left, | |
| 40 | Y: info.CursorPosition.Y, | |
| 41 | } | |
| 42 | count := uint32(info.Size.X) * uint32(w.lines) | |
| 43 | _, _, _ = procFillConsoleOutputCharacter.Call( | |
| 44 | uintptr(w.fd), | |
| 45 | uintptr(' '), | |
| 46 | uintptr(count), | |
| 47 | *(*uintptr)(unsafe.Pointer(cursor)), | |
| 48 | uintptr(unsafe.Pointer(new(uint32))), | |
| 49 | ) | |
| 50 | return nil | |
| 22 | 51 | } |
| 23 | 52 | |
| 24 | type smallRect struct { | |
| 25 | left int16 | |
| 26 | top int16 | |
| 27 | right int16 | |
| 28 | bottom int16 | |
| 53 | // GetSize returns the visible dimensions of the given terminal. | |
| 54 | // | |
| 55 | // These dimensions don't include any scrollback buffer height. | |
| 56 | func GetSize(fd int) (width, height int, err error) { | |
| 57 | var info windows.ConsoleScreenBufferInfo | |
| 58 | if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { | |
| 59 | return 0, 0, err | |
| 60 | } | |
| 61 | // terminal.GetSize from crypto/ssh adds "+ 1" to both width and height: | |
| 62 | // https://go.googlesource.com/crypto/+/refs/heads/release-branch.go1.14/ssh/terminal/util_windows.go#75 | |
| 63 | // but looks like this is a root cause of issue #66, so removing both "+ 1" have fixed it. | |
| 64 | return int(info.Window.Right - info.Window.Left), int(info.Window.Bottom - info.Window.Top), nil | |
| 29 | 65 | } |
| 30 | 66 | |
| 31 | type consoleScreenBufferInfo struct { | |
| 32 | size coord | |
| 33 | cursorPosition coord | |
| 34 | attributes uint16 | |
| 35 | window smallRect | |
| 36 | maximumWindowSize coord | |
| 67 | // IsTerminal returns whether the given file descriptor is a terminal. | |
| 68 | func IsTerminal(fd int) bool { | |
| 69 | var st uint32 | |
| 70 | err := windows.GetConsoleMode(windows.Handle(fd), &st) | |
| 71 | return err == nil | |
| 37 | 72 | } |
| 38 | ||
| 39 | func (w *Writer) clearLines() { | |
| 40 | if !w.isTerminal { | |
| 41 | fmt.Fprintf(w.out, cuuAndEd, w.lineCount) | |
| 42 | } | |
| 43 | var info consoleScreenBufferInfo | |
| 44 | procGetConsoleScreenBufferInfo.Call(w.fd, uintptr(unsafe.Pointer(&info))) | |
| 45 | ||
| 46 | info.cursorPosition.y -= int16(w.lineCount) | |
| 47 | if info.cursorPosition.y < 0 { | |
| 48 | info.cursorPosition.y = 0 | |
| 49 | } | |
| 50 | procSetConsoleCursorPosition.Call(w.fd, uintptr(uint32(uint16(info.cursorPosition.y))<<16|uint32(uint16(info.cursorPosition.x)))) | |
| 51 | ||
| 52 | // clear the lines | |
| 53 | cursor := coord{ | |
| 54 | x: info.window.left, | |
| 55 | y: info.cursorPosition.y, | |
| 56 | } | |
| 57 | count := uint32(info.size.x) * uint32(w.lineCount) | |
| 58 | procFillConsoleOutputCharacter.Call(w.fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(new(uint32)))) | |
| 59 | } |
| 0 | 0 | package decor |
| 1 | 1 | |
| 2 | 2 | // Any decorator displays text, that can be changed during decorator's |
| 3 | // lifetime via provided func call back. | |
| 3 | // lifetime via provided DecorFunc. | |
| 4 | 4 | // |
| 5 | // `f` call back which provides string to display | |
| 5 | // `fn` DecorFunc callback | |
| 6 | 6 | // |
| 7 | 7 | // `wcc` optional WC config |
| 8 | 8 | // |
| 9 | func Any(f func(*Statistics) string, wcc ...WC) Decorator { | |
| 10 | return &any{initWC(wcc...), f} | |
| 9 | func Any(fn DecorFunc, wcc ...WC) Decorator { | |
| 10 | return &any{initWC(wcc...), fn} | |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | 13 | type any struct { |
| 14 | 14 | WC |
| 15 | f func(*Statistics) string | |
| 15 | fn DecorFunc | |
| 16 | 16 | } |
| 17 | 17 | |
| 18 | func (d *any) Decor(s *Statistics) string { | |
| 19 | return d.FormatMsg(d.f(s)) | |
| 18 | func (d *any) Decor(s Statistics) string { | |
| 19 | return d.FormatMsg(d.fn(s)) | |
| 20 | 20 | } |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | "strings" | |
| 4 | 5 | ) |
| 5 | 6 | |
| 6 | 7 | const ( |
| 30 | 31 | // |
| 31 | 32 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit |
| 32 | 33 | // |
| 33 | // `pairFmt` printf compatible verbs for current and total, like "%f" or "%d" | |
| 34 | // `pairFmt` printf compatible verbs for current and total pair | |
| 34 | 35 | // |
| 35 | 36 | // `wcc` optional WC config |
| 36 | 37 | // |
| 42 | 43 | // pairFmt="% d / % d" output: "1 MB / 12 MB" |
| 43 | 44 | // |
| 44 | 45 | func Counters(unit int, pairFmt string, wcc ...WC) Decorator { |
| 45 | return Any(chooseSizeProducer(unit, pairFmt), wcc...) | |
| 46 | } | |
| 47 | ||
| 48 | func chooseSizeProducer(unit int, format string) func(*Statistics) string { | |
| 49 | if format == "" { | |
| 50 | format = "%d / %d" | |
| 51 | } | |
| 52 | switch unit { | |
| 53 | case UnitKiB: | |
| 54 | return func(s *Statistics) string { | |
| 55 | return fmt.Sprintf(format, SizeB1024(s.Current), SizeB1024(s.Total)) | |
| 56 | } | |
| 57 | case UnitKB: | |
| 58 | return func(s *Statistics) string { | |
| 59 | return fmt.Sprintf(format, SizeB1000(s.Current), SizeB1000(s.Total)) | |
| 60 | } | |
| 61 | default: | |
| 62 | return func(s *Statistics) string { | |
| 63 | return fmt.Sprintf(format, s.Current, s.Total) | |
| 64 | } | |
| 65 | } | |
| 66 | } | |
| 46 | producer := func(unit int, pairFmt string) DecorFunc { | |
| 47 | if pairFmt == "" { | |
| 48 | pairFmt = "%d / %d" | |
| 49 | } else if strings.Count(pairFmt, "%") != 2 { | |
| 50 | panic("expected pairFmt with exactly 2 verbs") | |
| 51 | } | |
| 52 | switch unit { | |
| 53 | case UnitKiB: | |
| 54 | return func(s Statistics) string { | |
| 55 | return fmt.Sprintf(pairFmt, SizeB1024(s.Current), SizeB1024(s.Total)) | |
| 56 | } | |
| 57 | case UnitKB: | |
| 58 | return func(s Statistics) string { | |
| 59 | return fmt.Sprintf(pairFmt, SizeB1000(s.Current), SizeB1000(s.Total)) | |
| 60 | } | |
| 61 | default: | |
| 62 | return func(s Statistics) string { | |
| 63 | return fmt.Sprintf(pairFmt, s.Current, s.Total) | |
| 64 | } | |
| 65 | } | |
| 66 | } | |
| 67 | return Any(producer(unit, pairFmt), wcc...) | |
| 68 | } | |
| 69 | ||
| 70 | // TotalNoUnit is a wrapper around Total with no unit param. | |
| 71 | func TotalNoUnit(format string, wcc ...WC) Decorator { | |
| 72 | return Total(0, format, wcc...) | |
| 73 | } | |
| 74 | ||
| 75 | // TotalKibiByte is a wrapper around Total with predefined unit | |
| 76 | // UnitKiB (bytes/1024). | |
| 77 | func TotalKibiByte(format string, wcc ...WC) Decorator { | |
| 78 | return Total(UnitKiB, format, wcc...) | |
| 79 | } | |
| 80 | ||
| 81 | // TotalKiloByte is a wrapper around Total with predefined unit | |
| 82 | // UnitKB (bytes/1000). | |
| 83 | func TotalKiloByte(format string, wcc ...WC) Decorator { | |
| 84 | return Total(UnitKB, format, wcc...) | |
| 85 | } | |
| 86 | ||
| 87 | // Total decorator with dynamic unit measure adjustment. | |
| 88 | // | |
| 89 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit | |
| 90 | // | |
| 91 | // `format` printf compatible verb for Total | |
| 92 | // | |
| 93 | // `wcc` optional WC config | |
| 94 | // | |
| 95 | // format example if unit=UnitKiB: | |
| 96 | // | |
| 97 | // format="%.1f" output: "12.0MiB" | |
| 98 | // format="% .1f" output: "12.0 MiB" | |
| 99 | // format="%d" output: "12MiB" | |
| 100 | // format="% d" output: "12 MiB" | |
| 101 | // | |
| 102 | func Total(unit int, format string, wcc ...WC) Decorator { | |
| 103 | producer := func(unit int, format string) DecorFunc { | |
| 104 | if format == "" { | |
| 105 | format = "%d" | |
| 106 | } else if strings.Count(format, "%") != 1 { | |
| 107 | panic("expected format with exactly 1 verb") | |
| 108 | } | |
| 109 | ||
| 110 | switch unit { | |
| 111 | case UnitKiB: | |
| 112 | return func(s Statistics) string { | |
| 113 | return fmt.Sprintf(format, SizeB1024(s.Total)) | |
| 114 | } | |
| 115 | case UnitKB: | |
| 116 | return func(s Statistics) string { | |
| 117 | return fmt.Sprintf(format, SizeB1000(s.Total)) | |
| 118 | } | |
| 119 | default: | |
| 120 | return func(s Statistics) string { | |
| 121 | return fmt.Sprintf(format, s.Total) | |
| 122 | } | |
| 123 | } | |
| 124 | } | |
| 125 | return Any(producer(unit, format), wcc...) | |
| 126 | } | |
| 127 | ||
| 128 | // CurrentNoUnit is a wrapper around Current with no unit param. | |
| 129 | func CurrentNoUnit(format string, wcc ...WC) Decorator { | |
| 130 | return Current(0, format, wcc...) | |
| 131 | } | |
| 132 | ||
| 133 | // CurrentKibiByte is a wrapper around Current with predefined unit | |
| 134 | // UnitKiB (bytes/1024). | |
| 135 | func CurrentKibiByte(format string, wcc ...WC) Decorator { | |
| 136 | return Current(UnitKiB, format, wcc...) | |
| 137 | } | |
| 138 | ||
| 139 | // CurrentKiloByte is a wrapper around Current with predefined unit | |
| 140 | // UnitKB (bytes/1000). | |
| 141 | func CurrentKiloByte(format string, wcc ...WC) Decorator { | |
| 142 | return Current(UnitKB, format, wcc...) | |
| 143 | } | |
| 144 | ||
| 145 | // Current decorator with dynamic unit measure adjustment. | |
| 146 | // | |
| 147 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit | |
| 148 | // | |
| 149 | // `format` printf compatible verb for Current | |
| 150 | // | |
| 151 | // `wcc` optional WC config | |
| 152 | // | |
| 153 | // format example if unit=UnitKiB: | |
| 154 | // | |
| 155 | // format="%.1f" output: "12.0MiB" | |
| 156 | // format="% .1f" output: "12.0 MiB" | |
| 157 | // format="%d" output: "12MiB" | |
| 158 | // format="% d" output: "12 MiB" | |
| 159 | // | |
| 160 | func Current(unit int, format string, wcc ...WC) Decorator { | |
| 161 | producer := func(unit int, format string) DecorFunc { | |
| 162 | if format == "" { | |
| 163 | format = "%d" | |
| 164 | } else if strings.Count(format, "%") != 1 { | |
| 165 | panic("expected format with exactly 1 verb") | |
| 166 | } | |
| 167 | ||
| 168 | switch unit { | |
| 169 | case UnitKiB: | |
| 170 | return func(s Statistics) string { | |
| 171 | return fmt.Sprintf(format, SizeB1024(s.Current)) | |
| 172 | } | |
| 173 | case UnitKB: | |
| 174 | return func(s Statistics) string { | |
| 175 | return fmt.Sprintf(format, SizeB1000(s.Current)) | |
| 176 | } | |
| 177 | default: | |
| 178 | return func(s Statistics) string { | |
| 179 | return fmt.Sprintf(format, s.Current) | |
| 180 | } | |
| 181 | } | |
| 182 | } | |
| 183 | return Any(producer(unit, format), wcc...) | |
| 184 | } | |
| 185 | ||
| 186 | // InvertedCurrentNoUnit is a wrapper around InvertedCurrent with no unit param. | |
| 187 | func InvertedCurrentNoUnit(format string, wcc ...WC) Decorator { | |
| 188 | return InvertedCurrent(0, format, wcc...) | |
| 189 | } | |
| 190 | ||
| 191 | // InvertedCurrentKibiByte is a wrapper around InvertedCurrent with predefined unit | |
| 192 | // UnitKiB (bytes/1024). | |
| 193 | func InvertedCurrentKibiByte(format string, wcc ...WC) Decorator { | |
| 194 | return InvertedCurrent(UnitKiB, format, wcc...) | |
| 195 | } | |
| 196 | ||
| 197 | // InvertedCurrentKiloByte is a wrapper around InvertedCurrent with predefined unit | |
| 198 | // UnitKB (bytes/1000). | |
| 199 | func InvertedCurrentKiloByte(format string, wcc ...WC) Decorator { | |
| 200 | return InvertedCurrent(UnitKB, format, wcc...) | |
| 201 | } | |
| 202 | ||
| 203 | // InvertedCurrent decorator with dynamic unit measure adjustment. | |
| 204 | // | |
| 205 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit | |
| 206 | // | |
| 207 | // `format` printf compatible verb for InvertedCurrent | |
| 208 | // | |
| 209 | // `wcc` optional WC config | |
| 210 | // | |
| 211 | // format example if unit=UnitKiB: | |
| 212 | // | |
| 213 | // format="%.1f" output: "12.0MiB" | |
| 214 | // format="% .1f" output: "12.0 MiB" | |
| 215 | // format="%d" output: "12MiB" | |
| 216 | // format="% d" output: "12 MiB" | |
| 217 | // | |
| 218 | func InvertedCurrent(unit int, format string, wcc ...WC) Decorator { | |
| 219 | producer := func(unit int, format string) DecorFunc { | |
| 220 | if format == "" { | |
| 221 | format = "%d" | |
| 222 | } else if strings.Count(format, "%") != 1 { | |
| 223 | panic("expected format with exactly 1 verb") | |
| 224 | } | |
| 225 | ||
| 226 | switch unit { | |
| 227 | case UnitKiB: | |
| 228 | return func(s Statistics) string { | |
| 229 | return fmt.Sprintf(format, SizeB1024(s.Total-s.Current)) | |
| 230 | } | |
| 231 | case UnitKB: | |
| 232 | return func(s Statistics) string { | |
| 233 | return fmt.Sprintf(format, SizeB1000(s.Total-s.Current)) | |
| 234 | } | |
| 235 | default: | |
| 236 | return func(s Statistics) string { | |
| 237 | return fmt.Sprintf(format, s.Total-s.Current) | |
| 238 | } | |
| 239 | } | |
| 240 | } | |
| 241 | return Any(producer(unit, format), wcc...) | |
| 242 | } | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | 4 | "time" |
| 5 | "unicode/utf8" | |
| 6 | 5 | |
| 7 | 6 | "github.com/acarl005/stripansi" |
| 7 | "github.com/mattn/go-runewidth" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | 10 | const ( |
| 46 | 46 | // Statistics consists of progress related statistics, that Decorator |
| 47 | 47 | // may need. |
| 48 | 48 | type Statistics struct { |
| 49 | ID int | |
| 50 | Completed bool | |
| 51 | Total int64 | |
| 52 | Current int64 | |
| 49 | ID int | |
| 50 | AvailableWidth int | |
| 51 | Total int64 | |
| 52 | Current int64 | |
| 53 | Refill int64 | |
| 54 | Completed bool | |
| 55 | Aborted bool | |
| 53 | 56 | } |
| 54 | 57 | |
| 55 | 58 | // Decorator interface. |
| 56 | // Implementors should embed WC type, that way only single method | |
| 57 | // Decor(*Statistics) needs to be implemented, the rest will be handled | |
| 58 | // by WC type. | |
| 59 | // Most of the time there is no need to implement this interface | |
| 60 | // manually, as decor package already provides a wide range of decorators | |
| 61 | // which implement this interface. If however built-in decorators don't | |
| 62 | // meet your needs, you're free to implement your own one by implementing | |
| 63 | // this particular interface. The easy way to go is to convert a | |
| 64 | // `DecorFunc` into a `Decorator` interface by using provided | |
| 65 | // `func Any(DecorFunc, ...WC) Decorator`. | |
| 59 | 66 | type Decorator interface { |
| 60 | 67 | Configurator |
| 61 | 68 | Synchronizer |
| 62 | Decor(*Statistics) string | |
| 69 | Decor(Statistics) string | |
| 63 | 70 | } |
| 71 | ||
| 72 | // DecorFunc func type. | |
| 73 | // To be used with `func Any`(DecorFunc, ...WC) Decorator`. | |
| 74 | type DecorFunc func(Statistics) string | |
| 64 | 75 | |
| 65 | 76 | // Synchronizer interface. |
| 66 | 77 | // All decorators implement this interface implicitly. Its Sync |
| 116 | 127 | // W represents width and C represents bit set of width related config. |
| 117 | 128 | // A decorator should embed WC, to enable width synchronization. |
| 118 | 129 | type WC struct { |
| 119 | W int | |
| 120 | C int | |
| 121 | dynFormat string | |
| 122 | wsync chan int | |
| 130 | W int | |
| 131 | C int | |
| 132 | fill func(s string, w int) string | |
| 133 | wsync chan int | |
| 123 | 134 | } |
| 124 | 135 | |
| 125 | 136 | // FormatMsg formats final message according to WC.W and WC.C. |
| 126 | 137 | // Should be called by any Decorator implementation. |
| 127 | 138 | func (wc *WC) FormatMsg(msg string) string { |
| 128 | var format string | |
| 129 | runeCount := utf8.RuneCountInString(stripansi.Strip(msg)) | |
| 130 | ansiCount := utf8.RuneCountInString(msg) - runeCount | |
| 139 | pureWidth := runewidth.StringWidth(msg) | |
| 140 | stripWidth := runewidth.StringWidth(stripansi.Strip(msg)) | |
| 141 | maxCell := wc.W | |
| 131 | 142 | if (wc.C & DSyncWidth) != 0 { |
| 143 | cellCount := stripWidth | |
| 132 | 144 | if (wc.C & DextraSpace) != 0 { |
| 133 | runeCount++ | |
| 145 | cellCount++ | |
| 134 | 146 | } |
| 135 | wc.wsync <- runeCount | |
| 136 | max := <-wc.wsync | |
| 137 | format = fmt.Sprintf(wc.dynFormat, ansiCount+max) | |
| 138 | } else { | |
| 139 | format = fmt.Sprintf(wc.dynFormat, ansiCount+wc.W) | |
| 147 | wc.wsync <- cellCount | |
| 148 | maxCell = <-wc.wsync | |
| 140 | 149 | } |
| 141 | return fmt.Sprintf(format, msg) | |
| 150 | return wc.fill(msg, maxCell+(pureWidth-stripWidth)) | |
| 142 | 151 | } |
| 143 | 152 | |
| 144 | 153 | // Init initializes width related config. |
| 145 | 154 | func (wc *WC) Init() WC { |
| 146 | wc.dynFormat = "%%" | |
| 155 | wc.fill = runewidth.FillLeft | |
| 147 | 156 | if (wc.C & DidentRight) != 0 { |
| 148 | wc.dynFormat += "-" | |
| 157 | wc.fill = runewidth.FillRight | |
| 149 | 158 | } |
| 150 | wc.dynFormat += "%ds" | |
| 151 | 159 | if (wc.C & DSyncWidth) != 0 { |
| 152 | 160 | // it's deliberate choice to override wsync on each Init() call, |
| 153 | 161 | // this way globals like WCSyncSpace can be reused |
| 0 | /* | |
| 1 | Package decor provides common decorators for "github.com/vbauerster/mpb/v5" module. | |
| 2 | ||
| 3 | Some decorators returned by this package might have a closure state. It is ok to use | |
| 4 | decorators concurrently, unless you share the same decorator among multiple | |
| 5 | *mpb.Bar instances. To avoid data races, create new decorator per *mpb.Bar instance. | |
| 6 | ||
| 7 | Don't: | |
| 8 | ||
| 9 | p := mpb.New() | |
| 10 | name := decor.Name("bar") | |
| 11 | p.AddBar(100, mpb.AppendDecorators(name)) | |
| 12 | p.AddBar(100, mpb.AppendDecorators(name)) | |
| 13 | ||
| 14 | Do: | |
| 15 | ||
| 16 | p := mpb.New() | |
| 17 | p.AddBar(100, mpb.AppendDecorators(decor.Name("bar1"))) | |
| 18 | p.AddBar(100, mpb.AppendDecorators(decor.Name("bar2"))) | |
| 19 | */ | |
| 0 | // Package decor provides common decorators for "github.com/vbauerster/mpb/v7" module. | |
| 1 | // | |
| 2 | // Some decorators returned by this package might have a closure state. It is ok to use | |
| 3 | // decorators concurrently, unless you share the same decorator among multiple | |
| 4 | // *mpb.Bar instances. To avoid data races, create new decorator per *mpb.Bar instance. | |
| 5 | // | |
| 6 | // Don't: | |
| 7 | // | |
| 8 | // p := mpb.New() | |
| 9 | // name := decor.Name("bar") | |
| 10 | // p.AddBar(100, mpb.AppendDecorators(name)) | |
| 11 | // p.AddBar(100, mpb.AppendDecorators(name)) | |
| 12 | // | |
| 13 | // Do: | |
| 14 | // | |
| 15 | // p := mpb.New() | |
| 16 | // p.AddBar(100, mpb.AppendDecorators(decor.Name("bar1"))) | |
| 17 | // p.AddBar(100, mpb.AppendDecorators(decor.Name("bar2"))) | |
| 20 | 18 | package decor |
| 24 | 24 | func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator { |
| 25 | 25 | var msg string |
| 26 | 26 | producer := chooseTimeProducer(style) |
| 27 | f := func(s *Statistics) string { | |
| 27 | fn := func(s Statistics) string { | |
| 28 | 28 | if !s.Completed { |
| 29 | 29 | msg = producer(time.Since(startTime)) |
| 30 | 30 | } |
| 31 | 31 | return msg |
| 32 | 32 | } |
| 33 | return Any(f, wcc...) | |
| 33 | return Any(fn, wcc...) | |
| 34 | 34 | } |
| 62 | 62 | producer func(time.Duration) string |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | func (d *movingAverageETA) Decor(s *Statistics) string { | |
| 65 | func (d *movingAverageETA) Decor(s Statistics) string { | |
| 66 | 66 | v := math.Round(d.average.Value()) |
| 67 | 67 | remaining := time.Duration((s.Total - s.Current) * int64(v)) |
| 68 | 68 | if d.normalizer != nil { |
| 116 | 116 | producer func(time.Duration) string |
| 117 | 117 | } |
| 118 | 118 | |
| 119 | func (d *averageETA) Decor(s *Statistics) string { | |
| 119 | func (d *averageETA) Decor(s Statistics) string { | |
| 120 | 120 | var remaining time.Duration |
| 121 | 121 | if s.Current != 0 { |
| 122 | 122 | durPerItem := float64(time.Since(d.startTime)) / float64(s.Current) |
| 0 | 0 | package decor |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | "fmt" | |
| 4 | 3 | "strings" |
| 5 | "unicode/utf8" | |
| 4 | ||
| 5 | "github.com/acarl005/stripansi" | |
| 6 | "github.com/mattn/go-runewidth" | |
| 6 | 7 | ) |
| 7 | 8 | |
| 8 | 9 | // Merge wraps its decorator argument with intention to sync width |
| 15 | 16 | // +----+--------+---------+--------+ |
| 16 | 17 | // |
| 17 | 18 | func Merge(decorator Decorator, placeholders ...WC) Decorator { |
| 19 | if decorator == nil { | |
| 20 | return nil | |
| 21 | } | |
| 18 | 22 | if _, ok := decorator.Sync(); !ok || len(placeholders) == 0 { |
| 19 | 23 | return decorator |
| 20 | 24 | } |
| 63 | 67 | return d.Decorator |
| 64 | 68 | } |
| 65 | 69 | |
| 66 | func (d *mergeDecorator) Decor(s *Statistics) string { | |
| 70 | func (d *mergeDecorator) Decor(s Statistics) string { | |
| 67 | 71 | msg := d.Decorator.Decor(s) |
| 68 | msgLen := utf8.RuneCountInString(msg) | |
| 72 | pureWidth := runewidth.StringWidth(msg) | |
| 73 | stripWidth := runewidth.StringWidth(stripansi.Strip(msg)) | |
| 74 | cellCount := stripWidth | |
| 69 | 75 | if (d.wc.C & DextraSpace) != 0 { |
| 70 | msgLen++ | |
| 76 | cellCount++ | |
| 71 | 77 | } |
| 72 | 78 | |
| 73 | var total int | |
| 74 | max := utf8.RuneCountInString(d.placeHolders[0].FormatMsg("")) | |
| 75 | total += max | |
| 76 | pw := (msgLen - max) / len(d.placeHolders) | |
| 77 | rem := (msgLen - max) % len(d.placeHolders) | |
| 79 | total := runewidth.StringWidth(d.placeHolders[0].FormatMsg("")) | |
| 80 | pw := (cellCount - total) / len(d.placeHolders) | |
| 81 | rem := (cellCount - total) % len(d.placeHolders) | |
| 78 | 82 | |
| 79 | 83 | var diff int |
| 80 | 84 | for i := 1; i < len(d.placeHolders); i++ { |
| 86 | 90 | width = 0 |
| 87 | 91 | } |
| 88 | 92 | } |
| 89 | max = utf8.RuneCountInString(ph.FormatMsg(strings.Repeat(" ", width))) | |
| 93 | max := runewidth.StringWidth(ph.FormatMsg(strings.Repeat(" ", width))) | |
| 90 | 94 | total += max |
| 91 | 95 | diff = max - pw |
| 92 | 96 | } |
| 93 | 97 | |
| 94 | 98 | d.wc.wsync <- pw + rem |
| 95 | max = <-d.wc.wsync | |
| 96 | return fmt.Sprintf(fmt.Sprintf(d.wc.dynFormat, max+total), msg) | |
| 99 | max := <-d.wc.wsync | |
| 100 | return d.wc.fill(msg, max+total+(pureWidth-stripWidth)) | |
| 97 | 101 | } |
| 98 | 102 | |
| 99 | 103 | type placeHolderDecorator struct { |
| 100 | 104 | WC |
| 101 | 105 | } |
| 102 | 106 | |
| 103 | func (d *placeHolderDecorator) Decor(*Statistics) string { | |
| 107 | func (d *placeHolderDecorator) Decor(Statistics) string { | |
| 104 | 108 | return "" |
| 105 | 109 | } |
| 7 | 7 | // `wcc` optional WC config |
| 8 | 8 | // |
| 9 | 9 | func Name(str string, wcc ...WC) Decorator { |
| 10 | return Any(func(*Statistics) string { return str }, wcc...) | |
| 10 | return Any(func(Statistics) string { return str }, wcc...) | |
| 11 | 11 | } |
| 0 | package decor | |
| 1 | ||
| 2 | // OnAbort returns decorator, which wraps provided decorator with sole | |
| 3 | // purpose to display provided message on abort event. It has no effect | |
| 4 | // if bar.Abort(drop bool) is called with true argument. | |
| 5 | // | |
| 6 | // `decorator` Decorator to wrap | |
| 7 | // | |
| 8 | // `message` message to display on abort event | |
| 9 | // | |
| 10 | func OnAbort(decorator Decorator, message string) Decorator { | |
| 11 | if decorator == nil { | |
| 12 | return nil | |
| 13 | } | |
| 14 | d := &onAbortWrapper{ | |
| 15 | Decorator: decorator, | |
| 16 | msg: message, | |
| 17 | } | |
| 18 | if md, ok := decorator.(*mergeDecorator); ok { | |
| 19 | d.Decorator, md.Decorator = md.Decorator, d | |
| 20 | return md | |
| 21 | } | |
| 22 | return d | |
| 23 | } | |
| 24 | ||
| 25 | type onAbortWrapper struct { | |
| 26 | Decorator | |
| 27 | msg string | |
| 28 | } | |
| 29 | ||
| 30 | func (d *onAbortWrapper) Decor(s Statistics) string { | |
| 31 | if s.Aborted { | |
| 32 | wc := d.GetConf() | |
| 33 | return wc.FormatMsg(d.msg) | |
| 34 | } | |
| 35 | return d.Decorator.Decor(s) | |
| 36 | } | |
| 37 | ||
| 38 | func (d *onAbortWrapper) Base() Decorator { | |
| 39 | return d.Decorator | |
| 40 | } |
| 0 | 0 | package decor |
| 1 | 1 | |
| 2 | // OnComplete returns decorator, which wraps provided decorator, with | |
| 2 | // OnComplete returns decorator, which wraps provided decorator with | |
| 3 | 3 | // sole purpose to display provided message on complete event. |
| 4 | 4 | // |
| 5 | 5 | // `decorator` Decorator to wrap |
| 7 | 7 | // `message` message to display on complete event |
| 8 | 8 | // |
| 9 | 9 | func OnComplete(decorator Decorator, message string) Decorator { |
| 10 | if decorator == nil { | |
| 11 | return nil | |
| 12 | } | |
| 10 | 13 | d := &onCompleteWrapper{ |
| 11 | 14 | Decorator: decorator, |
| 12 | 15 | msg: message, |
| 23 | 26 | msg string |
| 24 | 27 | } |
| 25 | 28 | |
| 26 | func (d *onCompleteWrapper) Decor(s *Statistics) string { | |
| 29 | func (d *onCompleteWrapper) Decor(s Statistics) string { | |
| 27 | 30 | if s.Completed { |
| 28 | 31 | wc := d.GetConf() |
| 29 | 32 | return wc.FormatMsg(d.msg) |
| 0 | package decor | |
| 1 | ||
| 2 | // OnPredicate returns decorator if predicate evaluates to true. | |
| 3 | // | |
| 4 | // `decorator` Decorator | |
| 5 | // | |
| 6 | // `predicate` func() bool | |
| 7 | // | |
| 8 | func OnPredicate(decorator Decorator, predicate func() bool) Decorator { | |
| 9 | if predicate() { | |
| 10 | return decorator | |
| 11 | } | |
| 12 | return nil | |
| 13 | } | |
| 14 | ||
| 15 | // OnCondition returns decorator if condition is true. | |
| 16 | // | |
| 17 | // `decorator` Decorator | |
| 18 | // | |
| 19 | // `cond` bool | |
| 20 | // | |
| 21 | func OnCondition(decorator Decorator, cond bool) Decorator { | |
| 22 | if cond { | |
| 23 | return decorator | |
| 24 | } | |
| 25 | return nil | |
| 26 | } |
| 0 | package decor | |
| 1 | ||
| 2 | import "io" | |
| 3 | ||
| 4 | func optimisticStringWriter(w io.Writer) func(string) { | |
| 5 | return func(s string) { | |
| 6 | _, err := io.WriteString(w, s) | |
| 7 | if err != nil { | |
| 8 | panic(err) | |
| 9 | } | |
| 10 | } | |
| 11 | } |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | "io" | |
| 5 | 4 | "strconv" |
| 6 | 5 | |
| 7 | "github.com/vbauerster/mpb/v5/internal" | |
| 6 | "github.com/vbauerster/mpb/v7/internal" | |
| 8 | 7 | ) |
| 9 | 8 | |
| 10 | 9 | type percentageType float64 |
| 23 | 22 | } |
| 24 | 23 | } |
| 25 | 24 | |
| 26 | io.WriteString(st, strconv.FormatFloat(float64(s), 'f', prec, 64)) | |
| 27 | ||
| 25 | osw := optimisticStringWriter(st) | |
| 26 | osw(strconv.FormatFloat(float64(s), 'f', prec, 64)) | |
| 28 | 27 | if st.Flag(' ') { |
| 29 | io.WriteString(st, " ") | |
| 28 | osw(" ") | |
| 30 | 29 | } |
| 31 | io.WriteString(st, "%") | |
| 30 | osw("%") | |
| 32 | 31 | } |
| 33 | 32 | |
| 34 | 33 | // Percentage returns percentage decorator. It's a wrapper of NewPercentage. |
| 49 | 48 | if format == "" { |
| 50 | 49 | format = "% d" |
| 51 | 50 | } |
| 52 | f := func(s *Statistics) string { | |
| 51 | f := func(s Statistics) string { | |
| 53 | 52 | p := internal.Percentage(s.Total, s.Current, 100) |
| 54 | 53 | return fmt.Sprintf(format, percentageType(p)) |
| 55 | 54 | } |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | "io" | |
| 5 | "math" | |
| 6 | 4 | "strconv" |
| 7 | 5 | ) |
| 8 | 6 | |
| 46 | 44 | unit = _iMiB |
| 47 | 45 | case self < _iTiB: |
| 48 | 46 | unit = _iGiB |
| 49 | case self <= math.MaxInt64: | |
| 47 | default: | |
| 50 | 48 | unit = _iTiB |
| 51 | 49 | } |
| 52 | 50 | |
| 53 | io.WriteString(st, strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) | |
| 54 | ||
| 51 | osw := optimisticStringWriter(st) | |
| 52 | osw(strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) | |
| 55 | 53 | if st.Flag(' ') { |
| 56 | io.WriteString(st, " ") | |
| 54 | osw(" ") | |
| 57 | 55 | } |
| 58 | io.WriteString(st, unit.String()) | |
| 56 | osw(unit.String()) | |
| 59 | 57 | } |
| 60 | 58 | |
| 61 | 59 | const ( |
| 95 | 93 | unit = _MB |
| 96 | 94 | case self < _TB: |
| 97 | 95 | unit = _GB |
| 98 | case self <= math.MaxInt64: | |
| 96 | default: | |
| 99 | 97 | unit = _TB |
| 100 | 98 | } |
| 101 | 99 | |
| 102 | io.WriteString(st, strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) | |
| 103 | ||
| 100 | osw := optimisticStringWriter(st) | |
| 101 | osw(strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) | |
| 104 | 102 | if st.Flag(' ') { |
| 105 | io.WriteString(st, " ") | |
| 103 | osw(" ") | |
| 106 | 104 | } |
| 107 | io.WriteString(st, unit.String()) | |
| 105 | osw(unit.String()) | |
| 108 | 106 | } |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | "io" | |
| 5 | 4 | "math" |
| 6 | 5 | "time" |
| 7 | 6 | |
| 23 | 22 | |
| 24 | 23 | func (self *speedFormatter) Format(st fmt.State, verb rune) { |
| 25 | 24 | self.Formatter.Format(st, verb) |
| 26 | io.WriteString(st, "/s") | |
| 25 | optimisticStringWriter(st)("/s") | |
| 27 | 26 | } |
| 28 | 27 | |
| 29 | 28 | // EwmaSpeed exponential-weighted-moving-average based speed decorator. |
| 77 | 76 | msg string |
| 78 | 77 | } |
| 79 | 78 | |
| 80 | func (d *movingAverageSpeed) Decor(s *Statistics) string { | |
| 79 | func (d *movingAverageSpeed) Decor(s Statistics) string { | |
| 81 | 80 | if !s.Completed { |
| 82 | 81 | var speed float64 |
| 83 | 82 | if v := d.average.Value(); v > 0 { |
| 139 | 138 | msg string |
| 140 | 139 | } |
| 141 | 140 | |
| 142 | func (d *averageSpeed) Decor(s *Statistics) string { | |
| 141 | func (d *averageSpeed) Decor(s Statistics) string { | |
| 143 | 142 | if !s.Completed { |
| 144 | 143 | speed := float64(s.Current) / float64(time.Since(d.startTime)) |
| 145 | 144 | d.msg = d.producer(speed * 1e9) |
| 121 | 121 | for _, tc := range cases { |
| 122 | 122 | t.Run(tc.name, func(t *testing.T) { |
| 123 | 123 | decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) |
| 124 | stat := &Statistics{ | |
| 124 | stat := Statistics{ | |
| 125 | 125 | Current: tc.current, |
| 126 | 126 | } |
| 127 | 127 | res := decor.Decor(stat) |
| 249 | 249 | for _, tc := range cases { |
| 250 | 250 | t.Run(tc.name, func(t *testing.T) { |
| 251 | 251 | decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) |
| 252 | stat := &Statistics{ | |
| 252 | stat := Statistics{ | |
| 253 | 253 | Current: tc.current, |
| 254 | 254 | } |
| 255 | 255 | res := decor.Decor(stat) |
| 11 | 11 | frames = defaultSpinnerStyle |
| 12 | 12 | } |
| 13 | 13 | var count uint |
| 14 | f := func(s *Statistics) string { | |
| 14 | f := func(s Statistics) string { | |
| 15 | 15 | frame := frames[count%uint(len(frames))] |
| 16 | 16 | count++ |
| 17 | 17 | return frame |
| 3 | 3 | "sync" |
| 4 | 4 | "testing" |
| 5 | 5 | |
| 6 | . "github.com/vbauerster/mpb/v5" | |
| 7 | "github.com/vbauerster/mpb/v5/decor" | |
| 6 | "github.com/vbauerster/mpb/v7" | |
| 7 | "github.com/vbauerster/mpb/v7/decor" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | 10 | func TestNameDecorator(t *testing.T) { |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | for _, test := range tests { |
| 34 | got := test.decorator.Decor(new(decor.Statistics)) | |
| 34 | got := test.decorator.Decor(decor.Statistics{}) | |
| 35 | 35 | if got != test.want { |
| 36 | 36 | t.Errorf("Want: %q, Got: %q\n", test.want, got) |
| 37 | 37 | } |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | 41 | type step struct { |
| 42 | stat *decor.Statistics | |
| 42 | stat decor.Statistics | |
| 43 | 43 | decorator decor.Decorator |
| 44 | 44 | want string |
| 45 | 45 | } |
| 49 | 49 | testCases := [][]step{ |
| 50 | 50 | { |
| 51 | 51 | { |
| 52 | &decor.Statistics{Total: 100, Current: 8}, | |
| 52 | decor.Statistics{Total: 100, Current: 8}, | |
| 53 | 53 | decor.Percentage(decor.WCSyncWidth), |
| 54 | 54 | "8 %", |
| 55 | 55 | }, |
| 56 | 56 | { |
| 57 | &decor.Statistics{Total: 100, Current: 9}, | |
| 57 | decor.Statistics{Total: 100, Current: 9}, | |
| 58 | 58 | decor.Percentage(decor.WCSyncWidth), |
| 59 | 59 | "9 %", |
| 60 | 60 | }, |
| 61 | 61 | }, |
| 62 | 62 | { |
| 63 | 63 | { |
| 64 | &decor.Statistics{Total: 100, Current: 9}, | |
| 64 | decor.Statistics{Total: 100, Current: 9}, | |
| 65 | 65 | decor.Percentage(decor.WCSyncWidth), |
| 66 | 66 | " 9 %", |
| 67 | 67 | }, |
| 68 | 68 | { |
| 69 | &decor.Statistics{Total: 100, Current: 10}, | |
| 69 | decor.Statistics{Total: 100, Current: 10}, | |
| 70 | 70 | decor.Percentage(decor.WCSyncWidth), |
| 71 | 71 | "10 %", |
| 72 | 72 | }, |
| 73 | 73 | }, |
| 74 | 74 | { |
| 75 | 75 | { |
| 76 | &decor.Statistics{Total: 100, Current: 9}, | |
| 76 | decor.Statistics{Total: 100, Current: 9}, | |
| 77 | 77 | decor.Percentage(decor.WCSyncWidth), |
| 78 | 78 | " 9 %", |
| 79 | 79 | }, |
| 80 | 80 | { |
| 81 | &decor.Statistics{Total: 100, Current: 100}, | |
| 81 | decor.Statistics{Total: 100, Current: 100}, | |
| 82 | 82 | decor.Percentage(decor.WCSyncWidth), |
| 83 | 83 | "100 %", |
| 84 | 84 | }, |
| 93 | 93 | testCases := [][]step{ |
| 94 | 94 | { |
| 95 | 95 | { |
| 96 | &decor.Statistics{Total: 100, Current: 8}, | |
| 96 | decor.Statistics{Total: 100, Current: 8}, | |
| 97 | 97 | decor.Percentage(decor.WCSyncWidthR), |
| 98 | 98 | "8 %", |
| 99 | 99 | }, |
| 100 | 100 | { |
| 101 | &decor.Statistics{Total: 100, Current: 9}, | |
| 101 | decor.Statistics{Total: 100, Current: 9}, | |
| 102 | 102 | decor.Percentage(decor.WCSyncWidthR), |
| 103 | 103 | "9 %", |
| 104 | 104 | }, |
| 105 | 105 | }, |
| 106 | 106 | { |
| 107 | 107 | { |
| 108 | &decor.Statistics{Total: 100, Current: 9}, | |
| 108 | decor.Statistics{Total: 100, Current: 9}, | |
| 109 | 109 | decor.Percentage(decor.WCSyncWidthR), |
| 110 | 110 | "9 % ", |
| 111 | 111 | }, |
| 112 | 112 | { |
| 113 | &decor.Statistics{Total: 100, Current: 10}, | |
| 113 | decor.Statistics{Total: 100, Current: 10}, | |
| 114 | 114 | decor.Percentage(decor.WCSyncWidthR), |
| 115 | 115 | "10 %", |
| 116 | 116 | }, |
| 117 | 117 | }, |
| 118 | 118 | { |
| 119 | 119 | { |
| 120 | &decor.Statistics{Total: 100, Current: 9}, | |
| 120 | decor.Statistics{Total: 100, Current: 9}, | |
| 121 | 121 | decor.Percentage(decor.WCSyncWidthR), |
| 122 | 122 | "9 % ", |
| 123 | 123 | }, |
| 124 | 124 | { |
| 125 | &decor.Statistics{Total: 100, Current: 100}, | |
| 125 | decor.Statistics{Total: 100, Current: 100}, | |
| 126 | 126 | decor.Percentage(decor.WCSyncWidthR), |
| 127 | 127 | "100 %", |
| 128 | 128 | }, |
| 137 | 137 | testCases := [][]step{ |
| 138 | 138 | { |
| 139 | 139 | { |
| 140 | &decor.Statistics{Total: 100, Current: 8}, | |
| 140 | decor.Statistics{Total: 100, Current: 8}, | |
| 141 | 141 | decor.Percentage(decor.WCSyncSpace), |
| 142 | 142 | " 8 %", |
| 143 | 143 | }, |
| 144 | 144 | { |
| 145 | &decor.Statistics{Total: 100, Current: 9}, | |
| 145 | decor.Statistics{Total: 100, Current: 9}, | |
| 146 | 146 | decor.Percentage(decor.WCSyncSpace), |
| 147 | 147 | " 9 %", |
| 148 | 148 | }, |
| 149 | 149 | }, |
| 150 | 150 | { |
| 151 | 151 | { |
| 152 | &decor.Statistics{Total: 100, Current: 9}, | |
| 152 | decor.Statistics{Total: 100, Current: 9}, | |
| 153 | 153 | decor.Percentage(decor.WCSyncSpace), |
| 154 | 154 | " 9 %", |
| 155 | 155 | }, |
| 156 | 156 | { |
| 157 | &decor.Statistics{Total: 100, Current: 10}, | |
| 157 | decor.Statistics{Total: 100, Current: 10}, | |
| 158 | 158 | decor.Percentage(decor.WCSyncSpace), |
| 159 | 159 | " 10 %", |
| 160 | 160 | }, |
| 161 | 161 | }, |
| 162 | 162 | { |
| 163 | 163 | { |
| 164 | &decor.Statistics{Total: 100, Current: 9}, | |
| 164 | decor.Statistics{Total: 100, Current: 9}, | |
| 165 | 165 | decor.Percentage(decor.WCSyncSpace), |
| 166 | 166 | " 9 %", |
| 167 | 167 | }, |
| 168 | 168 | { |
| 169 | &decor.Statistics{Total: 100, Current: 100}, | |
| 169 | decor.Statistics{Total: 100, Current: 100}, | |
| 170 | 170 | decor.Percentage(decor.WCSyncSpace), |
| 171 | 171 | " 100 %", |
| 172 | 172 | }, |
| 181 | 181 | t.Fail() |
| 182 | 182 | } |
| 183 | 183 | |
| 184 | numBars := len(testCases[0]) | |
| 185 | var wg sync.WaitGroup | |
| 186 | 184 | for _, columnCase := range testCases { |
| 185 | mpb.SyncWidth(toSyncMatrix(columnCase)) | |
| 186 | numBars := len(columnCase) | |
| 187 | gott := make([]chan string, numBars) | |
| 188 | wg := new(sync.WaitGroup) | |
| 187 | 189 | wg.Add(numBars) |
| 188 | SyncWidth(toSyncMatrix(columnCase)) | |
| 189 | gott := make([]chan string, numBars) | |
| 190 | for i := 0; i < numBars; i++ { | |
| 191 | gott[i] = make(chan string, 1) | |
| 192 | go func(s step, ch chan string) { | |
| 190 | for i, step := range columnCase { | |
| 191 | step := step | |
| 192 | ch := make(chan string, 1) | |
| 193 | go func() { | |
| 193 | 194 | defer wg.Done() |
| 194 | ch <- s.decorator.Decor(s.stat) | |
| 195 | }(columnCase[i], gott[i]) | |
| 195 | ch <- step.decorator.Decor(step.stat) | |
| 196 | }() | |
| 197 | gott[i] = ch | |
| 196 | 198 | } |
| 197 | 199 | wg.Wait() |
| 198 | 200 | |
| 5 | 5 | "unicode/utf8" |
| 6 | 6 | ) |
| 7 | 7 | |
| 8 | func TestDraw(t *testing.T) { | |
| 8 | func TestDrawDefault(t *testing.T) { | |
| 9 | 9 | // key is termWidth |
| 10 | 10 | testSuite := map[int][]struct { |
| 11 | name string | |
| 12 | total, current int64 | |
| 13 | barWidth int | |
| 14 | trimSpace bool | |
| 15 | reverse bool | |
| 16 | rup int64 | |
| 17 | want string | |
| 11 | style BarStyleComposer | |
| 12 | name string | |
| 13 | total int64 | |
| 14 | current int64 | |
| 15 | refill int64 | |
| 16 | barWidth int | |
| 17 | trim bool | |
| 18 | want string | |
| 18 | 19 | }{ |
| 19 | 20 | 0: { |
| 20 | 21 | { |
| 21 | name: "t,c,bw{60,20,80}", | |
| 22 | style: BarStyle(), | |
| 23 | name: "t,c{60,20}", | |
| 24 | total: 60, | |
| 25 | current: 20, | |
| 26 | want: "", | |
| 27 | }, | |
| 28 | { | |
| 29 | style: BarStyle(), | |
| 30 | name: "t,c{60,20}trim", | |
| 31 | total: 60, | |
| 32 | current: 20, | |
| 33 | trim: true, | |
| 34 | want: "", | |
| 35 | }, | |
| 36 | }, | |
| 37 | 1: { | |
| 38 | { | |
| 39 | style: BarStyle(), | |
| 40 | name: "t,c{60,20}", | |
| 41 | total: 60, | |
| 42 | current: 20, | |
| 43 | want: "", | |
| 44 | }, | |
| 45 | { | |
| 46 | style: BarStyle(), | |
| 47 | name: "t,c{60,20}trim", | |
| 48 | total: 60, | |
| 49 | current: 20, | |
| 50 | trim: true, | |
| 51 | want: "", | |
| 52 | }, | |
| 53 | }, | |
| 54 | 2: { | |
| 55 | { | |
| 56 | style: BarStyle(), | |
| 57 | name: "t,c{60,20}", | |
| 58 | total: 60, | |
| 59 | current: 20, | |
| 60 | want: " ", | |
| 61 | }, | |
| 62 | { | |
| 63 | style: BarStyle(), | |
| 64 | name: "t,c{60,20}trim", | |
| 65 | total: 60, | |
| 66 | current: 20, | |
| 67 | trim: true, | |
| 68 | want: "[]", | |
| 69 | }, | |
| 70 | }, | |
| 71 | 3: { | |
| 72 | { | |
| 73 | style: BarStyle(), | |
| 74 | name: "t,c{60,20}", | |
| 75 | total: 60, | |
| 76 | current: 20, | |
| 77 | want: " ", | |
| 78 | }, | |
| 79 | { | |
| 80 | style: BarStyle(), | |
| 81 | name: "t,c{60,20}trim", | |
| 82 | total: 60, | |
| 83 | current: 20, | |
| 84 | trim: true, | |
| 85 | want: "[-]", | |
| 86 | }, | |
| 87 | { | |
| 88 | style: BarStyle(), | |
| 89 | name: "t,c{60,59}", | |
| 90 | total: 60, | |
| 91 | current: 59, | |
| 92 | want: " ", | |
| 93 | }, | |
| 94 | { | |
| 95 | style: BarStyle(), | |
| 96 | name: "t,c{60,59}trim", | |
| 97 | total: 60, | |
| 98 | current: 59, | |
| 99 | trim: true, | |
| 100 | want: "[>]", | |
| 101 | }, | |
| 102 | { | |
| 103 | style: BarStyle(), | |
| 104 | name: "t,c{60,60}", | |
| 105 | total: 60, | |
| 106 | current: 60, | |
| 107 | want: " ", | |
| 108 | }, | |
| 109 | { | |
| 110 | style: BarStyle(), | |
| 111 | name: "t,c{60,60}trim", | |
| 112 | total: 60, | |
| 113 | current: 60, | |
| 114 | trim: true, | |
| 115 | want: "[=]", | |
| 116 | }, | |
| 117 | }, | |
| 118 | 4: { | |
| 119 | { | |
| 120 | style: BarStyle(), | |
| 121 | name: "t,c{60,20}", | |
| 122 | total: 60, | |
| 123 | current: 20, | |
| 124 | want: " [] ", | |
| 125 | }, | |
| 126 | { | |
| 127 | style: BarStyle(), | |
| 128 | name: "t,c{60,20}trim", | |
| 129 | total: 60, | |
| 130 | current: 20, | |
| 131 | trim: true, | |
| 132 | want: "[>-]", | |
| 133 | }, | |
| 134 | { | |
| 135 | style: BarStyle(), | |
| 136 | name: "t,c{60,59}", | |
| 137 | total: 60, | |
| 138 | current: 59, | |
| 139 | want: " [] ", | |
| 140 | }, | |
| 141 | { | |
| 142 | style: BarStyle(), | |
| 143 | name: "t,c{60,59}trim", | |
| 144 | total: 60, | |
| 145 | current: 59, | |
| 146 | trim: true, | |
| 147 | want: "[=>]", | |
| 148 | }, | |
| 149 | { | |
| 150 | style: BarStyle(), | |
| 151 | name: "t,c{60,60}", | |
| 152 | total: 60, | |
| 153 | current: 60, | |
| 154 | want: " [] ", | |
| 155 | }, | |
| 156 | { | |
| 157 | style: BarStyle(), | |
| 158 | name: "t,c{60,60}trim", | |
| 159 | total: 60, | |
| 160 | current: 60, | |
| 161 | trim: true, | |
| 162 | want: "[==]", | |
| 163 | }, | |
| 164 | }, | |
| 165 | 5: { | |
| 166 | { | |
| 167 | style: BarStyle(), | |
| 168 | name: "t,c{60,20}", | |
| 169 | total: 60, | |
| 170 | current: 20, | |
| 171 | want: " [-] ", | |
| 172 | }, | |
| 173 | { | |
| 174 | style: BarStyle(), | |
| 175 | name: "t,c{60,20}trim", | |
| 176 | total: 60, | |
| 177 | current: 20, | |
| 178 | trim: true, | |
| 179 | want: "[>--]", | |
| 180 | }, | |
| 181 | { | |
| 182 | style: BarStyle(), | |
| 183 | name: "t,c{60,59}", | |
| 184 | total: 60, | |
| 185 | current: 59, | |
| 186 | want: " [>] ", | |
| 187 | }, | |
| 188 | { | |
| 189 | style: BarStyle(), | |
| 190 | name: "t,c{60,59}trim", | |
| 191 | total: 60, | |
| 192 | current: 59, | |
| 193 | trim: true, | |
| 194 | want: "[==>]", | |
| 195 | }, | |
| 196 | { | |
| 197 | style: BarStyle(), | |
| 198 | name: "t,c{60,60}", | |
| 199 | total: 60, | |
| 200 | current: 60, | |
| 201 | want: " [=] ", | |
| 202 | }, | |
| 203 | { | |
| 204 | style: BarStyle(), | |
| 205 | name: "t,c{60,60}trim", | |
| 206 | total: 60, | |
| 207 | current: 60, | |
| 208 | trim: true, | |
| 209 | want: "[===]", | |
| 210 | }, | |
| 211 | }, | |
| 212 | 6: { | |
| 213 | { | |
| 214 | style: BarStyle(), | |
| 215 | name: "t,c{60,20}", | |
| 216 | total: 60, | |
| 217 | current: 20, | |
| 218 | want: " [>-] ", | |
| 219 | }, | |
| 220 | { | |
| 221 | style: BarStyle(), | |
| 222 | name: "t,c{60,20}trim", | |
| 223 | total: 60, | |
| 224 | current: 20, | |
| 225 | trim: true, | |
| 226 | want: "[>---]", | |
| 227 | }, | |
| 228 | { | |
| 229 | style: BarStyle(), | |
| 230 | name: "t,c{60,59}", | |
| 231 | total: 60, | |
| 232 | current: 59, | |
| 233 | want: " [=>] ", | |
| 234 | }, | |
| 235 | { | |
| 236 | style: BarStyle(), | |
| 237 | name: "t,c{60,59}trim", | |
| 238 | total: 60, | |
| 239 | current: 59, | |
| 240 | trim: true, | |
| 241 | want: "[===>]", | |
| 242 | }, | |
| 243 | { | |
| 244 | style: BarStyle(), | |
| 245 | name: "t,c{60,60}", | |
| 246 | total: 60, | |
| 247 | current: 60, | |
| 248 | want: " [==] ", | |
| 249 | }, | |
| 250 | { | |
| 251 | style: BarStyle(), | |
| 252 | name: "t,c{60,60}trim", | |
| 253 | total: 60, | |
| 254 | current: 60, | |
| 255 | trim: true, | |
| 256 | want: "[====]", | |
| 257 | }, | |
| 258 | }, | |
| 259 | 7: { | |
| 260 | { | |
| 261 | style: BarStyle(), | |
| 262 | name: "t,c{60,20}", | |
| 263 | total: 60, | |
| 264 | current: 20, | |
| 265 | want: " [>--] ", | |
| 266 | }, | |
| 267 | { | |
| 268 | style: BarStyle(), | |
| 269 | name: "t,c{60,20}trim", | |
| 270 | total: 60, | |
| 271 | current: 20, | |
| 272 | trim: true, | |
| 273 | want: "[=>---]", | |
| 274 | }, | |
| 275 | { | |
| 276 | style: BarStyle(), | |
| 277 | name: "t,c{60,59}", | |
| 278 | total: 60, | |
| 279 | current: 59, | |
| 280 | want: " [==>] ", | |
| 281 | }, | |
| 282 | { | |
| 283 | style: BarStyle(), | |
| 284 | name: "t,c{60,59}trim", | |
| 285 | total: 60, | |
| 286 | current: 59, | |
| 287 | trim: true, | |
| 288 | want: "[====>]", | |
| 289 | }, | |
| 290 | { | |
| 291 | style: BarStyle(), | |
| 292 | name: "t,c{60,60}", | |
| 293 | total: 60, | |
| 294 | current: 60, | |
| 295 | want: " [===] ", | |
| 296 | }, | |
| 297 | { | |
| 298 | style: BarStyle(), | |
| 299 | name: "t,c{60,60}trim", | |
| 300 | total: 60, | |
| 301 | current: 60, | |
| 302 | trim: true, | |
| 303 | want: "[=====]", | |
| 304 | }, | |
| 305 | }, | |
| 306 | 8: { | |
| 307 | { | |
| 308 | style: BarStyle(), | |
| 309 | name: "t,c{60,20}", | |
| 310 | total: 60, | |
| 311 | current: 20, | |
| 312 | want: " [>---] ", | |
| 313 | }, | |
| 314 | { | |
| 315 | style: BarStyle(), | |
| 316 | name: "t,c{60,20}trim", | |
| 317 | total: 60, | |
| 318 | current: 20, | |
| 319 | trim: true, | |
| 320 | want: "[=>----]", | |
| 321 | }, | |
| 322 | { | |
| 323 | style: BarStyle(), | |
| 324 | name: "t,c{60,59}", | |
| 325 | total: 60, | |
| 326 | current: 59, | |
| 327 | want: " [===>] ", | |
| 328 | }, | |
| 329 | { | |
| 330 | style: BarStyle(), | |
| 331 | name: "t,c{60,59}trim", | |
| 332 | total: 60, | |
| 333 | current: 59, | |
| 334 | trim: true, | |
| 335 | want: "[=====>]", | |
| 336 | }, | |
| 337 | { | |
| 338 | style: BarStyle(), | |
| 339 | name: "t,c{60,60}", | |
| 340 | total: 60, | |
| 341 | current: 60, | |
| 342 | want: " [====] ", | |
| 343 | }, | |
| 344 | { | |
| 345 | style: BarStyle(), | |
| 346 | name: "t,c{60,60}trim", | |
| 347 | total: 60, | |
| 348 | current: 60, | |
| 349 | trim: true, | |
| 350 | want: "[======]", | |
| 351 | }, | |
| 352 | }, | |
| 353 | 80: { | |
| 354 | { | |
| 355 | style: BarStyle(), | |
| 356 | name: "t,c{60,20}", | |
| 357 | total: 60, | |
| 358 | current: 20, | |
| 359 | want: " [========================>---------------------------------------------------] ", | |
| 360 | }, | |
| 361 | { | |
| 362 | style: BarStyle(), | |
| 363 | name: "t,c{60,20}trim", | |
| 364 | total: 60, | |
| 365 | current: 20, | |
| 366 | trim: true, | |
| 367 | want: "[=========================>----------------------------------------------------]", | |
| 368 | }, | |
| 369 | { | |
| 370 | style: BarStyle(), | |
| 371 | name: "t,c,bw{60,20,60}", | |
| 22 | 372 | total: 60, |
| 23 | 373 | current: 20, |
| 24 | barWidth: 80, | |
| 25 | want: "", | |
| 26 | }, | |
| 27 | { | |
| 28 | name: "t,c,bw{60,20,80}", | |
| 29 | total: 60, | |
| 30 | current: 20, | |
| 31 | barWidth: 80, | |
| 32 | trimSpace: true, | |
| 33 | want: "", | |
| 34 | }, | |
| 35 | }, | |
| 36 | 1: { | |
| 37 | { | |
| 38 | name: "t,c,bw{60,20,80}", | |
| 374 | barWidth: 60, | |
| 375 | want: " [==================>---------------------------------------] ", | |
| 376 | }, | |
| 377 | { | |
| 378 | style: BarStyle(), | |
| 379 | name: "t,c,bw{60,20,60}trim", | |
| 39 | 380 | total: 60, |
| 40 | 381 | current: 20, |
| 41 | barWidth: 80, | |
| 42 | want: "", | |
| 43 | }, | |
| 44 | { | |
| 45 | name: "t,c,bw{60,20,80}", | |
| 46 | total: 60, | |
| 47 | current: 20, | |
| 48 | barWidth: 80, | |
| 49 | trimSpace: true, | |
| 50 | want: "", | |
| 51 | }, | |
| 52 | }, | |
| 53 | 2: { | |
| 54 | { | |
| 55 | name: "t,c,bw{60,20,80}", | |
| 382 | barWidth: 60, | |
| 383 | trim: true, | |
| 384 | want: "[==================>---------------------------------------]", | |
| 385 | }, | |
| 386 | { | |
| 387 | style: BarStyle(), | |
| 388 | name: "t,c{60,59}", | |
| 389 | total: 60, | |
| 390 | current: 59, | |
| 391 | want: " [==========================================================================>-] ", | |
| 392 | }, | |
| 393 | { | |
| 394 | style: BarStyle(), | |
| 395 | name: "t,c{60,59}trim", | |
| 396 | total: 60, | |
| 397 | current: 59, | |
| 398 | trim: true, | |
| 399 | want: "[============================================================================>-]", | |
| 400 | }, | |
| 401 | { | |
| 402 | style: BarStyle(), | |
| 403 | name: "t,c,bw{60,59,60}", | |
| 56 | 404 | total: 60, |
| 57 | current: 20, | |
| 58 | barWidth: 80, | |
| 59 | want: " ", | |
| 60 | }, | |
| 61 | { | |
| 62 | name: "t,c,bw,trim{60,20,80,true}", | |
| 63 | total: 60, | |
| 64 | current: 20, | |
| 65 | barWidth: 80, | |
| 66 | trimSpace: true, | |
| 67 | want: "", | |
| 68 | }, | |
| 69 | }, | |
| 70 | 3: { | |
| 71 | { | |
| 72 | name: "t,c,bw{60,20,80}", | |
| 405 | current: 59, | |
| 406 | barWidth: 60, | |
| 407 | want: " [========================================================>-] ", | |
| 408 | }, | |
| 409 | { | |
| 410 | style: BarStyle(), | |
| 411 | name: "t,c,bw{60,59,60}trim", | |
| 73 | 412 | total: 60, |
| 74 | current: 20, | |
| 75 | barWidth: 80, | |
| 76 | want: " ", | |
| 77 | }, | |
| 78 | { | |
| 79 | name: "t,c,bw,trim{60,20,80,true}", | |
| 80 | total: 60, | |
| 81 | current: 20, | |
| 82 | barWidth: 80, | |
| 83 | trimSpace: true, | |
| 84 | want: "", | |
| 85 | }, | |
| 86 | }, | |
| 87 | 4: { | |
| 88 | { | |
| 89 | name: "t,c,bw{60,20,80}", | |
| 413 | current: 59, | |
| 414 | barWidth: 60, | |
| 415 | trim: true, | |
| 416 | want: "[========================================================>-]", | |
| 417 | }, | |
| 418 | { | |
| 419 | style: BarStyle(), | |
| 420 | name: "t,c{60,60}", | |
| 421 | total: 60, | |
| 422 | current: 60, | |
| 423 | want: " [============================================================================] ", | |
| 424 | }, | |
| 425 | { | |
| 426 | style: BarStyle(), | |
| 427 | name: "t,c{60,60}trim", | |
| 428 | total: 60, | |
| 429 | current: 60, | |
| 430 | trim: true, | |
| 431 | want: "[==============================================================================]", | |
| 432 | }, | |
| 433 | { | |
| 434 | style: BarStyle(), | |
| 435 | name: "t,c,bw{60,60,60}", | |
| 90 | 436 | total: 60, |
| 91 | current: 20, | |
| 92 | barWidth: 80, | |
| 93 | want: " ", | |
| 94 | }, | |
| 95 | { | |
| 96 | name: "t,c,bw,trim{60,20,80,true}", | |
| 97 | total: 60, | |
| 98 | current: 20, | |
| 99 | barWidth: 80, | |
| 100 | trimSpace: true, | |
| 101 | want: "[>-]", | |
| 102 | }, | |
| 103 | }, | |
| 104 | 5: { | |
| 105 | { | |
| 106 | name: "t,c,bw{60,20,80}", | |
| 437 | current: 60, | |
| 438 | barWidth: 60, | |
| 439 | want: " [==========================================================] ", | |
| 440 | }, | |
| 441 | { | |
| 442 | style: BarStyle(), | |
| 443 | name: "t,c,bw{60,60,60}trim", | |
| 107 | 444 | total: 60, |
| 108 | current: 20, | |
| 109 | barWidth: 80, | |
| 110 | want: " ", | |
| 111 | }, | |
| 112 | { | |
| 113 | name: "t,c,bw,trim{60,20,80,true}", | |
| 114 | total: 60, | |
| 115 | current: 20, | |
| 116 | barWidth: 80, | |
| 117 | trimSpace: true, | |
| 118 | want: "[>--]", | |
| 119 | }, | |
| 120 | }, | |
| 121 | 6: { | |
| 122 | { | |
| 123 | name: "t,c,bw{60,20,80}", | |
| 124 | total: 60, | |
| 125 | current: 20, | |
| 126 | barWidth: 80, | |
| 127 | want: " [>-] ", | |
| 128 | }, | |
| 129 | { | |
| 130 | name: "t,c,bw,trim{60,20,80,true}", | |
| 131 | total: 60, | |
| 132 | current: 20, | |
| 133 | barWidth: 80, | |
| 134 | trimSpace: true, | |
| 135 | want: "[>---]", | |
| 136 | }, | |
| 137 | }, | |
| 138 | 7: { | |
| 139 | { | |
| 140 | name: "t,c,bw{60,20,80}", | |
| 141 | total: 60, | |
| 142 | current: 20, | |
| 143 | barWidth: 80, | |
| 144 | want: " [>--] ", | |
| 145 | }, | |
| 146 | { | |
| 147 | name: "t,c,bw,trim{60,20,80,true}", | |
| 148 | total: 60, | |
| 149 | current: 20, | |
| 150 | barWidth: 80, | |
| 151 | trimSpace: true, | |
| 152 | want: "[=>---]", | |
| 153 | }, | |
| 154 | }, | |
| 155 | 8: { | |
| 156 | { | |
| 157 | name: "t,c,bw{60,20,80}", | |
| 158 | total: 60, | |
| 159 | current: 20, | |
| 160 | barWidth: 80, | |
| 161 | want: " [>---] ", | |
| 162 | }, | |
| 163 | { | |
| 164 | name: "t,c,bw,trim{60,20,80,true}", | |
| 165 | total: 60, | |
| 166 | current: 20, | |
| 167 | barWidth: 80, | |
| 168 | trimSpace: true, | |
| 169 | want: "[=>----]", | |
| 170 | }, | |
| 171 | }, | |
| 172 | 80: { | |
| 173 | { | |
| 174 | name: "t,c,bw{60,20,80}", | |
| 175 | total: 60, | |
| 176 | current: 20, | |
| 177 | barWidth: 80, | |
| 178 | want: " [========================>---------------------------------------------------] ", | |
| 179 | }, | |
| 180 | { | |
| 181 | name: "t,c,bw,trim{60,20,80,true}", | |
| 182 | total: 60, | |
| 183 | current: 20, | |
| 184 | barWidth: 80, | |
| 185 | trimSpace: true, | |
| 186 | want: "[=========================>----------------------------------------------------]", | |
| 445 | current: 60, | |
| 446 | barWidth: 60, | |
| 447 | trim: true, | |
| 448 | want: "[==========================================================]", | |
| 449 | }, | |
| 450 | }, | |
| 451 | 99: { | |
| 452 | { | |
| 453 | style: BarStyle(), | |
| 454 | name: "t,c{100,1}", | |
| 455 | total: 100, | |
| 456 | current: 1, | |
| 457 | want: " [>----------------------------------------------------------------------------------------------] ", | |
| 458 | }, | |
| 459 | { | |
| 460 | style: BarStyle(), | |
| 461 | name: "t,c{100,1}trim", | |
| 462 | total: 100, | |
| 463 | current: 1, | |
| 464 | trim: true, | |
| 465 | want: "[>------------------------------------------------------------------------------------------------]", | |
| 466 | }, | |
| 467 | { | |
| 468 | style: BarStyle(), | |
| 469 | name: "t,c,r{100,40,33}", | |
| 470 | total: 100, | |
| 471 | current: 40, | |
| 472 | refill: 33, | |
| 473 | want: " [+++++++++++++++++++++++++++++++======>---------------------------------------------------------] ", | |
| 474 | }, | |
| 475 | { | |
| 476 | style: BarStyle(), | |
| 477 | name: "t,c,r{100,40,33}trim", | |
| 478 | total: 100, | |
| 479 | current: 40, | |
| 480 | refill: 33, | |
| 481 | trim: true, | |
| 482 | want: "[++++++++++++++++++++++++++++++++======>----------------------------------------------------------]", | |
| 483 | }, | |
| 484 | { | |
| 485 | style: BarStyle().Tip("<").Reverse(), | |
| 486 | name: "t,c,r{100,40,33},rev", | |
| 487 | total: 100, | |
| 488 | current: 40, | |
| 489 | refill: 33, | |
| 490 | want: " [---------------------------------------------------------<======+++++++++++++++++++++++++++++++] ", | |
| 491 | }, | |
| 492 | { | |
| 493 | style: BarStyle().Tip("<").Reverse(), | |
| 494 | name: "t,c,r{100,40,33}trim,rev", | |
| 495 | total: 100, | |
| 496 | current: 40, | |
| 497 | refill: 33, | |
| 498 | trim: true, | |
| 499 | want: "[----------------------------------------------------------<======++++++++++++++++++++++++++++++++]", | |
| 500 | }, | |
| 501 | { | |
| 502 | style: BarStyle(), | |
| 503 | name: "t,c{100,99}", | |
| 504 | total: 100, | |
| 505 | current: 99, | |
| 506 | want: " [=============================================================================================>-] ", | |
| 507 | }, | |
| 508 | { | |
| 509 | style: BarStyle(), | |
| 510 | name: "t,c{100,99}trim", | |
| 511 | total: 100, | |
| 512 | current: 99, | |
| 513 | trim: true, | |
| 514 | want: "[===============================================================================================>-]", | |
| 515 | }, | |
| 516 | { | |
| 517 | style: BarStyle(), | |
| 518 | name: "t,c{100,100}", | |
| 519 | total: 100, | |
| 520 | current: 100, | |
| 521 | want: " [===============================================================================================] ", | |
| 522 | }, | |
| 523 | { | |
| 524 | style: BarStyle(), | |
| 525 | name: "t,c{100,100}trim", | |
| 526 | total: 100, | |
| 527 | current: 100, | |
| 528 | trim: true, | |
| 529 | want: "[=================================================================================================]", | |
| 530 | }, | |
| 531 | { | |
| 532 | style: BarStyle(), | |
| 533 | name: "t,c,r{100,100,99}", | |
| 534 | total: 100, | |
| 535 | current: 100, | |
| 536 | refill: 99, | |
| 537 | want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=] ", | |
| 538 | }, | |
| 539 | { | |
| 540 | style: BarStyle(), | |
| 541 | name: "t,c,r{100,100,99}trim", | |
| 542 | total: 100, | |
| 543 | current: 100, | |
| 544 | refill: 99, | |
| 545 | trim: true, | |
| 546 | want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=]", | |
| 547 | }, | |
| 548 | { | |
| 549 | style: BarStyle().Reverse(), | |
| 550 | name: "t,c,r{100,100,99}rev", | |
| 551 | total: 100, | |
| 552 | current: 100, | |
| 553 | refill: 99, | |
| 554 | want: " [=++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", | |
| 555 | }, | |
| 556 | { | |
| 557 | style: BarStyle().Reverse(), | |
| 558 | name: "t,c,r{100,100,99}trim,rev", | |
| 559 | total: 100, | |
| 560 | current: 100, | |
| 561 | refill: 99, | |
| 562 | trim: true, | |
| 563 | want: "[=++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", | |
| 564 | }, | |
| 565 | { | |
| 566 | style: BarStyle(), | |
| 567 | name: "t,c,r{100,100,100}", | |
| 568 | total: 100, | |
| 569 | current: 100, | |
| 570 | refill: 100, | |
| 571 | want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", | |
| 572 | }, | |
| 573 | { | |
| 574 | style: BarStyle(), | |
| 575 | name: "t,c,r{100,100,100}trim", | |
| 576 | total: 100, | |
| 577 | current: 100, | |
| 578 | refill: 100, | |
| 579 | trim: true, | |
| 580 | want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", | |
| 581 | }, | |
| 582 | { | |
| 583 | style: BarStyle().Reverse(), | |
| 584 | name: "t,c,r{100,100,100}rev", | |
| 585 | total: 100, | |
| 586 | current: 100, | |
| 587 | refill: 100, | |
| 588 | want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", | |
| 589 | }, | |
| 590 | { | |
| 591 | style: BarStyle().Reverse(), | |
| 592 | name: "t,c,r{100,100,100}trim", | |
| 593 | total: 100, | |
| 594 | current: 100, | |
| 595 | refill: 100, | |
| 596 | trim: true, | |
| 597 | want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", | |
| 187 | 598 | }, |
| 188 | 599 | }, |
| 189 | 600 | 100: { |
| 190 | 601 | { |
| 191 | name: "t,c,bw{100,100,0}", | |
| 192 | total: 100, | |
| 193 | current: 0, | |
| 194 | barWidth: 100, | |
| 195 | want: " [------------------------------------------------------------------------------------------------] ", | |
| 196 | }, | |
| 197 | { | |
| 198 | name: "t,c,bw,trim{100,100,0,true}", | |
| 199 | total: 100, | |
| 200 | current: 0, | |
| 201 | barWidth: 100, | |
| 202 | trimSpace: true, | |
| 203 | want: "[--------------------------------------------------------------------------------------------------]", | |
| 204 | }, | |
| 205 | { | |
| 206 | name: "t,c,bw{100,1,100}", | |
| 207 | total: 100, | |
| 208 | current: 1, | |
| 209 | barWidth: 100, | |
| 210 | want: " [>-----------------------------------------------------------------------------------------------] ", | |
| 211 | }, | |
| 212 | { | |
| 213 | name: "t,c,bw,trim{100,1,100,true}", | |
| 214 | total: 100, | |
| 215 | current: 1, | |
| 216 | barWidth: 100, | |
| 217 | trimSpace: true, | |
| 218 | want: "[>-------------------------------------------------------------------------------------------------]", | |
| 219 | }, | |
| 220 | { | |
| 221 | name: "t,c,bw{100,33,100}", | |
| 222 | total: 100, | |
| 223 | current: 33, | |
| 224 | barWidth: 100, | |
| 225 | want: " [===============================>----------------------------------------------------------------] ", | |
| 226 | }, | |
| 227 | { | |
| 228 | name: "t,c,bw,trim{100,33,100,true}", | |
| 229 | total: 100, | |
| 230 | current: 33, | |
| 231 | barWidth: 100, | |
| 232 | trimSpace: true, | |
| 233 | want: "[===============================>------------------------------------------------------------------]", | |
| 234 | }, | |
| 235 | { | |
| 236 | name: "t,c,bw,trim,rev{100,33,100,true,true}", | |
| 237 | total: 100, | |
| 238 | current: 33, | |
| 239 | barWidth: 100, | |
| 240 | trimSpace: true, | |
| 241 | reverse: true, | |
| 242 | want: "[------------------------------------------------------------------<===============================]", | |
| 243 | }, | |
| 244 | { | |
| 245 | name: "t,c,bw,rup{100,33,100,33}", | |
| 246 | total: 100, | |
| 247 | current: 33, | |
| 248 | barWidth: 100, | |
| 249 | rup: 33, | |
| 250 | want: " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ", | |
| 251 | }, | |
| 252 | { | |
| 253 | name: "t,c,bw,rup,trim{100,33,100,33,true}", | |
| 254 | total: 100, | |
| 255 | current: 33, | |
| 256 | barWidth: 100, | |
| 257 | rup: 33, | |
| 258 | trimSpace: true, | |
| 259 | want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]", | |
| 260 | }, | |
| 261 | { | |
| 262 | name: "t,c,bw,rup,trim,rev{100,33,100,33,true,true}", | |
| 263 | total: 100, | |
| 264 | current: 33, | |
| 265 | barWidth: 100, | |
| 266 | rup: 33, | |
| 267 | trimSpace: true, | |
| 268 | reverse: true, | |
| 269 | want: "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]", | |
| 270 | }, | |
| 271 | { | |
| 272 | name: "t,c,bw,rup{100,40,100,32}", | |
| 273 | total: 100, | |
| 274 | current: 40, | |
| 275 | barWidth: 100, | |
| 276 | rup: 33, | |
| 277 | want: " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ", | |
| 278 | }, | |
| 279 | { | |
| 280 | name: "t,c,bw,rup,trim{100,40,100,32,true}", | |
| 281 | total: 100, | |
| 282 | current: 40, | |
| 283 | barWidth: 100, | |
| 284 | rup: 33, | |
| 285 | trimSpace: true, | |
| 286 | want: "[++++++++++++++++++++++++++++++++======>-----------------------------------------------------------]", | |
| 287 | }, | |
| 288 | { | |
| 289 | name: "t,c,bw{100,99,100}", | |
| 290 | total: 100, | |
| 291 | current: 99, | |
| 292 | barWidth: 100, | |
| 293 | want: " [==============================================================================================>-] ", | |
| 294 | }, | |
| 295 | { | |
| 296 | name: "t,c,bw,trim{100,99,100,true}", | |
| 297 | total: 100, | |
| 298 | current: 99, | |
| 299 | barWidth: 100, | |
| 300 | trimSpace: true, | |
| 301 | want: "[================================================================================================>-]", | |
| 302 | }, | |
| 303 | { | |
| 304 | name: "t,c,bw{100,100,100}", | |
| 305 | total: 100, | |
| 306 | current: 100, | |
| 307 | barWidth: 100, | |
| 308 | want: " [================================================================================================] ", | |
| 309 | }, | |
| 310 | { | |
| 311 | name: "t,c,bw,trim{100,100,100,true}", | |
| 312 | total: 100, | |
| 313 | current: 100, | |
| 314 | barWidth: 100, | |
| 315 | trimSpace: true, | |
| 316 | want: "[==================================================================================================]", | |
| 602 | style: BarStyle(), | |
| 603 | name: "t,c{100,0}", | |
| 604 | total: 100, | |
| 605 | current: 0, | |
| 606 | want: " [------------------------------------------------------------------------------------------------] ", | |
| 607 | }, | |
| 608 | { | |
| 609 | style: BarStyle(), | |
| 610 | name: "t,c{100,0}trim", | |
| 611 | total: 100, | |
| 612 | current: 0, | |
| 613 | trim: true, | |
| 614 | want: "[--------------------------------------------------------------------------------------------------]", | |
| 615 | }, | |
| 616 | { | |
| 617 | style: BarStyle(), | |
| 618 | name: "t,c{100,1}", | |
| 619 | total: 100, | |
| 620 | current: 1, | |
| 621 | want: " [>-----------------------------------------------------------------------------------------------] ", | |
| 622 | }, | |
| 623 | { | |
| 624 | style: BarStyle(), | |
| 625 | name: "t,c{100,1}trim", | |
| 626 | total: 100, | |
| 627 | current: 1, | |
| 628 | trim: true, | |
| 629 | want: "[>-------------------------------------------------------------------------------------------------]", | |
| 630 | }, | |
| 631 | { | |
| 632 | style: BarStyle(), | |
| 633 | name: "t,c{100,99}", | |
| 634 | total: 100, | |
| 635 | current: 99, | |
| 636 | want: " [==============================================================================================>-] ", | |
| 637 | }, | |
| 638 | { | |
| 639 | style: BarStyle(), | |
| 640 | name: "t,c{100,99}trim", | |
| 641 | total: 100, | |
| 642 | current: 99, | |
| 643 | trim: true, | |
| 644 | want: "[================================================================================================>-]", | |
| 645 | }, | |
| 646 | { | |
| 647 | style: BarStyle(), | |
| 648 | name: "t,c{100,100}", | |
| 649 | total: 100, | |
| 650 | current: 100, | |
| 651 | want: " [================================================================================================] ", | |
| 652 | }, | |
| 653 | { | |
| 654 | style: BarStyle(), | |
| 655 | name: "t,c{100,100}trim", | |
| 656 | total: 100, | |
| 657 | current: 100, | |
| 658 | trim: true, | |
| 659 | want: "[==================================================================================================]", | |
| 660 | }, | |
| 661 | { | |
| 662 | style: BarStyle(), | |
| 663 | name: "t,c,r{100,100,99}", | |
| 664 | total: 100, | |
| 665 | current: 100, | |
| 666 | refill: 99, | |
| 667 | want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=] ", | |
| 668 | }, | |
| 669 | { | |
| 670 | style: BarStyle(), | |
| 671 | name: "t,c,r{100,100,99}trim", | |
| 672 | total: 100, | |
| 673 | current: 100, | |
| 674 | refill: 99, | |
| 675 | trim: true, | |
| 676 | want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=]", | |
| 677 | }, | |
| 678 | { | |
| 679 | style: BarStyle(), | |
| 680 | name: "t,c,r{100,100,100}", | |
| 681 | total: 100, | |
| 682 | current: 100, | |
| 683 | refill: 100, | |
| 684 | want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", | |
| 685 | }, | |
| 686 | { | |
| 687 | style: BarStyle(), | |
| 688 | name: "t,c,r{100,100,100}trim", | |
| 689 | total: 100, | |
| 690 | current: 100, | |
| 691 | refill: 100, | |
| 692 | trim: true, | |
| 693 | want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", | |
| 694 | }, | |
| 695 | { | |
| 696 | style: BarStyle().Reverse(), | |
| 697 | name: "t,c,r{100,100,99}rev", | |
| 698 | total: 100, | |
| 699 | current: 100, | |
| 700 | refill: 99, | |
| 701 | want: " [=+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", | |
| 702 | }, | |
| 703 | { | |
| 704 | style: BarStyle().Reverse(), | |
| 705 | name: "t,c,r{100,100,99}trim,rev", | |
| 706 | total: 100, | |
| 707 | current: 100, | |
| 708 | refill: 99, | |
| 709 | trim: true, | |
| 710 | want: "[=+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", | |
| 711 | }, | |
| 712 | { | |
| 713 | style: BarStyle().Reverse(), | |
| 714 | name: "t,c,r{100,100,100}rev", | |
| 715 | total: 100, | |
| 716 | current: 100, | |
| 717 | refill: 100, | |
| 718 | want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", | |
| 719 | }, | |
| 720 | { | |
| 721 | style: BarStyle().Reverse(), | |
| 722 | name: "t,c,r{100,100,100}trim", | |
| 723 | total: 100, | |
| 724 | current: 100, | |
| 725 | refill: 100, | |
| 726 | trim: true, | |
| 727 | want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", | |
| 728 | }, | |
| 729 | { | |
| 730 | style: BarStyle(), | |
| 731 | name: "t,c,r{100,40,33}", | |
| 732 | total: 100, | |
| 733 | current: 40, | |
| 734 | refill: 33, | |
| 735 | want: " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ", | |
| 736 | }, | |
| 737 | { | |
| 738 | style: BarStyle(), | |
| 739 | name: "t,c,r{100,40,33}trim", | |
| 740 | total: 100, | |
| 741 | current: 40, | |
| 742 | refill: 33, | |
| 743 | trim: true, | |
| 744 | want: "[++++++++++++++++++++++++++++++++======>-----------------------------------------------------------]", | |
| 745 | }, | |
| 746 | { | |
| 747 | style: BarStyle().Tip("<").Reverse(), | |
| 748 | name: "t,c,r{100,40,33},rev", | |
| 749 | total: 100, | |
| 750 | current: 40, | |
| 751 | refill: 33, | |
| 752 | want: " [----------------------------------------------------------<=====++++++++++++++++++++++++++++++++] ", | |
| 753 | }, | |
| 754 | { | |
| 755 | style: BarStyle().Tip("<").Reverse(), | |
| 756 | name: "t,c,r{100,40,33}trim,rev", | |
| 757 | total: 100, | |
| 758 | current: 40, | |
| 759 | refill: 33, | |
| 760 | trim: true, | |
| 761 | want: "[-----------------------------------------------------------<======++++++++++++++++++++++++++++++++]", | |
| 317 | 762 | }, |
| 318 | 763 | }, |
| 319 | 764 | } |
| 320 | 765 | |
| 321 | 766 | var tmpBuf bytes.Buffer |
| 322 | for termWidth, cases := range testSuite { | |
| 767 | for tw, cases := range testSuite { | |
| 323 | 768 | for _, tc := range cases { |
| 324 | s := newTestState(tc.reverse) | |
| 325 | s.width = tc.barWidth | |
| 769 | s := newTestState(tc.style.Build()) | |
| 770 | s.reqWidth = tc.barWidth | |
| 326 | 771 | s.total = tc.total |
| 327 | 772 | s.current = tc.current |
| 328 | s.trimSpace = tc.trimSpace | |
| 329 | if tc.rup > 0 { | |
| 330 | if f, ok := s.filler.(interface{ SetRefill(int64) }); ok { | |
| 331 | f.SetRefill(tc.rup) | |
| 332 | } | |
| 773 | s.trimSpace = tc.trim | |
| 774 | s.refill = tc.refill | |
| 775 | tmpBuf.Reset() | |
| 776 | _, err := tmpBuf.ReadFrom(s.draw(newStatistics(tw, s))) | |
| 777 | if err != nil { | |
| 778 | t.FailNow() | |
| 333 | 779 | } |
| 334 | tmpBuf.Reset() | |
| 335 | tmpBuf.ReadFrom(s.draw(termWidth, newStatistics(s))) | |
| 336 | 780 | by := tmpBuf.Bytes() |
| 337 | by = by[:len(by)-1] | |
| 338 | 781 | |
| 339 | if utf8.RuneCount(by) > termWidth { | |
| 340 | t.Errorf("termWidth:%d %q barWidth:%d overflow termWidth\n", termWidth, tc.name, utf8.RuneCount(by)) | |
| 782 | got := string(by[:len(by)-1]) | |
| 783 | if !utf8.ValidString(got) { | |
| 784 | t.Fail() | |
| 341 | 785 | } |
| 342 | ||
| 343 | got := string(by) | |
| 344 | 786 | if got != tc.want { |
| 345 | t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", termWidth, tc.name, tc.want, len(tc.want), got, len(got)) | |
| 787 | t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got)) | |
| 346 | 788 | } |
| 347 | 789 | } |
| 348 | 790 | } |
| 349 | 791 | } |
| 350 | 792 | |
| 351 | func newTestState(reverse bool) *bState { | |
| 352 | s := &bState{ | |
| 353 | filler: NewBarFiller(DefaultBarStyle, reverse), | |
| 354 | bufP: new(bytes.Buffer), | |
| 355 | bufB: new(bytes.Buffer), | |
| 356 | bufA: new(bytes.Buffer), | |
| 793 | func TestDrawTipOnComplete(t *testing.T) { | |
| 794 | // key is termWidth | |
| 795 | testSuite := map[int][]struct { | |
| 796 | style BarStyleComposer | |
| 797 | name string | |
| 798 | total int64 | |
| 799 | current int64 | |
| 800 | refill int64 | |
| 801 | barWidth int | |
| 802 | trim bool | |
| 803 | want string | |
| 804 | }{ | |
| 805 | 3: { | |
| 806 | { | |
| 807 | style: BarStyle().TipOnComplete(">"), | |
| 808 | name: "t,c{60,60}", | |
| 809 | total: 60, | |
| 810 | current: 60, | |
| 811 | want: " ", | |
| 812 | }, | |
| 813 | { | |
| 814 | style: BarStyle().TipOnComplete(">"), | |
| 815 | name: "t,c{60,60}trim", | |
| 816 | total: 60, | |
| 817 | current: 60, | |
| 818 | trim: true, | |
| 819 | want: "[>]", | |
| 820 | }, | |
| 821 | }, | |
| 822 | 4: { | |
| 823 | { | |
| 824 | style: BarStyle().TipOnComplete(">"), | |
| 825 | name: "t,c{60,59}", | |
| 826 | total: 60, | |
| 827 | current: 59, | |
| 828 | want: " [] ", | |
| 829 | }, | |
| 830 | { | |
| 831 | style: BarStyle().TipOnComplete(">"), | |
| 832 | name: "t,c{60,59}trim", | |
| 833 | total: 60, | |
| 834 | current: 59, | |
| 835 | trim: true, | |
| 836 | want: "[=>]", | |
| 837 | }, | |
| 838 | { | |
| 839 | style: BarStyle().TipOnComplete(">"), | |
| 840 | name: "t,c{60,60}", | |
| 841 | total: 60, | |
| 842 | current: 60, | |
| 843 | want: " [] ", | |
| 844 | }, | |
| 845 | { | |
| 846 | style: BarStyle().TipOnComplete(">"), | |
| 847 | name: "t,c{60,60}trim", | |
| 848 | total: 60, | |
| 849 | current: 60, | |
| 850 | trim: true, | |
| 851 | want: "[=>]", | |
| 852 | }, | |
| 853 | }, | |
| 854 | 5: { | |
| 855 | { | |
| 856 | style: BarStyle().TipOnComplete(">"), | |
| 857 | name: "t,c{60,59}", | |
| 858 | total: 60, | |
| 859 | current: 59, | |
| 860 | want: " [>] ", | |
| 861 | }, | |
| 862 | { | |
| 863 | style: BarStyle().TipOnComplete(">"), | |
| 864 | name: "t,c{60,59}trim", | |
| 865 | total: 60, | |
| 866 | current: 59, | |
| 867 | trim: true, | |
| 868 | want: "[==>]", | |
| 869 | }, | |
| 870 | { | |
| 871 | style: BarStyle().TipOnComplete(">"), | |
| 872 | name: "t,c{60,60}", | |
| 873 | total: 60, | |
| 874 | current: 60, | |
| 875 | want: " [>] ", | |
| 876 | }, | |
| 877 | { | |
| 878 | style: BarStyle().TipOnComplete(">"), | |
| 879 | name: "t,c{60,60}trim", | |
| 880 | total: 60, | |
| 881 | current: 60, | |
| 882 | trim: true, | |
| 883 | want: "[==>]", | |
| 884 | }, | |
| 885 | }, | |
| 886 | 6: { | |
| 887 | { | |
| 888 | style: BarStyle().TipOnComplete(">"), | |
| 889 | name: "t,c{60,59}", | |
| 890 | total: 60, | |
| 891 | current: 59, | |
| 892 | want: " [=>] ", | |
| 893 | }, | |
| 894 | { | |
| 895 | style: BarStyle().TipOnComplete(">"), | |
| 896 | name: "t,c{60,59}trim", | |
| 897 | total: 60, | |
| 898 | current: 59, | |
| 899 | trim: true, | |
| 900 | want: "[===>]", | |
| 901 | }, | |
| 902 | { | |
| 903 | style: BarStyle().TipOnComplete(">"), | |
| 904 | name: "t,c{60,60}", | |
| 905 | total: 60, | |
| 906 | current: 60, | |
| 907 | want: " [=>] ", | |
| 908 | }, | |
| 909 | { | |
| 910 | style: BarStyle().TipOnComplete(">"), | |
| 911 | name: "t,c{60,60}trim", | |
| 912 | total: 60, | |
| 913 | current: 60, | |
| 914 | trim: true, | |
| 915 | want: "[===>]", | |
| 916 | }, | |
| 917 | }, | |
| 918 | 7: { | |
| 919 | { | |
| 920 | style: BarStyle().TipOnComplete(">"), | |
| 921 | name: "t,c{60,59}", | |
| 922 | total: 60, | |
| 923 | current: 59, | |
| 924 | want: " [==>] ", | |
| 925 | }, | |
| 926 | { | |
| 927 | style: BarStyle().TipOnComplete(">"), | |
| 928 | name: "t,c{60,59}trim", | |
| 929 | total: 60, | |
| 930 | current: 59, | |
| 931 | trim: true, | |
| 932 | want: "[====>]", | |
| 933 | }, | |
| 934 | { | |
| 935 | style: BarStyle().TipOnComplete(">"), | |
| 936 | name: "t,c{60,60}", | |
| 937 | total: 60, | |
| 938 | current: 60, | |
| 939 | want: " [==>] ", | |
| 940 | }, | |
| 941 | { | |
| 942 | style: BarStyle().TipOnComplete(">"), | |
| 943 | name: "t,c{60,60}trim", | |
| 944 | total: 60, | |
| 945 | current: 60, | |
| 946 | trim: true, | |
| 947 | want: "[====>]", | |
| 948 | }, | |
| 949 | }, | |
| 950 | 8: { | |
| 951 | { | |
| 952 | style: BarStyle().TipOnComplete(">"), | |
| 953 | name: "t,c{60,59}", | |
| 954 | total: 60, | |
| 955 | current: 59, | |
| 956 | want: " [===>] ", | |
| 957 | }, | |
| 958 | { | |
| 959 | style: BarStyle().TipOnComplete(">"), | |
| 960 | name: "t,c{60,59}trim", | |
| 961 | total: 60, | |
| 962 | current: 59, | |
| 963 | trim: true, | |
| 964 | want: "[=====>]", | |
| 965 | }, | |
| 966 | { | |
| 967 | style: BarStyle().TipOnComplete(">"), | |
| 968 | name: "t,c{60,60}", | |
| 969 | total: 60, | |
| 970 | current: 60, | |
| 971 | want: " [===>] ", | |
| 972 | }, | |
| 973 | { | |
| 974 | style: BarStyle().TipOnComplete(">"), | |
| 975 | name: "t,c{60,60}trim", | |
| 976 | total: 60, | |
| 977 | current: 60, | |
| 978 | trim: true, | |
| 979 | want: "[=====>]", | |
| 980 | }, | |
| 981 | }, | |
| 982 | 80: { | |
| 983 | { | |
| 984 | style: BarStyle().TipOnComplete(">"), | |
| 985 | name: "t,c{60,59}", | |
| 986 | total: 60, | |
| 987 | current: 59, | |
| 988 | want: " [==========================================================================>-] ", | |
| 989 | }, | |
| 990 | { | |
| 991 | style: BarStyle().TipOnComplete(">"), | |
| 992 | name: "t,c{60,59}trim", | |
| 993 | total: 60, | |
| 994 | current: 59, | |
| 995 | trim: true, | |
| 996 | want: "[============================================================================>-]", | |
| 997 | }, | |
| 998 | { | |
| 999 | style: BarStyle().TipOnComplete(">"), | |
| 1000 | name: "t,c,bw{60,59,60}", | |
| 1001 | total: 60, | |
| 1002 | current: 59, | |
| 1003 | barWidth: 60, | |
| 1004 | want: " [========================================================>-] ", | |
| 1005 | }, | |
| 1006 | { | |
| 1007 | style: BarStyle().TipOnComplete(">"), | |
| 1008 | name: "t,c,bw{60,59,60}trim", | |
| 1009 | total: 60, | |
| 1010 | current: 59, | |
| 1011 | barWidth: 60, | |
| 1012 | trim: true, | |
| 1013 | want: "[========================================================>-]", | |
| 1014 | }, | |
| 1015 | { | |
| 1016 | style: BarStyle().TipOnComplete(">"), | |
| 1017 | name: "t,c{60,60}", | |
| 1018 | total: 60, | |
| 1019 | current: 60, | |
| 1020 | want: " [===========================================================================>] ", | |
| 1021 | }, | |
| 1022 | { | |
| 1023 | style: BarStyle().TipOnComplete(">"), | |
| 1024 | name: "t,c{60,60}trim", | |
| 1025 | total: 60, | |
| 1026 | current: 60, | |
| 1027 | trim: true, | |
| 1028 | want: "[=============================================================================>]", | |
| 1029 | }, | |
| 1030 | { | |
| 1031 | style: BarStyle().TipOnComplete(">"), | |
| 1032 | name: "t,c,bw{60,60,60}", | |
| 1033 | total: 60, | |
| 1034 | current: 60, | |
| 1035 | barWidth: 60, | |
| 1036 | want: " [=========================================================>] ", | |
| 1037 | }, | |
| 1038 | { | |
| 1039 | style: BarStyle().TipOnComplete(">"), | |
| 1040 | name: "t,c,bw{60,60,60}trim", | |
| 1041 | total: 60, | |
| 1042 | current: 60, | |
| 1043 | barWidth: 60, | |
| 1044 | trim: true, | |
| 1045 | want: "[=========================================================>]", | |
| 1046 | }, | |
| 1047 | }, | |
| 1048 | 99: { | |
| 1049 | { | |
| 1050 | style: BarStyle().TipOnComplete(">"), | |
| 1051 | name: "t,c{100,99}", | |
| 1052 | total: 100, | |
| 1053 | current: 99, | |
| 1054 | want: " [=============================================================================================>-] ", | |
| 1055 | }, | |
| 1056 | { | |
| 1057 | style: BarStyle().TipOnComplete(">"), | |
| 1058 | name: "t,c{100,99}trim", | |
| 1059 | total: 100, | |
| 1060 | current: 99, | |
| 1061 | trim: true, | |
| 1062 | want: "[===============================================================================================>-]", | |
| 1063 | }, | |
| 1064 | { | |
| 1065 | style: BarStyle().TipOnComplete(">"), | |
| 1066 | name: "t,c{100,100}", | |
| 1067 | total: 100, | |
| 1068 | current: 100, | |
| 1069 | want: " [==============================================================================================>] ", | |
| 1070 | }, | |
| 1071 | { | |
| 1072 | style: BarStyle().TipOnComplete(">"), | |
| 1073 | name: "t,c{100,100}trim", | |
| 1074 | total: 100, | |
| 1075 | current: 100, | |
| 1076 | trim: true, | |
| 1077 | want: "[================================================================================================>]", | |
| 1078 | }, | |
| 1079 | { | |
| 1080 | style: BarStyle().TipOnComplete(">"), | |
| 1081 | name: "t,c,r{100,100,99}", | |
| 1082 | total: 100, | |
| 1083 | current: 100, | |
| 1084 | refill: 99, | |
| 1085 | want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>] ", | |
| 1086 | }, | |
| 1087 | { | |
| 1088 | style: BarStyle().TipOnComplete(">"), | |
| 1089 | name: "t,c,r{100,100,99}trim", | |
| 1090 | total: 100, | |
| 1091 | current: 100, | |
| 1092 | refill: 99, | |
| 1093 | trim: true, | |
| 1094 | want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>]", | |
| 1095 | }, | |
| 1096 | { | |
| 1097 | style: BarStyle().TipOnComplete("<").Reverse(), | |
| 1098 | name: `t,c,r{100,100,99}TipOnComplete("<").Reverse()`, | |
| 1099 | total: 100, | |
| 1100 | current: 100, | |
| 1101 | refill: 99, | |
| 1102 | want: " [<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", | |
| 1103 | }, | |
| 1104 | { | |
| 1105 | style: BarStyle().TipOnComplete("<").Reverse(), | |
| 1106 | name: `t,c,r{100,100,99}TipOnComplete("<").Reverse()trim`, | |
| 1107 | total: 100, | |
| 1108 | current: 100, | |
| 1109 | refill: 99, | |
| 1110 | trim: true, | |
| 1111 | want: "[<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", | |
| 1112 | }, | |
| 1113 | { | |
| 1114 | style: BarStyle().TipOnComplete(">"), | |
| 1115 | name: "t,c,r{100,100,100}", | |
| 1116 | total: 100, | |
| 1117 | current: 100, | |
| 1118 | refill: 100, | |
| 1119 | want: " [++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>] ", | |
| 1120 | }, | |
| 1121 | { | |
| 1122 | style: BarStyle().TipOnComplete(">"), | |
| 1123 | name: "t,c,r{100,100,100}trim", | |
| 1124 | total: 100, | |
| 1125 | current: 100, | |
| 1126 | refill: 100, | |
| 1127 | trim: true, | |
| 1128 | want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>]", | |
| 1129 | }, | |
| 1130 | { | |
| 1131 | style: BarStyle().TipOnComplete("<").Reverse(), | |
| 1132 | name: `t,c,r{100,100,100}TipOnComplete("<").Reverse()`, | |
| 1133 | total: 100, | |
| 1134 | current: 100, | |
| 1135 | refill: 100, | |
| 1136 | want: " [<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++] ", | |
| 1137 | }, | |
| 1138 | { | |
| 1139 | style: BarStyle().TipOnComplete("<").Reverse(), | |
| 1140 | name: `t,c,r{100,100,100}TipOnComplete("<").Reverse()trim`, | |
| 1141 | total: 100, | |
| 1142 | current: 100, | |
| 1143 | refill: 100, | |
| 1144 | trim: true, | |
| 1145 | want: "[<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]", | |
| 1146 | }, | |
| 1147 | }, | |
| 1148 | 100: { | |
| 1149 | { | |
| 1150 | style: BarStyle().TipOnComplete(">"), | |
| 1151 | name: "t,c{100,99}", | |
| 1152 | total: 100, | |
| 1153 | current: 99, | |
| 1154 | want: " [==============================================================================================>-] ", | |
| 1155 | }, | |
| 1156 | { | |
| 1157 | style: BarStyle().TipOnComplete(">"), | |
| 1158 | name: "t,c{100,99}trim", | |
| 1159 | total: 100, | |
| 1160 | current: 99, | |
| 1161 | trim: true, | |
| 1162 | want: "[================================================================================================>-]", | |
| 1163 | }, | |
| 1164 | { | |
| 1165 | style: BarStyle().TipOnComplete(">"), | |
| 1166 | name: "t,c{100,100}", | |
| 1167 | total: 100, | |
| 1168 | current: 100, | |
| 1169 | want: " [===============================================================================================>] ", | |
| 1170 | }, | |
| 1171 | { | |
| 1172 | style: BarStyle().TipOnComplete(">"), | |
| 1173 | name: "t,c{100,100}trim", | |
| 1174 | total: 100, | |
| 1175 | current: 100, | |
| 1176 | trim: true, | |
| 1177 | want: "[=================================================================================================>]", | |
| 1178 | }, | |
| 1179 | { | |
| 1180 | style: BarStyle().TipOnComplete(">"), | |
| 1181 | name: "t,c,r{100,100,99}", | |
| 1182 | total: 100, | |
| 1183 | current: 100, | |
| 1184 | refill: 99, | |
| 1185 | want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>] ", | |
| 1186 | }, | |
| 1187 | { | |
| 1188 | style: BarStyle().TipOnComplete(">"), | |
| 1189 | name: "t,c,r{100,100,99}trim", | |
| 1190 | total: 100, | |
| 1191 | current: 100, | |
| 1192 | refill: 99, | |
| 1193 | trim: true, | |
| 1194 | want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>]", | |
| 1195 | }, | |
| 1196 | { | |
| 1197 | style: BarStyle().TipOnComplete(">"), | |
| 1198 | name: "t,c,r{100,100,100}", | |
| 1199 | total: 100, | |
| 1200 | current: 100, | |
| 1201 | refill: 100, | |
| 1202 | want: " [+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>] ", | |
| 1203 | }, | |
| 1204 | { | |
| 1205 | style: BarStyle().TipOnComplete(">"), | |
| 1206 | name: "t,c,r{100,100,100}trim", | |
| 1207 | total: 100, | |
| 1208 | current: 100, | |
| 1209 | refill: 100, | |
| 1210 | trim: true, | |
| 1211 | want: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>]", | |
| 1212 | }, | |
| 1213 | }, | |
| 357 | 1214 | } |
| 358 | return s | |
| 1215 | ||
| 1216 | var tmpBuf bytes.Buffer | |
| 1217 | for tw, cases := range testSuite { | |
| 1218 | for _, tc := range cases { | |
| 1219 | s := newTestState(tc.style.Build()) | |
| 1220 | s.reqWidth = tc.barWidth | |
| 1221 | s.total = tc.total | |
| 1222 | s.current = tc.current | |
| 1223 | s.trimSpace = tc.trim | |
| 1224 | s.refill = tc.refill | |
| 1225 | tmpBuf.Reset() | |
| 1226 | _, err := tmpBuf.ReadFrom(s.draw(newStatistics(tw, s))) | |
| 1227 | if err != nil { | |
| 1228 | t.FailNow() | |
| 1229 | } | |
| 1230 | by := tmpBuf.Bytes() | |
| 1231 | ||
| 1232 | got := string(by[:len(by)-1]) | |
| 1233 | if !utf8.ValidString(got) { | |
| 1234 | t.Fail() | |
| 1235 | } | |
| 1236 | if got != tc.want { | |
| 1237 | t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got)) | |
| 1238 | } | |
| 1239 | } | |
| 1240 | } | |
| 359 | 1241 | } |
| 1242 | ||
| 1243 | func TestDrawDoubleWidth(t *testing.T) { | |
| 1244 | // key is termWidth | |
| 1245 | testSuite := map[int][]struct { | |
| 1246 | style BarStyleComposer | |
| 1247 | name string | |
| 1248 | total int64 | |
| 1249 | current int64 | |
| 1250 | refill int64 | |
| 1251 | barWidth int | |
| 1252 | trim bool | |
| 1253 | want string | |
| 1254 | }{ | |
| 1255 | 99: { | |
| 1256 | { | |
| 1257 | style: BarStyle().Lbound("の").Rbound("の"), | |
| 1258 | name: `t,c{100,1}.Lbound("の").Rbound("の")`, | |
| 1259 | total: 100, | |
| 1260 | current: 1, | |
| 1261 | want: " の>--------------------------------------------------------------------------------------------の ", | |
| 1262 | }, | |
| 1263 | { | |
| 1264 | style: BarStyle().Lbound("の").Rbound("の"), | |
| 1265 | name: `t,c{100,1}.Lbound("の").Rbound("の")`, | |
| 1266 | total: 100, | |
| 1267 | current: 2, | |
| 1268 | want: " の=>-------------------------------------------------------------------------------------------の ", | |
| 1269 | }, | |
| 1270 | { | |
| 1271 | style: BarStyle().Tip("だ"), | |
| 1272 | name: `t,c{100,1}Tip("だ")`, | |
| 1273 | total: 100, | |
| 1274 | current: 1, | |
| 1275 | want: " [だ---------------------------------------------------------------------------------------------] ", | |
| 1276 | }, | |
| 1277 | { | |
| 1278 | style: BarStyle().Tip("だ"), | |
| 1279 | name: `t,c{100,2}Tip("だ")`, | |
| 1280 | total: 100, | |
| 1281 | current: 2, | |
| 1282 | want: " [だ---------------------------------------------------------------------------------------------] ", | |
| 1283 | }, | |
| 1284 | { | |
| 1285 | style: BarStyle().Tip("だ"), | |
| 1286 | name: `t,c{100,3}Tip("だ")`, | |
| 1287 | total: 100, | |
| 1288 | current: 3, | |
| 1289 | want: " [=だ--------------------------------------------------------------------------------------------] ", | |
| 1290 | }, | |
| 1291 | { | |
| 1292 | style: BarStyle().Tip("だ"), | |
| 1293 | name: `t,c{100,99}Tip("だ")`, | |
| 1294 | total: 100, | |
| 1295 | current: 99, | |
| 1296 | want: " [============================================================================================だ-] ", | |
| 1297 | }, | |
| 1298 | { | |
| 1299 | style: BarStyle().Tip("だ"), | |
| 1300 | name: `t,c{100,100}Tip("だ")`, | |
| 1301 | total: 100, | |
| 1302 | current: 100, | |
| 1303 | want: " [===============================================================================================] ", | |
| 1304 | }, | |
| 1305 | { | |
| 1306 | style: BarStyle().TipOnComplete("だ"), | |
| 1307 | name: `t,c{100,100}TipOnComplete("だ")`, | |
| 1308 | total: 100, | |
| 1309 | current: 100, | |
| 1310 | want: " [=============================================================================================だ] ", | |
| 1311 | }, | |
| 1312 | { | |
| 1313 | style: BarStyle().Filler("の").Tip("だ").Padding("つ"), | |
| 1314 | name: `t,c{100,1}Filler("の").Tip("だ").Padding("つ")`, | |
| 1315 | total: 100, | |
| 1316 | current: 1, | |
| 1317 | want: " [だつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", | |
| 1318 | }, | |
| 1319 | { | |
| 1320 | style: BarStyle().Filler("の").Tip("だ").Padding("つ"), | |
| 1321 | name: `t,c{100,2}Filler("の").Tip("だ").Padding("つ")`, | |
| 1322 | total: 100, | |
| 1323 | current: 2, | |
| 1324 | want: " [だつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつつ…] ", | |
| 1325 | }, | |
| 1326 | { | |
| 1327 | style: BarStyle().Filler("の").Tip("だ").Padding("つ"), | |
| 1328 | name: `t,c{100,99}Filler("の").Tip("だ").Padding("つ")`, | |
| 1329 | total: 100, | |
| 1330 | current: 99, | |
| 1331 | want: " [ののののののののののののののののののののののののののののののののののののののののののののののだ…] ", | |
| 1332 | }, | |
| 1333 | { | |
| 1334 | style: BarStyle().Filler("の").Tip("だ").Padding("つ"), | |
| 1335 | name: `t,c{100,100}.Filler("の").Tip("だ").Padding("つ")`, | |
| 1336 | total: 100, | |
| 1337 | current: 100, | |
| 1338 | want: " […ののののののののののののののののののののののののののののののののののののののののののののののの] ", | |
| 1339 | }, | |
| 1340 | { | |
| 1341 | style: BarStyle().Filler("の").Tip("だ").Padding("つ").Reverse(), | |
| 1342 | name: `t,c{100,100}Filler("の").Tip("だ").Padding("つ").Reverse()`, | |
| 1343 | total: 100, | |
| 1344 | current: 100, | |
| 1345 | want: " [ののののののののののののののののののののののののののののののののののののののののののののののの…] ", | |
| 1346 | }, | |
| 1347 | { | |
| 1348 | style: BarStyle().Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ"), | |
| 1349 | name: `t,c{100,99}Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ")`, | |
| 1350 | total: 100, | |
| 1351 | current: 99, | |
| 1352 | want: " [ののののののののののののののののののののののののののののののののののののののののののののののだ…] ", | |
| 1353 | }, | |
| 1354 | { | |
| 1355 | style: BarStyle().Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ"), | |
| 1356 | name: `t,c{100,100}.Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ")`, | |
| 1357 | total: 100, | |
| 1358 | current: 100, | |
| 1359 | want: " […ののののののののののののののののののののののののののののののののののののののののののののののだ] ", | |
| 1360 | }, | |
| 1361 | { | |
| 1362 | style: BarStyle().Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ").Reverse(), | |
| 1363 | name: `t,c{100,100}.Filler("の").Tip("だ").TipOnComplete("だ").Padding("つ").Reverse()`, | |
| 1364 | total: 100, | |
| 1365 | current: 100, | |
| 1366 | want: " [だのののののののののののののののののののののののののののののののののののののののののののののの…] ", | |
| 1367 | }, | |
| 1368 | { | |
| 1369 | style: BarStyle().Refiller("の"), | |
| 1370 | name: `t,c,r{100,100,99}Refiller("の")`, | |
| 1371 | total: 100, | |
| 1372 | current: 100, | |
| 1373 | refill: 99, | |
| 1374 | want: " [ののののののののののののののののののののののののののののののののののののののののののののののの=] ", | |
| 1375 | }, | |
| 1376 | { | |
| 1377 | style: BarStyle().Refiller("の"), | |
| 1378 | name: `t,c,r{100,100,99}Refiller("の")trim`, | |
| 1379 | total: 100, | |
| 1380 | current: 100, | |
| 1381 | refill: 99, | |
| 1382 | trim: true, | |
| 1383 | want: "[のののののののののののののののののののののののののののののののののののののののののののののののの=]", | |
| 1384 | }, | |
| 1385 | }, | |
| 1386 | } | |
| 1387 | ||
| 1388 | var tmpBuf bytes.Buffer | |
| 1389 | for tw, cases := range testSuite { | |
| 1390 | for _, tc := range cases { | |
| 1391 | s := newTestState(tc.style.Build()) | |
| 1392 | s.reqWidth = tc.barWidth | |
| 1393 | s.total = tc.total | |
| 1394 | s.current = tc.current | |
| 1395 | s.trimSpace = tc.trim | |
| 1396 | s.refill = tc.refill | |
| 1397 | tmpBuf.Reset() | |
| 1398 | _, err := tmpBuf.ReadFrom(s.draw(newStatistics(tw, s))) | |
| 1399 | if err != nil { | |
| 1400 | t.FailNow() | |
| 1401 | } | |
| 1402 | by := tmpBuf.Bytes() | |
| 1403 | ||
| 1404 | got := string(by[:len(by)-1]) | |
| 1405 | if !utf8.ValidString(got) { | |
| 1406 | t.Fail() | |
| 1407 | } | |
| 1408 | if got != tc.want { | |
| 1409 | t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got)) | |
| 1410 | } | |
| 1411 | } | |
| 1412 | } | |
| 1413 | } | |
| 1414 | ||
| 1415 | func newTestState(filler BarFiller) *bState { | |
| 1416 | bs := &bState{ | |
| 1417 | filler: filler, | |
| 1418 | } | |
| 1419 | for i := 0; i < len(bs.buffers); i++ { | |
| 1420 | bs.buffers[i] = bytes.NewBuffer(make([]byte, 0, 512)) | |
| 1421 | } | |
| 1422 | return bs | |
| 1423 | } |
| 6 | 6 | "math/rand" |
| 7 | 7 | "time" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v7" | |
| 10 | "github.com/vbauerster/mpb/v7/decor" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | func Example() { |
| 16 | 16 | |
| 17 | 17 | total := 100 |
| 18 | 18 | name := "Single Bar:" |
| 19 | // adding a single bar, which will inherit container's width | |
| 20 | bar := p.AddBar(int64(total), | |
| 21 | // override DefaultBarStyle, which is "[=>-]<+" | |
| 22 | mpb.BarStyle("╢▌▌░╟"), | |
| 19 | // create a single bar, which will inherit container's width | |
| 20 | bar := p.New(int64(total), | |
| 21 | // BarFillerBuilder with custom style | |
| 22 | mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), | |
| 23 | 23 | mpb.PrependDecorators( |
| 24 | 24 | // display our name with one space on the right |
| 25 | 25 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 77 | 77 | defer proxyReader.Close() |
| 78 | 78 | |
| 79 | 79 | // and copy from reader, ignoring errors |
| 80 | io.Copy(ioutil.Discard, proxyReader) | |
| 80 | _, _ = io.Copy(ioutil.Discard, proxyReader) | |
| 81 | 81 | |
| 82 | 82 | p.Wait() |
| 83 | 83 | } |
| 1 | 1 | |
| 2 | 2 | // make syncWidth func public in test |
| 3 | 3 | var SyncWidth = syncWidth |
| 4 | var MaxWidthDistributor = &maxWidthDistributor |
| 0 | module github.com/vbauerster/mpb/v5 | |
| 0 | module github.com/vbauerster/mpb/v7 | |
| 1 | 1 | |
| 2 | 2 | require ( |
| 3 | github.com/VividCortex/ewma v1.1.1 | |
| 3 | github.com/VividCortex/ewma v1.2.0 | |
| 4 | 4 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d |
| 5 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 | |
| 6 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect | |
| 5 | github.com/mattn/go-runewidth v0.0.13 | |
| 6 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 | |
| 7 | 7 | ) |
| 8 | 8 | |
| 9 | 9 | go 1.14 |
| 0 | github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= | |
| 1 | github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= | |
| 0 | github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= | |
| 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 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |
| 5 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= | |
| 6 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |
| 7 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
| 8 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
| 9 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
| 10 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= | |
| 11 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
| 12 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |
| 4 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= | |
| 5 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | |
| 6 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | |
| 7 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | |
| 8 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= | |
| 9 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| 2 | 2 | import "math" |
| 3 | 3 | |
| 4 | 4 | // Percentage is a helper function, to calculate percentage. |
| 5 | func Percentage(total, current int64, width int) float64 { | |
| 5 | func Percentage(total, current int64, width uint) float64 { | |
| 6 | 6 | if total <= 0 { |
| 7 | 7 | return 0 |
| 8 | } | |
| 9 | if current >= total { | |
| 10 | return float64(width) | |
| 8 | 11 | } |
| 9 | 12 | return float64(int64(width)*current) / float64(total) |
| 10 | 13 | } |
| 11 | 14 | |
| 12 | func PercentageRound(total, current int64, width int) float64 { | |
| 15 | // PercentageRound same as Percentage but with math.Round. | |
| 16 | func PercentageRound(total, current int64, width uint) float64 { | |
| 13 | 17 | return math.Round(Percentage(total, current, width)) |
| 14 | 18 | } |
| 3 | 3 | |
| 4 | 4 | func TestPercentage(t *testing.T) { |
| 5 | 5 | // key is barWidth |
| 6 | testSuite := map[int][]struct { | |
| 6 | testSuite := map[uint][]struct { | |
| 7 | 7 | name string |
| 8 | 8 | total int64 |
| 9 | 9 | current int64 |
| 20 | 20 | {"t,c,e{100,50,50}", 100, 50, 50}, |
| 21 | 21 | {"t,c,e{100,99,99}", 100, 99, 99}, |
| 22 | 22 | {"t,c,e{100,100,100}", 100, 100, 100}, |
| 23 | {"t,c,e{100,101,101}", 100, 101, 101}, | |
| 24 | {"t,c,e{100,102,101}", 100, 102, 102}, | |
| 23 | {"t,c,e{100,101,101}", 100, 101, 100}, | |
| 25 | 24 | {"t,c,e{120,0,0}", 120, 0, 0}, |
| 26 | 25 | {"t,c,e{120,10,8}", 120, 10, 8}, |
| 27 | 26 | {"t,c,e{120,15,13}", 120, 15, 13}, |
| 32 | 31 | {"t,c,e{120,118,98}", 120, 118, 98}, |
| 33 | 32 | {"t,c,e{120,119,99}", 120, 119, 99}, |
| 34 | 33 | {"t,c,e{120,120,100}", 120, 120, 100}, |
| 35 | {"t,c,e{120,121,101}", 120, 121, 101}, | |
| 36 | {"t,c,e{120,122,101}", 120, 122, 102}, | |
| 34 | {"t,c,e{120,121,101}", 120, 121, 100}, | |
| 37 | 35 | }, |
| 38 | 36 | 80: { |
| 39 | 37 | {"t,c,e{-1,-1,0}", -1, -1, 0}, |
| 46 | 44 | {"t,c,e{100,50,40}", 100, 50, 40}, |
| 47 | 45 | {"t,c,e{100,99,79}", 100, 99, 79}, |
| 48 | 46 | {"t,c,e{100,100,80}", 100, 100, 80}, |
| 49 | {"t,c,e{100,101,81}", 100, 101, 81}, | |
| 50 | {"t,c,e{100,102,82}", 100, 102, 82}, | |
| 47 | {"t,c,e{100,101,81}", 100, 101, 80}, | |
| 51 | 48 | {"t,c,e{120,0,0}", 120, 0, 0}, |
| 52 | 49 | {"t,c,e{120,10,7}", 120, 10, 7}, |
| 53 | 50 | {"t,c,e{120,15,10}", 120, 15, 10}, |
| 58 | 55 | {"t,c,e{120,118,79}", 120, 118, 79}, |
| 59 | 56 | {"t,c,e{120,119,79}", 120, 119, 79}, |
| 60 | 57 | {"t,c,e{120,120,80}", 120, 120, 80}, |
| 61 | {"t,c,e{120,121,81}", 120, 121, 81}, | |
| 62 | {"t,c,e{120,122,81}", 120, 122, 81}, | |
| 58 | {"t,c,e{120,121,81}", 120, 121, 80}, | |
| 63 | 59 | }, |
| 64 | 60 | } |
| 65 | 61 | |
| 0 | package internal | |
| 1 | ||
| 2 | // CheckRequestedWidth checks that requested width doesn't overflow | |
| 3 | // available width | |
| 4 | func CheckRequestedWidth(requested, available int) int { | |
| 5 | if requested < 1 || requested >= available { | |
| 6 | return available | |
| 7 | } | |
| 8 | return requested | |
| 9 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "io/ioutil" | |
| 5 | "sync" | |
| 6 | "time" | |
| 7 | ) | |
| 8 | ||
| 9 | // ContainerOption is a function option which changes the default | |
| 10 | // behavior of progress container, if passed to mpb.New(...ContainerOption). | |
| 11 | type ContainerOption func(*pState) | |
| 12 | ||
| 13 | // WithWaitGroup provides means to have a single joint point. If | |
| 14 | // *sync.WaitGroup is provided, you can safely call just p.Wait() | |
| 15 | // without calling Wait() on provided *sync.WaitGroup. Makes sense | |
| 16 | // when there are more than one bar to render. | |
| 17 | func WithWaitGroup(wg *sync.WaitGroup) ContainerOption { | |
| 18 | return func(s *pState) { | |
| 19 | s.uwg = wg | |
| 20 | } | |
| 21 | } | |
| 22 | ||
| 23 | // WithWidth sets container width. Default is 80. Bars inherit this | |
| 24 | // width, as long as no BarWidth is applied. | |
| 25 | func WithWidth(w int) ContainerOption { | |
| 26 | return func(s *pState) { | |
| 27 | if w < 0 { | |
| 28 | return | |
| 29 | } | |
| 30 | s.width = w | |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | // WithRefreshRate overrides default 120ms refresh rate. | |
| 35 | func WithRefreshRate(d time.Duration) ContainerOption { | |
| 36 | return func(s *pState) { | |
| 37 | s.rr = d | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | // WithManualRefresh disables internal auto refresh time.Ticker. | |
| 42 | // Refresh will occur upon receive value from provided ch. | |
| 43 | func WithManualRefresh(ch <-chan time.Time) ContainerOption { | |
| 44 | return func(s *pState) { | |
| 45 | s.refreshSrc = ch | |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | // WithRenderDelay delays rendering. By default rendering starts as | |
| 50 | // soon as bar is added, with this option it's possible to delay | |
| 51 | // rendering process by keeping provided chan unclosed. In other words | |
| 52 | // rendering will start as soon as provided chan is closed. | |
| 53 | func WithRenderDelay(ch <-chan struct{}) ContainerOption { | |
| 54 | return func(s *pState) { | |
| 55 | s.renderDelay = ch | |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | // WithShutdownNotifier provided chanel will be closed, after all bars | |
| 60 | // have been rendered. | |
| 61 | func WithShutdownNotifier(ch chan struct{}) ContainerOption { | |
| 62 | return func(s *pState) { | |
| 63 | s.shutdownNotifier = ch | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | // WithOutput overrides default os.Stdout output. Setting it to nil | |
| 68 | // will effectively disable auto refresh rate and discard any output, | |
| 69 | // useful if you want to disable progress bars with little overhead. | |
| 70 | func WithOutput(w io.Writer) ContainerOption { | |
| 71 | return func(s *pState) { | |
| 72 | if w == nil { | |
| 73 | s.refreshSrc = make(chan time.Time) | |
| 74 | s.output = ioutil.Discard | |
| 75 | return | |
| 76 | } | |
| 77 | s.output = w | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | // WithDebugOutput sets debug output. | |
| 82 | func WithDebugOutput(w io.Writer) ContainerOption { | |
| 83 | if w == nil { | |
| 84 | return nil | |
| 85 | } | |
| 86 | return func(s *pState) { | |
| 87 | s.debugOut = w | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | // PopCompletedMode will pop and stop rendering completed bars. | |
| 92 | func PopCompletedMode() ContainerOption { | |
| 93 | return func(s *pState) { | |
| 94 | s.popCompleted = true | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | // ContainerOptOn returns option when condition evaluates to true. | |
| 99 | func ContainerOptOn(option ContainerOption, condition func() bool) ContainerOption { | |
| 100 | if condition() { | |
| 101 | return option | |
| 102 | } | |
| 103 | return nil | |
| 104 | } |
| 5 | 5 | "context" |
| 6 | 6 | "fmt" |
| 7 | 7 | "io" |
| 8 | "io/ioutil" | |
| 9 | "log" | |
| 8 | "math" | |
| 10 | 9 | "os" |
| 11 | 10 | "sync" |
| 12 | 11 | "time" |
| 13 | 12 | |
| 14 | "github.com/vbauerster/mpb/v5/cwriter" | |
| 15 | "github.com/vbauerster/mpb/v5/decor" | |
| 13 | "github.com/vbauerster/mpb/v7/cwriter" | |
| 14 | "github.com/vbauerster/mpb/v7/decor" | |
| 16 | 15 | ) |
| 17 | 16 | |
| 18 | 17 | const ( |
| 19 | 18 | // default RefreshRate |
| 20 | prr = 120 * time.Millisecond | |
| 21 | // default width | |
| 22 | pwidth = 80 | |
| 19 | prr = 150 * time.Millisecond | |
| 23 | 20 | ) |
| 24 | 21 | |
| 25 | // Progress represents the container that renders Progress bars | |
| 22 | // Progress represents a container that renders one or more progress | |
| 23 | // bars. | |
| 26 | 24 | type Progress struct { |
| 27 | 25 | ctx context.Context |
| 28 | 26 | uwg *sync.WaitGroup |
| 32 | 30 | done chan struct{} |
| 33 | 31 | refreshCh chan time.Time |
| 34 | 32 | once sync.Once |
| 35 | dlogger *log.Logger | |
| 36 | } | |
| 37 | ||
| 33 | } | |
| 34 | ||
| 35 | // pState holds bars in its priorityQueue. It gets passed to | |
| 36 | // *Progress.serve(...) monitor goroutine. | |
| 38 | 37 | type pState struct { |
| 39 | 38 | bHeap priorityQueue |
| 40 | 39 | heapUpdated bool |
| 41 | 40 | pMatrix map[int][]chan int |
| 42 | 41 | aMatrix map[int][]chan int |
| 43 | 42 | barShutdownQueue []*Bar |
| 44 | barPopQueue []*Bar | |
| 45 | 43 | |
| 46 | 44 | // following are provided/overrided by user |
| 47 | 45 | idCount int |
| 48 | width int | |
| 46 | reqWidth int | |
| 49 | 47 | popCompleted bool |
| 48 | outputDiscarded bool | |
| 50 | 49 | rr time.Duration |
| 51 | 50 | uwg *sync.WaitGroup |
| 52 | refreshSrc <-chan time.Time | |
| 51 | externalRefresh <-chan interface{} | |
| 53 | 52 | renderDelay <-chan struct{} |
| 54 | 53 | shutdownNotifier chan struct{} |
| 55 | 54 | parkedBars map[*Bar]*Bar |
| 69 | 68 | func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { |
| 70 | 69 | s := &pState{ |
| 71 | 70 | bHeap: priorityQueue{}, |
| 72 | width: pwidth, | |
| 73 | 71 | rr: prr, |
| 74 | 72 | parkedBars: make(map[*Bar]*Bar), |
| 75 | 73 | output: os.Stdout, |
| 76 | debugOut: ioutil.Discard, | |
| 77 | 74 | } |
| 78 | 75 | |
| 79 | 76 | for _, opt := range options { |
| 89 | 86 | bwg: new(sync.WaitGroup), |
| 90 | 87 | operateState: make(chan func(*pState)), |
| 91 | 88 | done: make(chan struct{}), |
| 92 | dlogger: log.New(s.debugOut, "[mpb] ", log.Lshortfile), | |
| 93 | 89 | } |
| 94 | 90 | |
| 95 | 91 | p.cwg.Add(1) |
| 97 | 93 | return p |
| 98 | 94 | } |
| 99 | 95 | |
| 100 | // AddBar creates a new progress bar and adds it to the rendering queue. | |
| 96 | // AddBar creates a bar with default bar filler. | |
| 101 | 97 | func (p *Progress) AddBar(total int64, options ...BarOption) *Bar { |
| 102 | return p.Add(total, NewBarFiller(DefaultBarStyle, false), options...) | |
| 103 | } | |
| 104 | ||
| 105 | // AddSpinner creates a new spinner bar and adds it to the rendering queue. | |
| 106 | func (p *Progress) AddSpinner(total int64, alignment SpinnerAlignment, options ...BarOption) *Bar { | |
| 107 | return p.Add(total, NewSpinnerFiller(DefaultSpinnerStyle, alignment), options...) | |
| 98 | return p.New(total, BarStyle(), options...) | |
| 99 | } | |
| 100 | ||
| 101 | // AddSpinner creates a bar with default spinner filler. | |
| 102 | func (p *Progress) AddSpinner(total int64, options ...BarOption) *Bar { | |
| 103 | return p.New(total, SpinnerStyle(), options...) | |
| 104 | } | |
| 105 | ||
| 106 | // New creates a bar with provided BarFillerBuilder. | |
| 107 | func (p *Progress) New(total int64, builder BarFillerBuilder, options ...BarOption) *Bar { | |
| 108 | return p.Add(total, builder.Build(), options...) | |
| 108 | 109 | } |
| 109 | 110 | |
| 110 | 111 | // Add creates a bar which renders itself by provided filler. |
| 111 | // Set total to 0, if you plan to update it later. | |
| 112 | // If `total <= 0` trigger complete event is disabled until reset with *bar.SetTotal(int64, bool). | |
| 112 | 113 | // Panics if *Progress instance is done, i.e. called after *Progress.Wait(). |
| 113 | 114 | func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) *Bar { |
| 114 | 115 | if filler == nil { |
| 115 | filler = NewBarFiller(DefaultBarStyle, false) | |
| 116 | filler = NopStyle().Build() | |
| 116 | 117 | } |
| 117 | 118 | p.bwg.Add(1) |
| 118 | 119 | result := make(chan *Bar) |
| 152 | 153 | } |
| 153 | 154 | } |
| 154 | 155 | |
| 155 | func (p *Progress) setBarPriority(b *Bar, priority int) { | |
| 156 | func (p *Progress) traverseBars(cb func(b *Bar) bool) { | |
| 157 | done := make(chan struct{}) | |
| 158 | select { | |
| 159 | case p.operateState <- func(s *pState) { | |
| 160 | for i := 0; i < s.bHeap.Len(); i++ { | |
| 161 | bar := s.bHeap[i] | |
| 162 | if !cb(bar) { | |
| 163 | break | |
| 164 | } | |
| 165 | } | |
| 166 | close(done) | |
| 167 | }: | |
| 168 | <-done | |
| 169 | case <-p.done: | |
| 170 | } | |
| 171 | } | |
| 172 | ||
| 173 | // UpdateBarPriority same as *Bar.SetPriority(int). | |
| 174 | func (p *Progress) UpdateBarPriority(b *Bar, priority int) { | |
| 156 | 175 | select { |
| 157 | 176 | case p.operateState <- func(s *pState) { |
| 158 | 177 | if b.index < 0 { |
| 165 | 184 | } |
| 166 | 185 | } |
| 167 | 186 | |
| 168 | // UpdateBarPriority same as *Bar.SetPriority(int). | |
| 169 | func (p *Progress) UpdateBarPriority(b *Bar, priority int) { | |
| 170 | p.setBarPriority(b, priority) | |
| 171 | } | |
| 172 | ||
| 173 | // BarCount returns bars count | |
| 187 | // BarCount returns bars count. | |
| 174 | 188 | func (p *Progress) BarCount() int { |
| 175 | result := make(chan int, 1) | |
| 189 | result := make(chan int) | |
| 176 | 190 | select { |
| 177 | 191 | case p.operateState <- func(s *pState) { result <- s.bHeap.Len() }: |
| 178 | 192 | return <-result |
| 181 | 195 | } |
| 182 | 196 | } |
| 183 | 197 | |
| 184 | // Wait waits far all bars to complete and finally shutdowns container. | |
| 198 | // Wait waits for all bars to complete and finally shutdowns container. | |
| 185 | 199 | // After this method has been called, there is no way to reuse *Progress |
| 186 | 200 | // instance. |
| 187 | 201 | func (p *Progress) Wait() { |
| 214 | 228 | op(s) |
| 215 | 229 | case <-p.refreshCh: |
| 216 | 230 | if err := s.render(cw); err != nil { |
| 217 | go p.dlogger.Println(err) | |
| 231 | if s.debugOut != nil { | |
| 232 | _, e := fmt.Fprintln(s.debugOut, err) | |
| 233 | if e != nil { | |
| 234 | panic(err) | |
| 235 | } | |
| 236 | } else { | |
| 237 | panic(err) | |
| 238 | } | |
| 218 | 239 | } |
| 219 | 240 | case <-s.shutdownNotifier: |
| 241 | for s.heapUpdated { | |
| 242 | if err := s.render(cw); err != nil { | |
| 243 | if s.debugOut != nil { | |
| 244 | _, e := fmt.Fprintln(s.debugOut, err) | |
| 245 | if e != nil { | |
| 246 | panic(err) | |
| 247 | } | |
| 248 | } else { | |
| 249 | panic(err) | |
| 250 | } | |
| 251 | } | |
| 252 | } | |
| 220 | 253 | return |
| 221 | 254 | } |
| 222 | 255 | } |
| 256 | } | |
| 257 | ||
| 258 | func (s *pState) newTicker(done <-chan struct{}) chan time.Time { | |
| 259 | ch := make(chan time.Time) | |
| 260 | if s.shutdownNotifier == nil { | |
| 261 | s.shutdownNotifier = make(chan struct{}) | |
| 262 | } | |
| 263 | go func() { | |
| 264 | if s.renderDelay != nil { | |
| 265 | <-s.renderDelay | |
| 266 | } | |
| 267 | var internalRefresh <-chan time.Time | |
| 268 | if !s.outputDiscarded { | |
| 269 | if s.externalRefresh == nil { | |
| 270 | ticker := time.NewTicker(s.rr) | |
| 271 | defer ticker.Stop() | |
| 272 | internalRefresh = ticker.C | |
| 273 | } | |
| 274 | } else { | |
| 275 | s.externalRefresh = nil | |
| 276 | } | |
| 277 | for { | |
| 278 | select { | |
| 279 | case t := <-internalRefresh: | |
| 280 | ch <- t | |
| 281 | case x := <-s.externalRefresh: | |
| 282 | if t, ok := x.(time.Time); ok { | |
| 283 | ch <- t | |
| 284 | } else { | |
| 285 | ch <- time.Now() | |
| 286 | } | |
| 287 | case <-done: | |
| 288 | close(s.shutdownNotifier) | |
| 289 | return | |
| 290 | } | |
| 291 | } | |
| 292 | }() | |
| 293 | return ch | |
| 223 | 294 | } |
| 224 | 295 | |
| 225 | 296 | func (s *pState) render(cw *cwriter.Writer) error { |
| 232 | 303 | |
| 233 | 304 | tw, err := cw.GetWidth() |
| 234 | 305 | if err != nil { |
| 235 | tw = s.width | |
| 306 | tw = s.reqWidth | |
| 236 | 307 | } |
| 237 | 308 | for i := 0; i < s.bHeap.Len(); i++ { |
| 238 | 309 | bar := s.bHeap[i] |
| 243 | 314 | } |
| 244 | 315 | |
| 245 | 316 | func (s *pState) flush(cw *cwriter.Writer) error { |
| 246 | var lineCount int | |
| 247 | bm := make(map[*Bar]struct{}, s.bHeap.Len()) | |
| 317 | var totalLines int | |
| 318 | bm := make(map[*Bar]int, s.bHeap.Len()) | |
| 248 | 319 | for s.bHeap.Len() > 0 { |
| 249 | 320 | b := heap.Pop(&s.bHeap).(*Bar) |
| 250 | cw.ReadFrom(<-b.frameCh) | |
| 321 | frame := <-b.frameCh | |
| 322 | _, err := cw.ReadFrom(frame.reader) | |
| 323 | if err != nil { | |
| 324 | return err | |
| 325 | } | |
| 251 | 326 | if b.toShutdown { |
| 252 | // shutdown at next flush | |
| 253 | // this ensures no bar ends up with less than 100% rendered | |
| 254 | defer func() { | |
| 327 | if b.recoveredPanic != nil { | |
| 255 | 328 | s.barShutdownQueue = append(s.barShutdownQueue, b) |
| 256 | }() | |
| 257 | } | |
| 258 | lineCount += b.extendedLines + 1 | |
| 259 | bm[b] = struct{}{} | |
| 329 | b.toShutdown = false | |
| 330 | } else { | |
| 331 | // shutdown at next flush | |
| 332 | // this ensures no bar ends up with less than 100% rendered | |
| 333 | defer func() { | |
| 334 | s.barShutdownQueue = append(s.barShutdownQueue, b) | |
| 335 | }() | |
| 336 | } | |
| 337 | } | |
| 338 | bm[b] = frame.lines | |
| 339 | totalLines += frame.lines | |
| 260 | 340 | } |
| 261 | 341 | |
| 262 | 342 | for _, b := range s.barShutdownQueue { |
| 266 | 346 | delete(s.parkedBars, b) |
| 267 | 347 | b.toDrop = true |
| 268 | 348 | } |
| 349 | if s.popCompleted && !b.noPop { | |
| 350 | totalLines -= bm[b] | |
| 351 | b.toDrop = true | |
| 352 | } | |
| 269 | 353 | if b.toDrop { |
| 270 | 354 | delete(bm, b) |
| 271 | 355 | s.heapUpdated = true |
| 272 | } else if s.popCompleted { | |
| 273 | if b := b; !b.noPop { | |
| 274 | defer func() { | |
| 275 | s.barPopQueue = append(s.barPopQueue, b) | |
| 276 | }() | |
| 277 | } | |
| 278 | 356 | } |
| 279 | 357 | b.cancel() |
| 280 | 358 | } |
| 281 | 359 | s.barShutdownQueue = s.barShutdownQueue[0:0] |
| 282 | ||
| 283 | for _, b := range s.barPopQueue { | |
| 284 | delete(bm, b) | |
| 285 | s.heapUpdated = true | |
| 286 | lineCount -= b.extendedLines + 1 | |
| 287 | } | |
| 288 | s.barPopQueue = s.barPopQueue[0:0] | |
| 289 | 360 | |
| 290 | 361 | for b := range bm { |
| 291 | 362 | heap.Push(&s.bHeap, b) |
| 292 | 363 | } |
| 293 | 364 | |
| 294 | return cw.Flush(lineCount) | |
| 295 | } | |
| 296 | ||
| 297 | func (s *pState) newTicker(done <-chan struct{}) chan time.Time { | |
| 298 | ch := make(chan time.Time) | |
| 299 | if s.shutdownNotifier == nil { | |
| 300 | s.shutdownNotifier = make(chan struct{}) | |
| 301 | } | |
| 302 | go func() { | |
| 303 | if s.renderDelay != nil { | |
| 304 | <-s.renderDelay | |
| 305 | } | |
| 306 | if s.refreshSrc == nil { | |
| 307 | ticker := time.NewTicker(s.rr) | |
| 308 | defer ticker.Stop() | |
| 309 | s.refreshSrc = ticker.C | |
| 310 | } | |
| 311 | for { | |
| 312 | select { | |
| 313 | case tick := <-s.refreshSrc: | |
| 314 | ch <- tick | |
| 315 | case <-done: | |
| 316 | close(s.shutdownNotifier) | |
| 317 | return | |
| 318 | } | |
| 319 | } | |
| 320 | }() | |
| 321 | return ch | |
| 365 | return cw.Flush(totalLines) | |
| 322 | 366 | } |
| 323 | 367 | |
| 324 | 368 | func (s *pState) updateSyncMatrix() { |
| 341 | 385 | |
| 342 | 386 | func (s *pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState { |
| 343 | 387 | bs := &bState{ |
| 388 | id: s.idCount, | |
| 389 | priority: s.idCount, | |
| 390 | reqWidth: s.reqWidth, | |
| 344 | 391 | total: total, |
| 345 | baseF: extractBaseFiller(filler), | |
| 346 | 392 | filler: filler, |
| 347 | priority: s.idCount, | |
| 348 | id: s.idCount, | |
| 349 | width: s.width, | |
| 393 | extender: func(r io.Reader, _ int, _ decor.Statistics) (io.Reader, int) { return r, 0 }, | |
| 350 | 394 | debugOut: s.debugOut, |
| 351 | extender: func(r io.Reader, _ int, _ *decor.Statistics) (io.Reader, int) { | |
| 352 | return r, 0 | |
| 353 | }, | |
| 395 | } | |
| 396 | ||
| 397 | if total > 0 { | |
| 398 | bs.triggerComplete = true | |
| 354 | 399 | } |
| 355 | 400 | |
| 356 | 401 | for _, opt := range options { |
| 359 | 404 | } |
| 360 | 405 | } |
| 361 | 406 | |
| 407 | if bs.middleware != nil { | |
| 408 | bs.filler = bs.middleware(filler) | |
| 409 | bs.middleware = nil | |
| 410 | } | |
| 411 | ||
| 362 | 412 | if s.popCompleted && !bs.noPop { |
| 363 | bs.priority = -1 | |
| 364 | } | |
| 365 | ||
| 366 | bs.bufP = bytes.NewBuffer(make([]byte, 0, bs.width)) | |
| 367 | bs.bufB = bytes.NewBuffer(make([]byte, 0, bs.width)) | |
| 368 | bs.bufA = bytes.NewBuffer(make([]byte, 0, bs.width)) | |
| 413 | bs.priority = -(math.MaxInt32 - s.idCount) | |
| 414 | } | |
| 415 | ||
| 416 | for i := 0; i < len(bs.buffers); i++ { | |
| 417 | bs.buffers[i] = bytes.NewBuffer(make([]byte, 0, 512)) | |
| 418 | } | |
| 369 | 419 | |
| 370 | 420 | return bs |
| 371 | 421 | } |
| 372 | 422 | |
| 373 | 423 | func syncWidth(matrix map[int][]chan int) { |
| 374 | 424 | for _, column := range matrix { |
| 375 | column := column | |
| 376 | go func() { | |
| 377 | var maxWidth int | |
| 378 | for _, ch := range column { | |
| 379 | if w := <-ch; w > maxWidth { | |
| 380 | maxWidth = w | |
| 381 | } | |
| 382 | } | |
| 383 | for _, ch := range column { | |
| 384 | ch <- maxWidth | |
| 385 | } | |
| 386 | }() | |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | func extractBaseFiller(f BarFiller) BarFiller { | |
| 391 | type wrapper interface { | |
| 392 | Base() BarFiller | |
| 393 | } | |
| 394 | if f, ok := f.(wrapper); ok { | |
| 395 | return extractBaseFiller(f.Base()) | |
| 396 | } | |
| 397 | return f | |
| 398 | } | |
| 425 | go maxWidthDistributor(column) | |
| 426 | } | |
| 427 | } | |
| 428 | ||
| 429 | var maxWidthDistributor = func(column []chan int) { | |
| 430 | var maxWidth int | |
| 431 | for _, ch := range column { | |
| 432 | if w := <-ch; w > maxWidth { | |
| 433 | maxWidth = w | |
| 434 | } | |
| 435 | } | |
| 436 | for _, ch := range column { | |
| 437 | ch <- maxWidth | |
| 438 | } | |
| 439 | } | |
| 8 | 8 | "testing" |
| 9 | 9 | "time" |
| 10 | 10 | |
| 11 | "github.com/vbauerster/mpb/v5" | |
| 11 | "github.com/vbauerster/mpb/v7" | |
| 12 | "github.com/vbauerster/mpb/v7/decor" | |
| 12 | 13 | ) |
| 13 | 14 | |
| 14 | 15 | func init() { |
| 22 | 23 | wg.Add(1) |
| 23 | 24 | b := p.AddBar(100) |
| 24 | 25 | go func() { |
| 26 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 25 | 27 | for i := 0; i < 100; i++ { |
| 26 | 28 | if i == 33 { |
| 27 | 29 | wg.Done() |
| 28 | 30 | } |
| 29 | 31 | b.Increment() |
| 30 | time.Sleep(randomDuration(100 * time.Millisecond)) | |
| 32 | time.Sleep((time.Duration(rng.Intn(10)+1) * (10 * time.Millisecond)) / 2) | |
| 31 | 33 | } |
| 32 | 34 | }() |
| 33 | 35 | |
| 49 | 51 | bars := make([]*mpb.Bar, 3) |
| 50 | 52 | for i := 0; i < 3; i++ { |
| 51 | 53 | b := p.AddBar(100) |
| 52 | bars[i] = b | |
| 54 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 53 | 55 | go func(n int) { |
| 54 | 56 | for i := 0; !b.Completed(); i++ { |
| 55 | 57 | if n == 0 && i >= 33 { |
| 57 | 59 | wg.Done() |
| 58 | 60 | } |
| 59 | 61 | b.Increment() |
| 60 | time.Sleep(randomDuration(100 * time.Millisecond)) | |
| 62 | time.Sleep((time.Duration(rng.Intn(10)+1) * (10 * time.Millisecond)) / 2) | |
| 61 | 63 | } |
| 62 | 64 | }(i) |
| 65 | bars[i] = b | |
| 63 | 66 | } |
| 64 | 67 | |
| 65 | 68 | wg.Wait() |
| 66 | 69 | count := p.BarCount() |
| 67 | 70 | if count != 2 { |
| 68 | t.Errorf("BarCount want: %q, got: %q\n", 2, count) | |
| 71 | t.Errorf("BarCount want: %d, got: %d\n", 2, count) | |
| 69 | 72 | } |
| 70 | 73 | bars[1].Abort(true) |
| 71 | 74 | bars[2].Abort(true) |
| 73 | 76 | } |
| 74 | 77 | |
| 75 | 78 | func TestWithContext(t *testing.T) { |
| 79 | shutdown := make(chan struct{}) | |
| 76 | 80 | ctx, cancel := context.WithCancel(context.Background()) |
| 77 | shutdown := make(chan struct{}) | |
| 78 | p := mpb.NewWithContext(ctx, | |
| 79 | mpb.WithOutput(ioutil.Discard), | |
| 80 | mpb.WithRefreshRate(50*time.Millisecond), | |
| 81 | mpb.WithShutdownNotifier(shutdown), | |
| 82 | ) | |
| 81 | p := mpb.NewWithContext(ctx, mpb.WithShutdownNotifier(shutdown), mpb.WithOutput(ioutil.Discard)) | |
| 83 | 82 | |
| 84 | total := 10000 | |
| 85 | numBars := 3 | |
| 86 | bars := make([]*mpb.Bar, 0, numBars) | |
| 83 | start := make(chan struct{}) | |
| 84 | done := make(chan struct{}) | |
| 85 | fail := make(chan struct{}) | |
| 86 | bar := p.AddBar(0) // never complete bar | |
| 87 | go func() { | |
| 88 | close(start) | |
| 89 | for !bar.Completed() { | |
| 90 | bar.Increment() | |
| 91 | time.Sleep(randomDuration(100 * time.Millisecond)) | |
| 92 | } | |
| 93 | close(done) | |
| 94 | }() | |
| 95 | ||
| 96 | go func() { | |
| 97 | select { | |
| 98 | case <-done: | |
| 99 | p.Wait() | |
| 100 | case <-time.After(150 * time.Millisecond): | |
| 101 | close(fail) | |
| 102 | } | |
| 103 | }() | |
| 104 | ||
| 105 | <-start | |
| 106 | cancel() | |
| 107 | select { | |
| 108 | case <-shutdown: | |
| 109 | case <-fail: | |
| 110 | t.Error("Progress didn't shutdown") | |
| 111 | } | |
| 112 | } | |
| 113 | ||
| 114 | // MaxWidthDistributor shouldn't stuck in the middle while removing or aborting a bar | |
| 115 | func TestMaxWidthDistributor(t *testing.T) { | |
| 116 | ||
| 117 | makeWrapper := func(f func([]chan int), start, end chan struct{}) func([]chan int) { | |
| 118 | return func(column []chan int) { | |
| 119 | start <- struct{}{} | |
| 120 | f(column) | |
| 121 | <-end | |
| 122 | } | |
| 123 | } | |
| 124 | ||
| 125 | ready := make(chan struct{}) | |
| 126 | start := make(chan struct{}) | |
| 127 | end := make(chan struct{}) | |
| 128 | *mpb.MaxWidthDistributor = makeWrapper(*mpb.MaxWidthDistributor, start, end) | |
| 129 | ||
| 130 | total := 80 | |
| 131 | numBars := 6 | |
| 132 | p := mpb.New(mpb.WithOutput(ioutil.Discard)) | |
| 87 | 133 | for i := 0; i < numBars; i++ { |
| 88 | bar := p.AddBar(int64(total)) | |
| 89 | bars = append(bars, bar) | |
| 134 | bar := p.AddBar(int64(total), | |
| 135 | mpb.BarOptional(mpb.BarRemoveOnComplete(), i == 0), | |
| 136 | mpb.PrependDecorators( | |
| 137 | decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), | |
| 138 | ), | |
| 139 | ) | |
| 90 | 140 | go func() { |
| 91 | for !bar.Completed() { | |
| 92 | bar.Increment() | |
| 93 | time.Sleep(randomDuration(100 * time.Millisecond)) | |
| 141 | <-ready | |
| 142 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 143 | for i := 0; i < total; i++ { | |
| 144 | start := time.Now() | |
| 145 | if id := bar.ID(); id > 1 && i >= 42 { | |
| 146 | if id&1 == 1 { | |
| 147 | bar.Abort(true) | |
| 148 | } else { | |
| 149 | bar.Abort(false) | |
| 150 | } | |
| 151 | } | |
| 152 | time.Sleep((time.Duration(rng.Intn(10)+1) * (50 * time.Millisecond)) / 2) | |
| 153 | bar.IncrInt64(rand.Int63n(5) + 1) | |
| 154 | bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 94 | 155 | } |
| 95 | 156 | }() |
| 96 | 157 | } |
| 97 | 158 | |
| 98 | time.Sleep(50 * time.Millisecond) | |
| 99 | cancel() | |
| 159 | go func() { | |
| 160 | <-ready | |
| 161 | p.Wait() | |
| 162 | close(start) | |
| 163 | }() | |
| 100 | 164 | |
| 101 | p.Wait() | |
| 102 | select { | |
| 103 | case <-shutdown: | |
| 104 | case <-time.After(100 * time.Millisecond): | |
| 105 | t.Error("Progress didn't stop") | |
| 165 | res := t.Run("maxWidthDistributor", func(t *testing.T) { | |
| 166 | close(ready) | |
| 167 | for v := range start { | |
| 168 | timer := time.NewTimer(100 * time.Millisecond) | |
| 169 | select { | |
| 170 | case end <- v: | |
| 171 | timer.Stop() | |
| 172 | case <-timer.C: | |
| 173 | t.FailNow() | |
| 174 | } | |
| 175 | } | |
| 176 | }) | |
| 177 | ||
| 178 | if !res { | |
| 179 | t.Error("maxWidthDistributor stuck in the middle") | |
| 106 | 180 | } |
| 107 | 181 | } |
| 108 | 182 | |
| 10 | 10 | bar *Bar |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | func (x *proxyReader) Read(p []byte) (int, error) { | |
| 13 | func (x proxyReader) Read(p []byte) (int, error) { | |
| 14 | 14 | n, err := x.ReadCloser.Read(p) |
| 15 | 15 | x.bar.IncrBy(n) |
| 16 | 16 | if err == io.EOF { |
| 17 | go x.bar.SetTotal(0, true) | |
| 17 | go x.bar.SetTotal(-1, true) | |
| 18 | 18 | } |
| 19 | 19 | return n, err |
| 20 | 20 | } |
| 21 | 21 | |
| 22 | 22 | type proxyWriterTo struct { |
| 23 | io.ReadCloser // *proxyReader | |
| 24 | wt io.WriterTo | |
| 25 | bar *Bar | |
| 23 | proxyReader | |
| 24 | wt io.WriterTo | |
| 26 | 25 | } |
| 27 | 26 | |
| 28 | func (x *proxyWriterTo) WriteTo(w io.Writer) (int64, error) { | |
| 27 | func (x proxyWriterTo) WriteTo(w io.Writer) (int64, error) { | |
| 29 | 28 | n, err := x.wt.WriteTo(w) |
| 30 | 29 | x.bar.IncrInt64(n) |
| 31 | 30 | if err == io.EOF { |
| 32 | go x.bar.SetTotal(0, true) | |
| 31 | go x.bar.SetTotal(-1, true) | |
| 33 | 32 | } |
| 34 | 33 | return n, err |
| 35 | 34 | } |
| 36 | 35 | |
| 37 | 36 | type ewmaProxyReader struct { |
| 38 | io.ReadCloser // *proxyReader | |
| 39 | bar *Bar | |
| 40 | iT time.Time | |
| 37 | proxyReader | |
| 41 | 38 | } |
| 42 | 39 | |
| 43 | func (x *ewmaProxyReader) Read(p []byte) (int, error) { | |
| 44 | n, err := x.ReadCloser.Read(p) | |
| 40 | func (x ewmaProxyReader) Read(p []byte) (int, error) { | |
| 41 | start := time.Now() | |
| 42 | n, err := x.proxyReader.Read(p) | |
| 45 | 43 | if n > 0 { |
| 46 | x.bar.DecoratorEwmaUpdate(time.Since(x.iT)) | |
| 47 | x.iT = time.Now() | |
| 44 | x.bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 48 | 45 | } |
| 49 | 46 | return n, err |
| 50 | 47 | } |
| 51 | 48 | |
| 52 | 49 | type ewmaProxyWriterTo struct { |
| 53 | io.ReadCloser // *ewmaProxyReader | |
| 54 | wt io.WriterTo // *proxyWriterTo | |
| 55 | bar *Bar | |
| 56 | iT time.Time | |
| 50 | ewmaProxyReader | |
| 51 | wt proxyWriterTo | |
| 57 | 52 | } |
| 58 | 53 | |
| 59 | func (x *ewmaProxyWriterTo) WriteTo(w io.Writer) (int64, error) { | |
| 54 | func (x ewmaProxyWriterTo) WriteTo(w io.Writer) (int64, error) { | |
| 55 | start := time.Now() | |
| 60 | 56 | n, err := x.wt.WriteTo(w) |
| 61 | 57 | if n > 0 { |
| 62 | x.bar.DecoratorEwmaUpdate(time.Since(x.iT)) | |
| 63 | x.iT = time.Now() | |
| 58 | x.bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 64 | 59 | } |
| 65 | 60 | return n, err |
| 66 | 61 | } |
| 67 | 62 | |
| 68 | func newProxyReader(r io.Reader, bar *Bar) io.ReadCloser { | |
| 69 | rc := toReadCloser(r) | |
| 70 | rc = &proxyReader{rc, bar} | |
| 71 | ||
| 72 | if wt, isWriterTo := r.(io.WriterTo); bar.hasEwmaDecorators { | |
| 73 | now := time.Now() | |
| 74 | rc = &ewmaProxyReader{rc, bar, now} | |
| 75 | if isWriterTo { | |
| 76 | rc = &ewmaProxyWriterTo{rc, wt, bar, now} | |
| 63 | func (b *Bar) newProxyReader(r io.Reader) (rc io.ReadCloser) { | |
| 64 | pr := proxyReader{toReadCloser(r), b} | |
| 65 | if wt, ok := r.(io.WriterTo); ok { | |
| 66 | pw := proxyWriterTo{pr, wt} | |
| 67 | if b.hasEwmaDecorators { | |
| 68 | rc = ewmaProxyWriterTo{ewmaProxyReader{pr}, pw} | |
| 69 | } else { | |
| 70 | rc = pw | |
| 77 | 71 | } |
| 78 | } else if isWriterTo { | |
| 79 | rc = &proxyWriterTo{rc, wt, bar} | |
| 72 | } else if b.hasEwmaDecorators { | |
| 73 | rc = ewmaProxyReader{pr} | |
| 74 | } else { | |
| 75 | rc = pr | |
| 80 | 76 | } |
| 81 | 77 | return rc |
| 82 | 78 | } |
| 6 | 6 | "strings" |
| 7 | 7 | "testing" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v7" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | const content = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do |
| 32 | 32 | |
| 33 | 33 | tReader := &testReader{strings.NewReader(content), false} |
| 34 | 34 | |
| 35 | bar := p.AddBar(int64(len(content)), mpb.TrimSpace()) | |
| 35 | bar := p.AddBar(int64(len(content)), mpb.BarFillerTrim()) | |
| 36 | 36 | |
| 37 | 37 | var buf bytes.Buffer |
| 38 | 38 | _, err := io.Copy(&buf, bar.ProxyReader(tReader)) |
| 66 | 66 | p := mpb.New(mpb.WithOutput(ioutil.Discard)) |
| 67 | 67 | |
| 68 | 68 | var reader io.Reader = strings.NewReader(content) |
| 69 | wt := reader.(io.WriterTo) | |
| 70 | tReader := &testWriterTo{reader, wt, false} | |
| 69 | tReader := &testWriterTo{reader, reader.(io.WriterTo), false} | |
| 71 | 70 | |
| 72 | bar := p.AddBar(int64(len(content)), mpb.TrimSpace()) | |
| 71 | bar := p.AddBar(int64(len(content)), mpb.BarFillerTrim()) | |
| 73 | 72 | |
| 74 | 73 | var buf bytes.Buffer |
| 75 | 74 | _, err := io.Copy(&buf, bar.ProxyReader(tReader)) |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "strings" | |
| 5 | "unicode/utf8" | |
| 6 | ||
| 7 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | ) | |
| 9 | ||
| 10 | // SpinnerAlignment enum. | |
| 11 | type SpinnerAlignment int | |
| 12 | ||
| 13 | // SpinnerAlignment kinds. | |
| 14 | const ( | |
| 15 | SpinnerOnLeft SpinnerAlignment = iota | |
| 16 | SpinnerOnMiddle | |
| 17 | SpinnerOnRight | |
| 18 | ) | |
| 19 | ||
| 20 | // DefaultSpinnerStyle is a slice of strings, which makes a spinner. | |
| 21 | var DefaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 22 | ||
| 23 | type spinnerFiller struct { | |
| 24 | frames []string | |
| 25 | count uint | |
| 26 | alignment SpinnerAlignment | |
| 27 | } | |
| 28 | ||
| 29 | // NewSpinnerFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. | |
| 30 | func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller { | |
| 31 | if len(style) == 0 { | |
| 32 | style = DefaultSpinnerStyle | |
| 33 | } | |
| 34 | filler := &spinnerFiller{ | |
| 35 | frames: style, | |
| 36 | alignment: alignment, | |
| 37 | } | |
| 38 | return filler | |
| 39 | } | |
| 40 | ||
| 41 | func (s *spinnerFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 42 | ||
| 43 | frame := s.frames[s.count%uint(len(s.frames))] | |
| 44 | frameWidth := utf8.RuneCountInString(frame) | |
| 45 | ||
| 46 | if width < frameWidth { | |
| 47 | return | |
| 48 | } | |
| 49 | ||
| 50 | switch rest := width - frameWidth; s.alignment { | |
| 51 | case SpinnerOnLeft: | |
| 52 | io.WriteString(w, frame+strings.Repeat(" ", rest)) | |
| 53 | case SpinnerOnMiddle: | |
| 54 | str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) | |
| 55 | io.WriteString(w, str) | |
| 56 | case SpinnerOnRight: | |
| 57 | io.WriteString(w, strings.Repeat(" ", rest)+frame) | |
| 58 | } | |
| 59 | s.count++ | |
| 60 | } |