Codebase list golang-github-vbauerster-mpb / 13d82fb
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
97 changed file(s) with 3749 addition(s) and 1678 deletion(s). Raw diff Collapse all Expand all
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
-8
.travis.yml less more
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
00 # Multi Progress Bar
11
2 [![GoDoc](https://godoc.org/github.com/vbauerster/mpb?status.svg)](https://godoc.org/github.com/vbauerster/mpb)
3 [![Build Status](https://travis-ci.org/vbauerster/mpb.svg?branch=master)](https://travis-ci.org/vbauerster/mpb)
4 [![Go Report Card](https://goreportcard.com/badge/github.com/vbauerster/mpb)](https://goreportcard.com/report/github.com/vbauerster/mpb)
2 [![GoDoc](https://pkg.go.dev/badge/github.com/vbauerster/mpb)](https://pkg.go.dev/github.com/vbauerster/mpb/v7)
3 [![Test status](https://github.com/vbauerster/mpb/actions/workflows/test.yml/badge.svg)](https://github.com/vbauerster/mpb/actions/workflows/test.yml)
4 [![Donate with PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/vbauerster)
55
66 **mpb** is a Go lib for rendering progress bars in terminal applications.
77
88 ## Features
99
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
1616
1717 ## Usage
1818
1919 #### [Rendering single bar](_examples/singleBar/main.go)
20
2021 ```go
2122 package main
2223
2425 "math/rand"
2526 "time"
2627
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"
2930 )
3031
3132 func main() {
3435
3536 total := 100
3637 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("╟"),
4142 mpb.PrependDecorators(
4243 // display our name with one space on the right
4344 decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}),
6061 ```
6162
6263 #### [Rendering multiple bars](_examples/multiBars/main.go)
64
6365 ```go
6466 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
6668 p := mpb.New(mpb.WithWaitGroup(&wg))
6769 total, numBars := 100, 3
6870 wg.Add(numBars)
8082 // replace ETA decorator with "done" message, OnComplete event
8183 decor.OnComplete(
8284 // 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",
8486 ),
8587 ),
8688 )
100102 }
101103 }()
102104 }
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
104106 p.Wait()
105107 ```
106108
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
66 "sync"
77 "time"
88
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"
1111 )
1212
1313 func main() {
1414 var wg sync.WaitGroup
15 // passed wg will be accounted at p.Wait() call
1516 p := mpb.New(mpb.WithWaitGroup(&wg))
1617 total, numBars := 100, 3
1718 wg.Add(numBars)
1819
1920 for i := 0; i < numBars; i++ {
2021 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) {
2223 if s.Completed {
2324 fmt.Fprintf(w, "Bar id: %d has been completed\n", s.ID)
2425 }
5455 }
5556 }()
5657 }
57 // wait for all bars to complete and flush
58 // wait for passed wg and for all bars to complete and flush
5859 p.Wait()
5960 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
66 "sync"
77 "time"
88
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"
1111 )
1212
1313 func main() {
1515 defer cancel()
1616
1717 var wg sync.WaitGroup
18 // passed wg will be accounted at p.Wait() call
1819 p := mpb.NewWithContext(ctx, mpb.WithWaitGroup(&wg))
1920 total := 300
2021 numBars := 3
4849 }
4950 }()
5051 }
51
52 // wait for passed wg and for all bars to complete and flush
5253 p.Wait()
5354 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 func init() {
1515
1616 func main() {
1717 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))
1920 numBars := 4
2021
2122 var bars []*mpb.Bar
4344 i := i
4445 go func() {
4546 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"
4749 // preparing delayed bars
4850 b := p.AddBar(rand.Int63n(101)+100,
4951 mpb.BarQueueAfter(bars[i]),
6264 go newTask(doneWg, b, numBars-i)
6365 }()
6466 }
65
67 // wait for passed doneWg and for all bars to complete and flush
6668 p.Wait()
6769 }
6870
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 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 func main() {
1313 var wg sync.WaitGroup
14 // passed wg will be accounted at p.Wait() call
1415 p := mpb.New(
1516 mpb.WithWaitGroup(&wg),
16 // container's width.
1717 mpb.WithWidth(60),
1818 )
1919 total, numBars := 100, 3
2323 name := fmt.Sprintf("Bar#%d:", i)
2424 bar := p.AddBar(int64(total),
2525 // 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),
2727 mpb.PrependDecorators(
2828 // simple name decorator
2929 decor.Name(name),
5454 }
5555 }()
5656 }
57 // wait for all bars to complete and flush
57 // wait for passed wg and for all bars to complete and flush
5858 p.Wait()
5959 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
44 "math/rand"
55 "time"
66
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"
99 )
1010
1111 func init() {
1616 p := mpb.New(mpb.WithWidth(64))
1717
1818 var total int64
19 // new bar with 'trigger complete event' disabled, because total is zero
1920 bar := p.AddBar(total,
2021 mpb.PrependDecorators(decor.Counters(decor.UnitKiB, "% .1f / % .1f")),
2122 mpb.AppendDecorators(decor.Percentage()),
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "io/ioutil"
66 "time"
77
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"
1010 )
1111
1212 func main() {
1818 mpb.WithRefreshRate(180*time.Millisecond),
1919 )
2020
21 bar := p.AddBar(total, mpb.BarStyle("[=>-|"),
21 bar := p.New(total,
22 mpb.BarStyle().Rbound("|"),
2223 mpb.PrependDecorators(
2324 decor.CountersKibiByte("% .2f / % .2f"),
2425 ),
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 func main() {
1313 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
1515 p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(60))
1616 total, numBars := 100, 3
1717 wg.Add(numBars)
2626 "done",
2727 ),
2828 decor.WCSyncSpace, // Placeholder
29 decor.WCSyncSpace, // Placeholder
2930 ),
3031 )
3132 } else {
3233 pdecorators = mpb.PrependDecorators(
3334 decor.CountersNoUnit("% .1d / % .1d", decor.WCSyncSpace),
35 decor.OnComplete(decor.Spinner(nil, decor.WCSyncSpace), "done"),
3436 decor.OnComplete(decor.Spinner(nil, decor.WCSyncSpace), "done"),
3537 )
3638 }
5658 }
5759 }()
5860 }
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
6062 p.Wait()
6163 }
6264
6365 func newVariadicSpinner(wc decor.WC) decor.Decorator {
6466 spinner := decor.Spinner(nil)
65 f := func(s *decor.Statistics) string {
67 fn := func(s decor.Statistics) string {
6668 return strings.Repeat(spinner.Decor(s), int(s.Current/3))
6769 }
68 return decor.Any(f, wc)
70 return decor.Any(fn, wc)
6971 }
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 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 func main() {
1313 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
1515 p := mpb.New(mpb.WithWaitGroup(&wg))
1616 total, numBars := 100, 3
1717 wg.Add(numBars)
2929 // replace ETA decorator with "done" message, OnComplete event
3030 decor.OnComplete(
3131 // 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",
3333 ),
3434 ),
3535 )
4949 }
5050 }()
5151 }
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
5353 p.Wait()
5454 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
22 import (
33 "fmt"
44 "os"
5 "strings"
56 "sync"
67 "time"
78
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"
1011 )
1112
1213 func main() {
1314 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 )
1520
16 wantPanic := "Some really long panic panic panic panic panic panic panic, really it is very long"
21 wantPanic := strings.Repeat("Panic ", 64)
1722 numBars := 3
1823 wg.Add(numBars)
1924
2934 }
3035 }()
3136 }
32
37 // wait for passed wg and for all bars to complete and flush
3338 p.Wait()
3439 }
3540
3641 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 {
3944 panic(panicMsg)
4045 }
4146 return name
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
11
22 import (
33 "fmt"
4 "io"
54 "math/rand"
6 "sync"
75 "time"
86
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"
119 )
1210
1311 func main() {
1412 p := mpb.New(mpb.PopCompletedMode())
1513
16 total, numBars := 100, 2
14 total, numBars := 100, 4
1715 for i := 0; i < numBars; i++ {
1816 name := fmt.Sprintf("Bar#%d:", i)
1917 bar := p.AddBar(int64(total),
20 mpb.BarNoPop(),
18 mpb.BarFillerOnComplete(fmt.Sprintf("%s has been completed", name)),
19 mpb.BarFillerTrim(),
2120 mpb.PrependDecorators(
22 decor.Name(name),
23 decor.Percentage(decor.WCSyncSpace),
21 decor.OnComplete(decor.Name(name), ""),
22 decor.OnComplete(decor.NewPercentage(" % d "), ""),
2423 ),
2524 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), ""),
2927 ),
3028 )
3129 // 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 }
4541 }
4642
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()
6143 p.Wait()
6244 }
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 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
66 "sync"
77 "time"
88
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"
1111 )
1212
1313 var quietMode bool
1919 func main() {
2020 flag.Parse()
2121 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
2323 p := mpb.New(
2424 mpb.WithWaitGroup(&wg),
25 mpb.ContainerOptOn(
25 mpb.ContainerOptional(
2626 // 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.
3030 mpb.WithOutput(nil),
31 func() bool { return quietMode },
31 quietMode,
3232 ),
3333 )
3434 total, numBars := 100, 3
6767 }
6868 }()
6969 }
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
7171 p.Wait()
7272 fmt.Println("done")
7373 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 func main() {
1313 var wg sync.WaitGroup
14 // passed wg will be accounted at p.Wait() call
1415 p := mpb.New(mpb.WithWaitGroup(&wg))
1516 total := 100
1617 numBars := 3
2021 name := fmt.Sprintf("Bar#%d:", i)
2122 bar := p.AddBar(int64(total),
2223 mpb.BarID(i),
23 mpb.BarOptOn(mpb.BarRemoveOnComplete(), func() bool { return i == 0 }),
24 mpb.BarOptional(mpb.BarRemoveOnComplete(), i == 0),
2425 mpb.PrependDecorators(
2526 decor.Name(name),
26 decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace),
2727 ),
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 ),
2938 )
3039 go func() {
3140 defer wg.Done()
3241 rng := rand.New(rand.NewSource(time.Now().UnixNano()))
3342 max := 100 * time.Millisecond
3443 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()
3844 if bar.ID() == 2 && i >= 42 {
39 // aborting and removing while bar is running
40 bar.Abort(true)
45 bar.Abort(false)
4146 }
4247 time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10)
4348 bar.Increment()
44 // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract
45 bar.DecoratorEwmaUpdate(time.Since(start))
4649 }
4750 }()
4851 }
49
52 // wait for passed wg and for all bars to complete and flush
5053 p.Wait()
5154 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 func main() {
1313 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
1515 p := mpb.New(mpb.WithWaitGroup(&wg))
1616 total, numBars := 100, 3
1717 wg.Add(numBars)
1818
1919 for i := 0; i < numBars; i++ {
2020 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),
2422 mpb.PrependDecorators(
2523 // simple name decorator
2624 decor.Name(name),
5149 }
5250 }()
5351 }
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
5553 p.Wait()
5654 }
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 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
33 "math/rand"
44 "time"
55
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"
88 )
99
1010 func main() {
1313
1414 total := 100
1515 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("╟"),
2020 mpb.PrependDecorators(
2121 // display our name with one space on the right
2222 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 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 func main() {
1313 var wg sync.WaitGroup
14 // passed wg will be accounted at p.Wait() call
1415 p := mpb.New(
1516 mpb.WithWaitGroup(&wg),
16 mpb.WithWidth(13),
17 mpb.WithWidth(16),
1718 )
1819 total, numBars := 101, 3
1920 wg.Add(numBars)
2021
2122 for i := 0; i < numBars; i++ {
2223 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",
3134 ),
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 )
5837 // simulating some work
5938 go func() {
6039 defer wg.Done()
7150 }
7251 }()
7352 }
74 // wait for all bars to complete and flush
53 // wait for passed wg and for all bars to complete and flush
7554 p.Wait()
7655 }
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 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 func main() {
1313 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
1515 p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(64))
1616 total, numBars := 100, 3
1717 wg.Add(numBars)
4343 }
4444 }()
4545 }
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
4747 p.Wait()
4848 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v7 v7.3.2
55 "sync"
66 "time"
77
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"
1010 )
1111
1212 const (
1515
1616 func main() {
1717 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))
2220 wg.Add(totalBars)
2321
2422 for i := 0; i < totalBars; i++ {
4644 }
4745 }()
4846 }
49
47 // wait for passed wg and for all bars to complete and flush
5048 p.Wait()
5149 }
11
22 go 1.14
33
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 )
77 "sync"
88 "time"
99
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"
1213 )
1314
1415 func main() {
1718 total := 100
1819 msgCh := make(chan string)
1920 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)),
2846 )
2947 ew := &errorWrapper{}
3048 time.AfterFunc(2*time.Second, func() {
7593 ew.Unlock()
7694 }
7795
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 {
9299 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:
122101 return ""
123102 default:
124103 return base.Decor(s)
125104 }
126105 }
127 return decor.Any(f)
106 return decor.Any(fn)
128107 }
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 }
+286
-193
bar.go less more
44 "context"
55 "fmt"
66 "io"
7 "log"
7 "runtime/debug"
88 "strings"
9 "sync"
910 "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"
1315 )
1416
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.
3118 type Bar struct {
3219 priority int // used by heap
3320 index int // used by heap
3421
35 extendedLines int
3622 toShutdown bool
3723 toDrop bool
3824 noPop bool
3925 hasEwmaDecorators bool
4026 operateState chan func(*bState)
41 frameCh chan io.Reader
42 syncTableCh chan [][]chan int
43 completed chan bool
27 frameCh chan *frame
4428
4529 // cancel is called either by user or on complete event
4630 cancel func()
4731 // done is closed after cacheState is assigned
4832 done chan struct{}
49 // cacheState is populated, right after close(shutdown)
33 // cacheState is populated, right after close(b.done)
5034 cacheState *bState
5135
5236 container *Progress
53 dlogger *log.Logger
5437 recoveredPanic interface{}
5538 }
5639
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.
5944 type bState struct {
60 baseF BarFiller
61 filler BarFiller
6245 id int
63 width int
46 priority int
47 reqWidth int
6448 total int64
6549 current int64
66 lastN int64
67 iterated bool
50 refill int64
51 lastIncrement int64
6852 trimSpace bool
69 toComplete bool
53 completed bool
7054 completeFlushed bool
55 aborted bool
56 triggerComplete bool
57 dropOnComplete bool
7158 noPop bool
7259 aDecorators []decor.Decorator
7360 pDecorators []decor.Decorator
7461 averageDecorators []decor.AverageDecorator
7562 ewmaDecorators []decor.EwmaDecorator
7663 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
8469 // runningBar is a key for *pState.parkedBars
8570 runningBar *Bar
8671
8772 debugOut io.Writer
8873 }
8974
75 type frame struct {
76 reader io.Reader
77 lines int
78 }
79
9080 func newBar(container *Progress, bs *bState) *Bar {
91 logPrefix := fmt.Sprintf("%sbar#%02d ", container.dlogger.Prefix(), bs.id)
9281 ctx, cancel := context.WithCancel(container.ctx)
9382
9483 bar := &Bar{
9786 toDrop: bs.dropOnComplete,
9887 noPop: bs.noPop,
9988 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),
10390 done: make(chan struct{}),
10491 cancel: cancel,
105 dlogger: log.New(bs.debugOut, logPrefix, log.Lshortfile),
10692 }
10793
10894 go bar.serve(ctx, bs)
115101 if r == nil {
116102 panic("expected non nil io.Reader")
117103 }
118 return newProxyReader(r, b)
104 return b.newProxyReader(r)
119105 }
120106
121107 // ID returs id of the bar.
140126 }
141127 }
142128
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.
146133 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:
154139 }
155140 }
156141
157142 // TraverseDecorators traverses all available decorators and calls cb func on each.
158143 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) {
160147 for _, decorators := range [...][]decor.Decorator{
161148 s.pDecorators,
162149 s.aDecorators,
165152 cb(extractBaseDecorator(d))
166153 }
167154 }
155 close(done)
156 }:
157 <-done
158 case <-b.done:
168159 }
169160 }
170161
171162 // 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 {
178169 s.total = s.current
179170 } else {
180171 s.total = total
181172 }
182 if complete && !s.toComplete {
173 if s.triggerComplete && !s.completed {
183174 s.current = s.total
184 s.toComplete = true
185 go b.refreshTillShutdown()
175 s.completed = true
176 go b.forceRefreshIfLastUncompleted()
186177 }
187178 }:
188179 case <-b.done:
190181 }
191182
192183 // SetCurrent sets progress' current to an arbitrary value.
184 // Setting a negative value will cause a panic.
193185 func (b *Bar) SetCurrent(current int64) {
194186 select {
195187 case b.operateState <- func(s *bState) {
196 s.iterated = true
197 s.lastN = current - s.current
188 s.lastIncrement = current - s.current
198189 s.current = current
199 if s.total > 0 && s.current >= s.total {
190 if s.triggerComplete && s.current >= s.total {
200191 s.current = s.total
201 s.toComplete = true
202 go b.refreshTillShutdown()
192 s.completed = true
193 go b.forceRefreshIfLastUncompleted()
203194 }
204195 }:
205196 case <-b.done:
218209
219210 // IncrInt64 increments progress by amount of n.
220211 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
225218 s.current += n
226 if s.total > 0 && s.current >= s.total {
219 if s.triggerComplete && s.current >= s.total {
227220 s.current = s.total
228 s.toComplete = true
229 go b.refreshTillShutdown()
221 s.completed = true
222 go b.forceRefreshIfLastUncompleted()
230223 }
231224 }:
232225 case <-b.done:
240233 func (b *Bar) DecoratorEwmaUpdate(dur time.Duration) {
241234 select {
242235 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 }
247248 }
248249 }
249250
253254 func (b *Bar) DecoratorAverageAdjust(start time.Time) {
254255 select {
255256 case b.operateState <- func(s *bState) {
256 for _, d := range s.averageDecorators {
257 d.AverageAdjust(start)
258 }
257 s.decoratorAverageAdjust(start)
259258 }:
260259 case <-b.done:
261260 }
265264 // priority, i.e. bar will be on top. If you don't need to set priority
266265 // dynamically, better use BarPriority option.
267266 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.
278273 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
285282 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:
286307 }
287308 }
288309
289310 // Completed reports whether the bar is in completed state.
290311 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
294316 case <-b.done:
295317 return true
296318 }
303325 case op := <-b.operateState:
304326 op(s)
305327 case <-ctx.Done():
328 s.decoratorShutdownNotify()
306329 b.cacheState = s
307330 close(b.done)
308 // Notifying decorators about shutdown event
309 for _, sl := range s.shutdownListeners {
310 sl.Shutdown()
311 }
312331 return
313332 }
314333 }
315334 }
316335
317336 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)
325340 defer func() {
326341 // recovering if user defined decorator panics for example
327342 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}
332354 }
355 s.completeFlushed = s.completed
333356 }()
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}
342360 }:
343361 case <-b.done:
344362 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 }
354371 }
355372
356373 func (b *Bar) subscribeDecorators() {
368385 shutdownListeners = append(shutdownListeners, d)
369386 }
370387 })
371 b.operateState <- func(s *bState) {
388 b.hasEwmaDecorators = len(ewmaDecorators) != 0
389 select {
390 case b.operateState <- func(s *bState) {
372391 s.averageDecorators = averageDecorators
373392 s.ewmaDecorators = ewmaDecorators
374393 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 }
385415 }
386416 }
387417 }
388418
389419 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
393424 case <-b.done:
394425 return b.cacheState.wSyncTable()
395426 }
396427 }
397428
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
399433 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
403451 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)
426465 }
427466
428467 func (s *bState) wSyncTable() [][]chan int {
447486 return table
448487 }
449488
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,
456549 }
457550 }
458551
463556 return d
464557 }
465558
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 }
11
22 import (
33 "io"
4 "unicode/utf8"
54
6 "github.com/vbauerster/mpb/v5/decor"
7 "github.com/vbauerster/mpb/v5/internal"
5 "github.com/vbauerster/mpb/v7/decor"
86 )
97
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.
2210 //
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.
2413 //
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)
4516 }
4617
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
5827 }
5928
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)
7035 }
7136
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()
8143 }
8244
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()
8549 }
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 }
33 "bytes"
44 "io"
55
6 "github.com/vbauerster/mpb/v5/decor"
6 "github.com/vbauerster/mpb/v7/decor"
77 )
88
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.
1010 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 }
1120
1221 func (s *bState) addDecorators(dest *[]decor.Decorator, decorators ...decor.Decorator) {
1322 type mergeWrapper interface {
2433 // AppendDecorators let you inject decorators to the bar's right side.
2534 func AppendDecorators(decorators ...decor.Decorator) BarOption {
2635 return func(s *bState) {
27 s.addDecorators(&s.aDecorators, decorators...)
36 s.addDecorators(&s.aDecorators, skipNil(decorators)...)
2837 }
2938 }
3039
3140 // PrependDecorators let you inject decorators to the bar's left side.
3241 func PrependDecorators(decorators ...decor.Decorator) BarOption {
3342 return func(s *bState) {
34 s.addDecorators(&s.pDecorators, decorators...)
43 s.addDecorators(&s.pDecorators, skipNil(decorators)...)
3544 }
3645 }
3746
4554 // BarWidth sets bar width independent of the container.
4655 func BarWidth(width int) BarOption {
4756 return func(s *bState) {
48 s.width = width
57 s.reqWidth = width
4958 }
5059 }
5160
7685
7786 // BarFillerOnComplete replaces bar's filler with message, on complete event.
7887 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 })
82100 }
83101
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 }
92107 }
93108
94109 // BarPriority sets bar's priority. Zero is highest priority, i.e. bar
100115 }
101116 }
102117
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 {
107121 return nil
108122 }
109123 return func(s *bState) {
110 s.extender = makeExtFunc(extender)
124 s.extender = makeExtenderFunc(filler)
111125 }
112126 }
113127
114 func makeExtFunc(extender BarFiller) extFunc {
128 func makeExtenderFunc(filler BarFiller) extenderFunc {
115129 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"))
120133 }
121134 }
122135
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 {
125138 return func(s *bState) {
126139 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 }
144140 }
145141 }
146142
152148 }
153149 }
154150
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 {
200154 return option
201155 }
202156 return nil
203157 }
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 }
99 "time"
1010 "unicode/utf8"
1111
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"
1414 )
1515
1616 func TestBarCompleted(t *testing.T) {
17 p := New(WithOutput(ioutil.Discard))
17 p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(ioutil.Discard))
1818 total := 80
1919 bar := p.AddBar(int64(total))
2020
3232 }
3333
3434 func TestBarID(t *testing.T) {
35 p := New(WithOutput(ioutil.Discard))
35 p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(ioutil.Discard))
3636 total := 100
3737 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() {
4141 for i := 0; i < total; i++ {
4242 time.Sleep(50 * time.Millisecond)
4343 bar.Increment()
4444 }
45 }(total)
45 }()
4646
4747 gotID := bar.ID()
4848 if gotID != wantID {
5656 func TestBarSetRefill(t *testing.T) {
5757 var buf bytes.Buffer
5858
59 width := 100
60 p := New(WithOutput(&buf), WithWidth(width))
59 p := mpb.New(mpb.WithOutput(&buf), mpb.WithWidth(100))
6160
6261 total := 100
6362 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())
6766
6867 bar.SetRefill(int64(till))
6968 bar.IncrBy(till)
7675 p.Wait()
7776
7877 wantBar := fmt.Sprintf("[%s%s]",
79 strings.Repeat(string(refillRune), till-1),
78 strings.Repeat(refiller, till-1),
8079 strings.Repeat("=", total-till-1),
8180 )
8281
9089 func TestBarHas100PercentWithOnCompleteDecorator(t *testing.T) {
9190 var buf bytes.Buffer
9291
93 p := New(WithOutput(&buf))
92 p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(&buf))
9493
9594 total := 50
9695
9796 bar := p.AddBar(int64(total),
98 AppendDecorators(
97 mpb.AppendDecorators(
9998 decor.OnComplete(
10099 decor.Percentage(), "done",
101100 ),
118117 func TestBarHas100PercentWithBarRemoveOnComplete(t *testing.T) {
119118 var buf bytes.Buffer
120119
121 p := New(WithOutput(&buf))
120 p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(&buf))
122121
123122 total := 50
124123
125124 bar := p.AddBar(int64(total),
126 BarRemoveOnComplete(),
127 AppendDecorators(decor.Percentage()),
125 mpb.BarRemoveOnComplete(),
126 mpb.AppendDecorators(decor.Percentage()),
128127 )
129128
130129 for i := 0; i < total; i++ {
143142 func TestBarStyle(t *testing.T) {
144143 var buf bytes.Buffer
145144 customFormat := "╢▌▌░╟"
146 p := New(WithOutput(&buf))
145 runes := []rune(customFormat)
147146 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",
159164 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]),
162168 )
163169 got := string(getLastLine(buf.Bytes()))
164170
169175
170176 func TestBarPanicBeforeComplete(t *testing.T) {
171177 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 )
173183
174184 total := 100
175185 panicMsg := "Upps!!!"
176186 var pCount uint32
177187 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 {
180190 if st.Current >= 42 {
181191 atomic.AddUint32(&pCount, 1)
182192 return true
205215
206216 func TestBarPanicAfterComplete(t *testing.T) {
207217 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 )
209223
210224 total := 100
211225 panicMsg := "Upps!!!"
212226 var pCount uint32
213227 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 {
216230 if st.Completed {
217231 atomic.AddUint32(&pCount, 1)
218232 return true
229243
230244 p.Wait()
231245
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")
234248 }
235249
236250 barStr := buf.String()
239253 }
240254 }
241255
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 }
00 package mpb
11
22 import (
3 "io/ioutil"
3 "sync"
44 "testing"
5
6 "github.com/vbauerster/mpb/v5/decor"
75 )
86
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)
1511 }
1612
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)
2315 }
2416
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)
3119 }
3220
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()
3929 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()
4155 }
56 p.Wait()
4257 }
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 // Package cwriter is a console writer abstraction for the underlying OS.
1 package cwriter
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
22 import (
33 "bytes"
44 "errors"
5 "fmt"
65 "io"
76 "os"
8
9 "golang.org/x/crypto/ssh/terminal"
7 "strconv"
108 )
119
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")
1412
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 )
1618
1719 // Writer is a buffered the writer that updates the terminal. The
1820 // contents of writer will be flushed when Flush is called.
1921 type Writer struct {
2022 out io.Writer
2123 buf bytes.Buffer
22 lineCount int
23 fd uintptr
24 lines int
25 fd int
2426 isTerminal bool
2527 }
2628
2830 func New(out io.Writer) *Writer {
2931 w := &Writer{out: out}
3032 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)
3335 }
3436 return w
3537 }
3638
3739 // 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 }
4147 }
42 w.lineCount = lineCount
48 w.lines = lines
4349 _, err = w.buf.WriteTo(w.out)
4450 return
4551 }
6268
6369 // GetWidth returns width of underlying terminal.
6470 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
6873 }
69 return -1, NotATTY
74 tw, _, err := GetSize(w.fd)
75 return tw, err
7076 }
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 }
11
22 package cwriter
33
4 import "fmt"
4 import (
5 "golang.org/x/sys/unix"
6 )
57
6 func (w *Writer) clearLines() {
7 fmt.Fprintf(w.out, cuuAndEd, w.lineCount)
8 func (w *Writer) clearLines() error {
9 return w.ansiCuuAndEd()
810 }
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 }
22 package cwriter
33
44 import (
5 "fmt"
6 "syscall"
75 "unsafe"
6
7 "golang.org/x/sys/windows"
88 )
99
10 var kernel32 = syscall.NewLazyDLL("kernel32.dll")
10 var kernel32 = windows.NewLazySystemDLL("kernel32.dll")
1111
1212 var (
13 procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
1413 procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
1514 procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
16 procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
1715 )
1816
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
2251 }
2352
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
2965 }
3066
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
3772 }
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 }
00 package decor
11
22 // Any decorator displays text, that can be changed during decorator's
3 // lifetime via provided func call back.
3 // lifetime via provided DecorFunc.
44 //
5 // `f` call back which provides string to display
5 // `fn` DecorFunc callback
66 //
77 // `wcc` optional WC config
88 //
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}
1111 }
1212
1313 type any struct {
1414 WC
15 f func(*Statistics) string
15 fn DecorFunc
1616 }
1717
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))
2020 }
11
22 import (
33 "fmt"
4 "strings"
45 )
56
67 const (
3031 //
3132 // `unit` one of [0|UnitKiB|UnitKB] zero for no unit
3233 //
33 // `pairFmt` printf compatible verbs for current and total, like "%f" or "%d"
34 // `pairFmt` printf compatible verbs for current and total pair
3435 //
3536 // `wcc` optional WC config
3637 //
4243 // pairFmt="% d / % d" output: "1 MB / 12 MB"
4344 //
4445 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 }
22 import (
33 "fmt"
44 "time"
5 "unicode/utf8"
65
76 "github.com/acarl005/stripansi"
7 "github.com/mattn/go-runewidth"
88 )
99
1010 const (
4646 // Statistics consists of progress related statistics, that Decorator
4747 // may need.
4848 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
5356 }
5457
5558 // 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`.
5966 type Decorator interface {
6067 Configurator
6168 Synchronizer
62 Decor(*Statistics) string
69 Decor(Statistics) string
6370 }
71
72 // DecorFunc func type.
73 // To be used with `func Any`(DecorFunc, ...WC) Decorator`.
74 type DecorFunc func(Statistics) string
6475
6576 // Synchronizer interface.
6677 // All decorators implement this interface implicitly. Its Sync
116127 // W represents width and C represents bit set of width related config.
117128 // A decorator should embed WC, to enable width synchronization.
118129 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
123134 }
124135
125136 // FormatMsg formats final message according to WC.W and WC.C.
126137 // Should be called by any Decorator implementation.
127138 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
131142 if (wc.C & DSyncWidth) != 0 {
143 cellCount := stripWidth
132144 if (wc.C & DextraSpace) != 0 {
133 runeCount++
145 cellCount++
134146 }
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
140149 }
141 return fmt.Sprintf(format, msg)
150 return wc.fill(msg, maxCell+(pureWidth-stripWidth))
142151 }
143152
144153 // Init initializes width related config.
145154 func (wc *WC) Init() WC {
146 wc.dynFormat = "%%"
155 wc.fill = runewidth.FillLeft
147156 if (wc.C & DidentRight) != 0 {
148 wc.dynFormat += "-"
157 wc.fill = runewidth.FillRight
149158 }
150 wc.dynFormat += "%ds"
151159 if (wc.C & DSyncWidth) != 0 {
152160 // it's deliberate choice to override wsync on each Init() call,
153161 // 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")))
2018 package decor
2424 func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator {
2525 var msg string
2626 producer := chooseTimeProducer(style)
27 f := func(s *Statistics) string {
27 fn := func(s Statistics) string {
2828 if !s.Completed {
2929 msg = producer(time.Since(startTime))
3030 }
3131 return msg
3232 }
33 return Any(f, wcc...)
33 return Any(fn, wcc...)
3434 }
6262 producer func(time.Duration) string
6363 }
6464
65 func (d *movingAverageETA) Decor(s *Statistics) string {
65 func (d *movingAverageETA) Decor(s Statistics) string {
6666 v := math.Round(d.average.Value())
6767 remaining := time.Duration((s.Total - s.Current) * int64(v))
6868 if d.normalizer != nil {
116116 producer func(time.Duration) string
117117 }
118118
119 func (d *averageETA) Decor(s *Statistics) string {
119 func (d *averageETA) Decor(s Statistics) string {
120120 var remaining time.Duration
121121 if s.Current != 0 {
122122 durPerItem := float64(time.Since(d.startTime)) / float64(s.Current)
00 package decor
11
22 import (
3 "fmt"
43 "strings"
5 "unicode/utf8"
4
5 "github.com/acarl005/stripansi"
6 "github.com/mattn/go-runewidth"
67 )
78
89 // Merge wraps its decorator argument with intention to sync width
1516 // +----+--------+---------+--------+
1617 //
1718 func Merge(decorator Decorator, placeholders ...WC) Decorator {
19 if decorator == nil {
20 return nil
21 }
1822 if _, ok := decorator.Sync(); !ok || len(placeholders) == 0 {
1923 return decorator
2024 }
6367 return d.Decorator
6468 }
6569
66 func (d *mergeDecorator) Decor(s *Statistics) string {
70 func (d *mergeDecorator) Decor(s Statistics) string {
6771 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
6975 if (d.wc.C & DextraSpace) != 0 {
70 msgLen++
76 cellCount++
7177 }
7278
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)
7882
7983 var diff int
8084 for i := 1; i < len(d.placeHolders); i++ {
8690 width = 0
8791 }
8892 }
89 max = utf8.RuneCountInString(ph.FormatMsg(strings.Repeat(" ", width)))
93 max := runewidth.StringWidth(ph.FormatMsg(strings.Repeat(" ", width)))
9094 total += max
9195 diff = max - pw
9296 }
9397
9498 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))
97101 }
98102
99103 type placeHolderDecorator struct {
100104 WC
101105 }
102106
103 func (d *placeHolderDecorator) Decor(*Statistics) string {
107 func (d *placeHolderDecorator) Decor(Statistics) string {
104108 return ""
105109 }
77 // `wcc` optional WC config
88 //
99 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...)
1111 }
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 }
00 package decor
11
2 // OnComplete returns decorator, which wraps provided decorator, with
2 // OnComplete returns decorator, which wraps provided decorator with
33 // sole purpose to display provided message on complete event.
44 //
55 // `decorator` Decorator to wrap
77 // `message` message to display on complete event
88 //
99 func OnComplete(decorator Decorator, message string) Decorator {
10 if decorator == nil {
11 return nil
12 }
1013 d := &onCompleteWrapper{
1114 Decorator: decorator,
1215 msg: message,
2326 msg string
2427 }
2528
26 func (d *onCompleteWrapper) Decor(s *Statistics) string {
29 func (d *onCompleteWrapper) Decor(s Statistics) string {
2730 if s.Completed {
2831 wc := d.GetConf()
2932 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 }
11
22 import (
33 "fmt"
4 "io"
54 "strconv"
65
7 "github.com/vbauerster/mpb/v5/internal"
6 "github.com/vbauerster/mpb/v7/internal"
87 )
98
109 type percentageType float64
2322 }
2423 }
2524
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))
2827 if st.Flag(' ') {
29 io.WriteString(st, " ")
28 osw(" ")
3029 }
31 io.WriteString(st, "%")
30 osw("%")
3231 }
3332
3433 // Percentage returns percentage decorator. It's a wrapper of NewPercentage.
4948 if format == "" {
5049 format = "% d"
5150 }
52 f := func(s *Statistics) string {
51 f := func(s Statistics) string {
5352 p := internal.Percentage(s.Total, s.Current, 100)
5453 return fmt.Sprintf(format, percentageType(p))
5554 }
11
22 import (
33 "fmt"
4 "io"
5 "math"
64 "strconv"
75 )
86
4644 unit = _iMiB
4745 case self < _iTiB:
4846 unit = _iGiB
49 case self <= math.MaxInt64:
47 default:
5048 unit = _iTiB
5149 }
5250
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))
5553 if st.Flag(' ') {
56 io.WriteString(st, " ")
54 osw(" ")
5755 }
58 io.WriteString(st, unit.String())
56 osw(unit.String())
5957 }
6058
6159 const (
9593 unit = _MB
9694 case self < _TB:
9795 unit = _GB
98 case self <= math.MaxInt64:
96 default:
9997 unit = _TB
10098 }
10199
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))
104102 if st.Flag(' ') {
105 io.WriteString(st, " ")
103 osw(" ")
106104 }
107 io.WriteString(st, unit.String())
105 osw(unit.String())
108106 }
11
22 import (
33 "fmt"
4 "io"
54 "math"
65 "time"
76
2322
2423 func (self *speedFormatter) Format(st fmt.State, verb rune) {
2524 self.Formatter.Format(st, verb)
26 io.WriteString(st, "/s")
25 optimisticStringWriter(st)("/s")
2726 }
2827
2928 // EwmaSpeed exponential-weighted-moving-average based speed decorator.
7776 msg string
7877 }
7978
80 func (d *movingAverageSpeed) Decor(s *Statistics) string {
79 func (d *movingAverageSpeed) Decor(s Statistics) string {
8180 if !s.Completed {
8281 var speed float64
8382 if v := d.average.Value(); v > 0 {
139138 msg string
140139 }
141140
142 func (d *averageSpeed) Decor(s *Statistics) string {
141 func (d *averageSpeed) Decor(s Statistics) string {
143142 if !s.Completed {
144143 speed := float64(s.Current) / float64(time.Since(d.startTime))
145144 d.msg = d.producer(speed * 1e9)
121121 for _, tc := range cases {
122122 t.Run(tc.name, func(t *testing.T) {
123123 decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed))
124 stat := &Statistics{
124 stat := Statistics{
125125 Current: tc.current,
126126 }
127127 res := decor.Decor(stat)
249249 for _, tc := range cases {
250250 t.Run(tc.name, func(t *testing.T) {
251251 decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed))
252 stat := &Statistics{
252 stat := Statistics{
253253 Current: tc.current,
254254 }
255255 res := decor.Decor(stat)
1111 frames = defaultSpinnerStyle
1212 }
1313 var count uint
14 f := func(s *Statistics) string {
14 f := func(s Statistics) string {
1515 frame := frames[count%uint(len(frames))]
1616 count++
1717 return frame
33 "sync"
44 "testing"
55
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"
88 )
99
1010 func TestNameDecorator(t *testing.T) {
3131 }
3232
3333 for _, test := range tests {
34 got := test.decorator.Decor(new(decor.Statistics))
34 got := test.decorator.Decor(decor.Statistics{})
3535 if got != test.want {
3636 t.Errorf("Want: %q, Got: %q\n", test.want, got)
3737 }
3939 }
4040
4141 type step struct {
42 stat *decor.Statistics
42 stat decor.Statistics
4343 decorator decor.Decorator
4444 want string
4545 }
4949 testCases := [][]step{
5050 {
5151 {
52 &decor.Statistics{Total: 100, Current: 8},
52 decor.Statistics{Total: 100, Current: 8},
5353 decor.Percentage(decor.WCSyncWidth),
5454 "8 %",
5555 },
5656 {
57 &decor.Statistics{Total: 100, Current: 9},
57 decor.Statistics{Total: 100, Current: 9},
5858 decor.Percentage(decor.WCSyncWidth),
5959 "9 %",
6060 },
6161 },
6262 {
6363 {
64 &decor.Statistics{Total: 100, Current: 9},
64 decor.Statistics{Total: 100, Current: 9},
6565 decor.Percentage(decor.WCSyncWidth),
6666 " 9 %",
6767 },
6868 {
69 &decor.Statistics{Total: 100, Current: 10},
69 decor.Statistics{Total: 100, Current: 10},
7070 decor.Percentage(decor.WCSyncWidth),
7171 "10 %",
7272 },
7373 },
7474 {
7575 {
76 &decor.Statistics{Total: 100, Current: 9},
76 decor.Statistics{Total: 100, Current: 9},
7777 decor.Percentage(decor.WCSyncWidth),
7878 " 9 %",
7979 },
8080 {
81 &decor.Statistics{Total: 100, Current: 100},
81 decor.Statistics{Total: 100, Current: 100},
8282 decor.Percentage(decor.WCSyncWidth),
8383 "100 %",
8484 },
9393 testCases := [][]step{
9494 {
9595 {
96 &decor.Statistics{Total: 100, Current: 8},
96 decor.Statistics{Total: 100, Current: 8},
9797 decor.Percentage(decor.WCSyncWidthR),
9898 "8 %",
9999 },
100100 {
101 &decor.Statistics{Total: 100, Current: 9},
101 decor.Statistics{Total: 100, Current: 9},
102102 decor.Percentage(decor.WCSyncWidthR),
103103 "9 %",
104104 },
105105 },
106106 {
107107 {
108 &decor.Statistics{Total: 100, Current: 9},
108 decor.Statistics{Total: 100, Current: 9},
109109 decor.Percentage(decor.WCSyncWidthR),
110110 "9 % ",
111111 },
112112 {
113 &decor.Statistics{Total: 100, Current: 10},
113 decor.Statistics{Total: 100, Current: 10},
114114 decor.Percentage(decor.WCSyncWidthR),
115115 "10 %",
116116 },
117117 },
118118 {
119119 {
120 &decor.Statistics{Total: 100, Current: 9},
120 decor.Statistics{Total: 100, Current: 9},
121121 decor.Percentage(decor.WCSyncWidthR),
122122 "9 % ",
123123 },
124124 {
125 &decor.Statistics{Total: 100, Current: 100},
125 decor.Statistics{Total: 100, Current: 100},
126126 decor.Percentage(decor.WCSyncWidthR),
127127 "100 %",
128128 },
137137 testCases := [][]step{
138138 {
139139 {
140 &decor.Statistics{Total: 100, Current: 8},
140 decor.Statistics{Total: 100, Current: 8},
141141 decor.Percentage(decor.WCSyncSpace),
142142 " 8 %",
143143 },
144144 {
145 &decor.Statistics{Total: 100, Current: 9},
145 decor.Statistics{Total: 100, Current: 9},
146146 decor.Percentage(decor.WCSyncSpace),
147147 " 9 %",
148148 },
149149 },
150150 {
151151 {
152 &decor.Statistics{Total: 100, Current: 9},
152 decor.Statistics{Total: 100, Current: 9},
153153 decor.Percentage(decor.WCSyncSpace),
154154 " 9 %",
155155 },
156156 {
157 &decor.Statistics{Total: 100, Current: 10},
157 decor.Statistics{Total: 100, Current: 10},
158158 decor.Percentage(decor.WCSyncSpace),
159159 " 10 %",
160160 },
161161 },
162162 {
163163 {
164 &decor.Statistics{Total: 100, Current: 9},
164 decor.Statistics{Total: 100, Current: 9},
165165 decor.Percentage(decor.WCSyncSpace),
166166 " 9 %",
167167 },
168168 {
169 &decor.Statistics{Total: 100, Current: 100},
169 decor.Statistics{Total: 100, Current: 100},
170170 decor.Percentage(decor.WCSyncSpace),
171171 " 100 %",
172172 },
181181 t.Fail()
182182 }
183183
184 numBars := len(testCases[0])
185 var wg sync.WaitGroup
186184 for _, columnCase := range testCases {
185 mpb.SyncWidth(toSyncMatrix(columnCase))
186 numBars := len(columnCase)
187 gott := make([]chan string, numBars)
188 wg := new(sync.WaitGroup)
187189 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() {
193194 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
196198 }
197199 wg.Wait()
198200
55 "unicode/utf8"
66 )
77
8 func TestDraw(t *testing.T) {
8 func TestDrawDefault(t *testing.T) {
99 // key is termWidth
1010 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
1819 }{
1920 0: {
2021 {
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}",
22372 total: 60,
23373 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",
39380 total: 60,
40381 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}",
56404 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",
73412 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}",
90436 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",
107444 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: "[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]",
187598 },
188599 },
189600 100: {
190601 {
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: "[-----------------------------------------------------------<======++++++++++++++++++++++++++++++++]",
317762 },
318763 },
319764 }
320765
321766 var tmpBuf bytes.Buffer
322 for termWidth, cases := range testSuite {
767 for tw, cases := range testSuite {
323768 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
326771 s.total = tc.total
327772 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()
333779 }
334 tmpBuf.Reset()
335 tmpBuf.ReadFrom(s.draw(termWidth, newStatistics(s)))
336780 by := tmpBuf.Bytes()
337 by = by[:len(by)-1]
338781
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()
341785 }
342
343 got := string(by)
344786 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))
346788 }
347789 }
348790 }
349791 }
350792
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 },
3571214 }
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 }
3591241 }
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 }
66 "math/rand"
77 "time"
88
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"
1111 )
1212
1313 func Example() {
1616
1717 total := 100
1818 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("╟"),
2323 mpb.PrependDecorators(
2424 // display our name with one space on the right
2525 decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}),
7777 defer proxyReader.Close()
7878
7979 // and copy from reader, ignoring errors
80 io.Copy(ioutil.Discard, proxyReader)
80 _, _ = io.Copy(ioutil.Discard, proxyReader)
8181
8282 p.Wait()
8383 }
11
22 // make syncWidth func public in test
33 var SyncWidth = syncWidth
4 var MaxWidthDistributor = &maxWidthDistributor
0 module github.com/vbauerster/mpb/v5
0 module github.com/vbauerster/mpb/v7
11
22 require (
3 github.com/VividCortex/ewma v1.1.1
3 github.com/VividCortex/ewma v1.2.0
44 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
77 )
88
99 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=
22 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
33 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=
22 import "math"
33
44 // 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 {
66 if total <= 0 {
77 return 0
8 }
9 if current >= total {
10 return float64(width)
811 }
912 return float64(int64(width)*current) / float64(total)
1013 }
1114
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 {
1317 return math.Round(Percentage(total, current, width))
1418 }
33
44 func TestPercentage(t *testing.T) {
55 // key is barWidth
6 testSuite := map[int][]struct {
6 testSuite := map[uint][]struct {
77 name string
88 total int64
99 current int64
2020 {"t,c,e{100,50,50}", 100, 50, 50},
2121 {"t,c,e{100,99,99}", 100, 99, 99},
2222 {"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},
2524 {"t,c,e{120,0,0}", 120, 0, 0},
2625 {"t,c,e{120,10,8}", 120, 10, 8},
2726 {"t,c,e{120,15,13}", 120, 15, 13},
3231 {"t,c,e{120,118,98}", 120, 118, 98},
3332 {"t,c,e{120,119,99}", 120, 119, 99},
3433 {"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},
3735 },
3836 80: {
3937 {"t,c,e{-1,-1,0}", -1, -1, 0},
4644 {"t,c,e{100,50,40}", 100, 50, 40},
4745 {"t,c,e{100,99,79}", 100, 99, 79},
4846 {"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},
5148 {"t,c,e{120,0,0}", 120, 0, 0},
5249 {"t,c,e{120,10,7}", 120, 10, 7},
5350 {"t,c,e{120,15,10}", 120, 15, 10},
5855 {"t,c,e{120,118,79}", 120, 118, 79},
5956 {"t,c,e{120,119,79}", 120, 119, 79},
6057 {"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},
6359 },
6460 }
6561
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
-105
options.go less more
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 }
55 "context"
66 "fmt"
77 "io"
8 "io/ioutil"
9 "log"
8 "math"
109 "os"
1110 "sync"
1211 "time"
1312
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"
1615 )
1716
1817 const (
1918 // default RefreshRate
20 prr = 120 * time.Millisecond
21 // default width
22 pwidth = 80
19 prr = 150 * time.Millisecond
2320 )
2421
25 // Progress represents the container that renders Progress bars
22 // Progress represents a container that renders one or more progress
23 // bars.
2624 type Progress struct {
2725 ctx context.Context
2826 uwg *sync.WaitGroup
3230 done chan struct{}
3331 refreshCh chan time.Time
3432 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.
3837 type pState struct {
3938 bHeap priorityQueue
4039 heapUpdated bool
4140 pMatrix map[int][]chan int
4241 aMatrix map[int][]chan int
4342 barShutdownQueue []*Bar
44 barPopQueue []*Bar
4543
4644 // following are provided/overrided by user
4745 idCount int
48 width int
46 reqWidth int
4947 popCompleted bool
48 outputDiscarded bool
5049 rr time.Duration
5150 uwg *sync.WaitGroup
52 refreshSrc <-chan time.Time
51 externalRefresh <-chan interface{}
5352 renderDelay <-chan struct{}
5453 shutdownNotifier chan struct{}
5554 parkedBars map[*Bar]*Bar
6968 func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress {
7069 s := &pState{
7170 bHeap: priorityQueue{},
72 width: pwidth,
7371 rr: prr,
7472 parkedBars: make(map[*Bar]*Bar),
7573 output: os.Stdout,
76 debugOut: ioutil.Discard,
7774 }
7875
7976 for _, opt := range options {
8986 bwg: new(sync.WaitGroup),
9087 operateState: make(chan func(*pState)),
9188 done: make(chan struct{}),
92 dlogger: log.New(s.debugOut, "[mpb] ", log.Lshortfile),
9389 }
9490
9591 p.cwg.Add(1)
9793 return p
9894 }
9995
100 // AddBar creates a new progress bar and adds it to the rendering queue.
96 // AddBar creates a bar with default bar filler.
10197 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...)
108109 }
109110
110111 // 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).
112113 // Panics if *Progress instance is done, i.e. called after *Progress.Wait().
113114 func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) *Bar {
114115 if filler == nil {
115 filler = NewBarFiller(DefaultBarStyle, false)
116 filler = NopStyle().Build()
116117 }
117118 p.bwg.Add(1)
118119 result := make(chan *Bar)
152153 }
153154 }
154155
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) {
156175 select {
157176 case p.operateState <- func(s *pState) {
158177 if b.index < 0 {
165184 }
166185 }
167186
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.
174188 func (p *Progress) BarCount() int {
175 result := make(chan int, 1)
189 result := make(chan int)
176190 select {
177191 case p.operateState <- func(s *pState) { result <- s.bHeap.Len() }:
178192 return <-result
181195 }
182196 }
183197
184 // Wait waits far all bars to complete and finally shutdowns container.
198 // Wait waits for all bars to complete and finally shutdowns container.
185199 // After this method has been called, there is no way to reuse *Progress
186200 // instance.
187201 func (p *Progress) Wait() {
214228 op(s)
215229 case <-p.refreshCh:
216230 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 }
218239 }
219240 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 }
220253 return
221254 }
222255 }
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
223294 }
224295
225296 func (s *pState) render(cw *cwriter.Writer) error {
232303
233304 tw, err := cw.GetWidth()
234305 if err != nil {
235 tw = s.width
306 tw = s.reqWidth
236307 }
237308 for i := 0; i < s.bHeap.Len(); i++ {
238309 bar := s.bHeap[i]
243314 }
244315
245316 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())
248319 for s.bHeap.Len() > 0 {
249320 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 }
251326 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 {
255328 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
260340 }
261341
262342 for _, b := range s.barShutdownQueue {
266346 delete(s.parkedBars, b)
267347 b.toDrop = true
268348 }
349 if s.popCompleted && !b.noPop {
350 totalLines -= bm[b]
351 b.toDrop = true
352 }
269353 if b.toDrop {
270354 delete(bm, b)
271355 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 }
278356 }
279357 b.cancel()
280358 }
281359 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]
289360
290361 for b := range bm {
291362 heap.Push(&s.bHeap, b)
292363 }
293364
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)
322366 }
323367
324368 func (s *pState) updateSyncMatrix() {
341385
342386 func (s *pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState {
343387 bs := &bState{
388 id: s.idCount,
389 priority: s.idCount,
390 reqWidth: s.reqWidth,
344391 total: total,
345 baseF: extractBaseFiller(filler),
346392 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 },
350394 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
354399 }
355400
356401 for _, opt := range options {
359404 }
360405 }
361406
407 if bs.middleware != nil {
408 bs.filler = bs.middleware(filler)
409 bs.middleware = nil
410 }
411
362412 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 }
369419
370420 return bs
371421 }
372422
373423 func syncWidth(matrix map[int][]chan int) {
374424 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 }
88 "testing"
99 "time"
1010
11 "github.com/vbauerster/mpb/v5"
11 "github.com/vbauerster/mpb/v7"
12 "github.com/vbauerster/mpb/v7/decor"
1213 )
1314
1415 func init() {
2223 wg.Add(1)
2324 b := p.AddBar(100)
2425 go func() {
26 rng := rand.New(rand.NewSource(time.Now().UnixNano()))
2527 for i := 0; i < 100; i++ {
2628 if i == 33 {
2729 wg.Done()
2830 }
2931 b.Increment()
30 time.Sleep(randomDuration(100 * time.Millisecond))
32 time.Sleep((time.Duration(rng.Intn(10)+1) * (10 * time.Millisecond)) / 2)
3133 }
3234 }()
3335
4951 bars := make([]*mpb.Bar, 3)
5052 for i := 0; i < 3; i++ {
5153 b := p.AddBar(100)
52 bars[i] = b
54 rng := rand.New(rand.NewSource(time.Now().UnixNano()))
5355 go func(n int) {
5456 for i := 0; !b.Completed(); i++ {
5557 if n == 0 && i >= 33 {
5759 wg.Done()
5860 }
5961 b.Increment()
60 time.Sleep(randomDuration(100 * time.Millisecond))
62 time.Sleep((time.Duration(rng.Intn(10)+1) * (10 * time.Millisecond)) / 2)
6163 }
6264 }(i)
65 bars[i] = b
6366 }
6467
6568 wg.Wait()
6669 count := p.BarCount()
6770 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)
6972 }
7073 bars[1].Abort(true)
7174 bars[2].Abort(true)
7376 }
7477
7578 func TestWithContext(t *testing.T) {
79 shutdown := make(chan struct{})
7680 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))
8382
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))
87133 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 )
90140 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))
94155 }
95156 }()
96157 }
97158
98 time.Sleep(50 * time.Millisecond)
99 cancel()
159 go func() {
160 <-ready
161 p.Wait()
162 close(start)
163 }()
100164
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")
106180 }
107181 }
108182
1010 bar *Bar
1111 }
1212
13 func (x *proxyReader) Read(p []byte) (int, error) {
13 func (x proxyReader) Read(p []byte) (int, error) {
1414 n, err := x.ReadCloser.Read(p)
1515 x.bar.IncrBy(n)
1616 if err == io.EOF {
17 go x.bar.SetTotal(0, true)
17 go x.bar.SetTotal(-1, true)
1818 }
1919 return n, err
2020 }
2121
2222 type proxyWriterTo struct {
23 io.ReadCloser // *proxyReader
24 wt io.WriterTo
25 bar *Bar
23 proxyReader
24 wt io.WriterTo
2625 }
2726
28 func (x *proxyWriterTo) WriteTo(w io.Writer) (int64, error) {
27 func (x proxyWriterTo) WriteTo(w io.Writer) (int64, error) {
2928 n, err := x.wt.WriteTo(w)
3029 x.bar.IncrInt64(n)
3130 if err == io.EOF {
32 go x.bar.SetTotal(0, true)
31 go x.bar.SetTotal(-1, true)
3332 }
3433 return n, err
3534 }
3635
3736 type ewmaProxyReader struct {
38 io.ReadCloser // *proxyReader
39 bar *Bar
40 iT time.Time
37 proxyReader
4138 }
4239
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)
4543 if n > 0 {
46 x.bar.DecoratorEwmaUpdate(time.Since(x.iT))
47 x.iT = time.Now()
44 x.bar.DecoratorEwmaUpdate(time.Since(start))
4845 }
4946 return n, err
5047 }
5148
5249 type ewmaProxyWriterTo struct {
53 io.ReadCloser // *ewmaProxyReader
54 wt io.WriterTo // *proxyWriterTo
55 bar *Bar
56 iT time.Time
50 ewmaProxyReader
51 wt proxyWriterTo
5752 }
5853
59 func (x *ewmaProxyWriterTo) WriteTo(w io.Writer) (int64, error) {
54 func (x ewmaProxyWriterTo) WriteTo(w io.Writer) (int64, error) {
55 start := time.Now()
6056 n, err := x.wt.WriteTo(w)
6157 if n > 0 {
62 x.bar.DecoratorEwmaUpdate(time.Since(x.iT))
63 x.iT = time.Now()
58 x.bar.DecoratorEwmaUpdate(time.Since(start))
6459 }
6560 return n, err
6661 }
6762
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
7771 }
78 } else if isWriterTo {
79 rc = &proxyWriterTo{rc, wt, bar}
72 } else if b.hasEwmaDecorators {
73 rc = ewmaProxyReader{pr}
74 } else {
75 rc = pr
8076 }
8177 return rc
8278 }
66 "strings"
77 "testing"
88
9 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v7"
1010 )
1111
1212 const content = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
3232
3333 tReader := &testReader{strings.NewReader(content), false}
3434
35 bar := p.AddBar(int64(len(content)), mpb.TrimSpace())
35 bar := p.AddBar(int64(len(content)), mpb.BarFillerTrim())
3636
3737 var buf bytes.Buffer
3838 _, err := io.Copy(&buf, bar.ProxyReader(tReader))
6666 p := mpb.New(mpb.WithOutput(ioutil.Discard))
6767
6868 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}
7170
72 bar := p.AddBar(int64(len(content)), mpb.TrimSpace())
71 bar := p.AddBar(int64(len(content)), mpb.BarFillerTrim())
7372
7473 var buf bytes.Buffer
7574 _, err := io.Copy(&buf, bar.ProxyReader(tReader))
+0
-61
spinner_filler.go less more
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 }