Codebase list golang-github-vbauerster-mpb / 50e3d03
New upstream version 6.0.3 Reinhard Tartler 5 years ago
84 changed file(s) with 1833 addition(s) and 1357 deletion(s). Raw diff Collapse all Expand all
00 language: go
1 arch:
2 - amd64
3 - ppc64le
14
25 go:
36 - 1.14.x
00 # Multi Progress Bar
11
2 [![GoDoc](https://godoc.org/github.com/vbauerster/mpb?status.svg)](https://godoc.org/github.com/vbauerster/mpb)
2 [![GoDoc](https://pkg.go.dev/badge/github.com/vbauerster/mpb)](https://pkg.go.dev/github.com/vbauerster/mpb/v6)
33 [![Build Status](https://travis-ci.org/vbauerster/mpb.svg?branch=master)](https://travis-ci.org/vbauerster/mpb)
44 [![Go Report Card](https://goreportcard.com/badge/github.com/vbauerster/mpb)](https://goreportcard.com/report/github.com/vbauerster/mpb)
55
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/v6"
29 "github.com/vbauerster/mpb/v6/decor"
2930 )
3031
3132 func main() {
3536 total := 100
3637 name := "Single Bar:"
3738 // adding a single bar, which will inherit container's width
38 bar := p.AddBar(int64(total),
39 // override DefaultBarStyle, which is "[=>-]<+"
40 mpb.BarStyle("╢▌▌░╟"),
39 bar := p.Add(int64(total),
40 // progress bar filler with customized style
41 mpb.NewBarFiller("╢▌▌░╟"),
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
6567 // pass &wg (optional), so p will wait for it eventually
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
66 "sync"
77 "time"
88
9 "github.com/vbauerster/mpb/v5"
10 "github.com/vbauerster/mpb/v5/decor"
9 "github.com/vbauerster/mpb/v6"
10 "github.com/vbauerster/mpb/v6/decor"
1111 )
1212
1313 func main() {
1818
1919 for i := 0; i < numBars; i++ {
2020 name := fmt.Sprintf("Bar#%d:", i)
21 efn := func(w io.Writer, width int, s *decor.Statistics) {
21 efn := func(w io.Writer, _ int, s decor.Statistics) {
2222 if s.Completed {
2323 fmt.Fprintf(w, "Bar id: %d has been completed\n", s.ID)
2424 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
66 "sync"
77 "time"
88
9 "github.com/vbauerster/mpb/v5"
10 "github.com/vbauerster/mpb/v5/decor"
9 "github.com/vbauerster/mpb/v6"
10 "github.com/vbauerster/mpb/v6/decor"
1111 )
1212
1313 func main() {
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/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 p := mpb.New(mpb.WithWaitGroup(doneWg))
1919 numBars := 4
2020
2121 var bars []*mpb.Bar
4343 i := i
4444 go func() {
4545 task := fmt.Sprintf("Task#%02d:", i)
46 job := "\x1b[31;1;4minstalling\x1b[0m"
46 // ANSI escape sequences are not supported on Windows OS
47 job := "\x1b[31;1;4mつのだ☆HIRO\x1b[0m"
4748 // preparing delayed bars
4849 b := p.AddBar(rand.Int63n(101)+100,
4950 mpb.BarQueueAfter(bars[i]),
0 module github.com/vbauerster/mpb/_examples/decoratorsOnTop
1
2 go 1.14
3
4 require github.com/vbauerster/mpb/v6 v6.0.3
0 package main
1
2 import (
3 "io"
4 "math/rand"
5 "time"
6
7 "github.com/vbauerster/mpb/v6"
8 "github.com/vbauerster/mpb/v6/decor"
9 )
10
11 func main() {
12 p := mpb.New()
13
14 total := 100
15 bar := p.Add(int64(total), nil,
16 mpb.PrependDecorators(
17 decor.Name("Percentage: "),
18 decor.NewPercentage("%d"),
19 ),
20 mpb.AppendDecorators(
21 decor.Name("ETA: "),
22 decor.OnComplete(
23 decor.AverageETA(decor.ET_STYLE_GO), "done",
24 ),
25 ),
26 mpb.BarExtender(nlBarFiller(mpb.NewBarFiller("╢▌▌░╟"))),
27 )
28 // simulating some work
29 max := 100 * time.Millisecond
30 for i := 0; i < total; i++ {
31 time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10)
32 bar.Increment()
33 }
34 // wait for our bar to complete and flush
35 p.Wait()
36 }
37
38 func nlBarFiller(filler mpb.BarFiller) mpb.BarFiller {
39 return mpb.BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) {
40 filler.Fill(w, reqWidth, st)
41 w.Write([]byte("\n"))
42 })
43 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 func main() {
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),
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
44 "math/rand"
55 "time"
66
7 "github.com/vbauerster/mpb/v5"
8 "github.com/vbauerster/mpb/v5/decor"
7 "github.com/vbauerster/mpb/v6"
8 "github.com/vbauerster/mpb/v6/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/v6 v6.0.3
55 "io/ioutil"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 func main() {
1818 mpb.WithRefreshRate(180*time.Millisecond),
1919 )
2020
21 bar := p.AddBar(total, mpb.BarStyle("[=>-|"),
21 bar := p.Add(total,
22 mpb.NewBarFiller("[=>-|"),
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/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 func main() {
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 }
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 }
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 func main() {
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
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/v6"
10 "github.com/vbauerster/mpb/v6/decor"
1011 )
1112
1213 func main() {
1314 var wg sync.WaitGroup
1415 p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithDebugOutput(os.Stderr))
1516
16 wantPanic := "Some really long panic panic panic panic panic panic panic, really it is very long"
17 wantPanic := strings.Repeat("Panic ", 64)
1718 numBars := 3
1819 wg.Add(numBars)
1920
3435 }
3536
3637 func panicDecorator(name, panicMsg string) decor.Decorator {
37 return decor.Any(func(s *decor.Statistics) string {
38 if s.ID == 1 && s.Current >= 42 {
38 return decor.Any(func(st decor.Statistics) string {
39 if st.ID == 1 && st.Current >= 42 {
3940 panic(panicMsg)
4041 }
4142 return name
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
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/v6"
8 "github.com/vbauerster/mpb/v6/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/v6 v6.0.3
66 "sync"
77 "time"
88
9 "github.com/vbauerster/mpb/v5"
10 "github.com/vbauerster/mpb/v5/decor"
9 "github.com/vbauerster/mpb/v6"
10 "github.com/vbauerster/mpb/v6/decor"
1111 )
1212
1313 var quietMode bool
2222 // pass &wg (optional), so p will wait for it eventually
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
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 func main() {
2020 name := fmt.Sprintf("Bar#%d:", i)
2121 bar := p.AddBar(int64(total),
2222 mpb.BarID(i),
23 mpb.BarOptOn(mpb.BarRemoveOnComplete(), func() bool { return i == 0 }),
23 mpb.BarOptional(mpb.BarRemoveOnComplete(), i == 0),
2424 mpb.PrependDecorators(
2525 decor.Name(name),
2626 decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace),
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 func main() {
1818
1919 for i := 0; i < numBars; i++ {
2020 name := fmt.Sprintf("Bar#%d:", i)
21 bar := p.AddBar(int64(total),
21 bar := p.Add(int64(total),
2222 // reverse Bar#1
23 mpb.BarOptOn(mpb.BarReverse(), func() bool { return i == 1 }),
23 mpb.NewBarFillerPick("", i == 1),
2424 mpb.PrependDecorators(
2525 // simple name decorator
2626 decor.Name(name),
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
33 "math/rand"
44 "time"
55
6 "github.com/vbauerster/mpb/v5"
7 "github.com/vbauerster/mpb/v5/decor"
6 "github.com/vbauerster/mpb/v6"
7 "github.com/vbauerster/mpb/v6/decor"
88 )
99
1010 func main() {
1414 total := 100
1515 name := "Single Bar:"
1616 // adding a single bar, which will inherit container's width
17 bar := p.AddBar(int64(total),
18 // override DefaultBarStyle, which is "[=>-]<+"
19 mpb.BarStyle("╢▌▌░╟"),
17 bar := p.Add(int64(total),
18 // progress bar filler with customized style
19 mpb.NewBarFiller("╢▌▌░╟"),
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}),
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 func main() {
1313 var wg sync.WaitGroup
1414 p := mpb.New(
1515 mpb.WithWaitGroup(&wg),
16 mpb.WithWidth(13),
16 mpb.WithWidth(14),
1717 )
1818 total, numBars := 101, 3
1919 wg.Add(numBars)
20
21 spinnerStyle := []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}
2022
2123 for i := 0; i < numBars; i++ {
2224 name := fmt.Sprintf("Bar#%d:", i)
2325 var bar *mpb.Bar
2426 if i == 0 {
25 bar = p.AddBar(int64(total),
26 // override mpb.DefaultBarStyle, which is "[=>-]<+"
27 mpb.BarStyle("╢▌▌░╟"),
27 bar = p.Add(int64(total),
28 mpb.NewBarFiller("╢▌▌░╟"),
2829 mpb.PrependDecorators(
2930 // simple name decorator
3031 decor.Name(name),
3839 ),
3940 )
4041 } else {
41 bar = p.AddSpinner(int64(total), mpb.SpinnerOnMiddle,
42 // override mpb.DefaultSpinnerStyle
43 mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}),
42 bar = p.Add(int64(total),
43 mpb.NewSpinnerFiller(spinnerStyle, mpb.SpinnerOnMiddle),
4444 mpb.PrependDecorators(
4545 // simple name decorator
4646 decor.Name(name),
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 func main() {
11
22 go 1.14
33
4 require github.com/vbauerster/mpb/v5 v5.0.3
4 require github.com/vbauerster/mpb/v6 v6.0.3
55 "sync"
66 "time"
77
8 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v5/decor"
8 "github.com/vbauerster/mpb/v6"
9 "github.com/vbauerster/mpb/v6/decor"
1010 )
1111
1212 const (
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.10
6 github.com/vbauerster/mpb/v6 v6.0.3
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/v6"
12 "github.com/vbauerster/mpb/v6/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 }
+130
-114
bar.go less more
55 "fmt"
66 "io"
77 "log"
8 "runtime/debug"
89 "strings"
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/v6/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
5441 recoveredPanic interface{}
5542 }
5643
57 type extFunc func(in io.Reader, tw int, st *decor.Statistics) (out io.Reader, lines int)
58
44 type extenderFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int)
45
46 // bState is actual bar state. It gets passed to *Bar.serve(...) monitor
47 // goroutine.
5948 type bState struct {
60 baseF BarFiller
61 filler BarFiller
6249 id int
63 width int
50 priority int
51 reqWidth int
6452 total int64
6553 current int64
54 refill int64
6655 lastN int64
6756 iterated bool
6857 trimSpace bool
69 toComplete bool
58 completed bool
7059 completeFlushed bool
60 triggerComplete bool
61 dropOnComplete bool
7162 noPop bool
7263 aDecorators []decor.Decorator
7364 pDecorators []decor.Decorator
7566 ewmaDecorators []decor.EwmaDecorator
7667 shutdownListeners []decor.ShutdownListener
7768 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
69 filler BarFiller
70 middleware func(BarFiller) BarFiller
71 extender extenderFunc
72
8473 // runningBar is a key for *pState.parkedBars
8574 runningBar *Bar
8675
9887 noPop: bs.noPop,
9988 operateState: make(chan func(*bState)),
10089 frameCh: make(chan io.Reader, 1),
101 syncTableCh: make(chan [][]chan int),
90 syncTableCh: make(chan [][]chan int, 1),
10291 completed: make(chan bool, 1),
10392 done: make(chan struct{}),
10493 cancel: cancel,
140129 }
141130 }
142131
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.
132 // SetRefill sets refill flag with specified amount.
133 // The underlying BarFiller will change its visual representation, to
134 // indicate refill event. Refill event may be referred to some retry
135 // operation for example.
146136 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 }
137 select {
138 case b.operateState <- func(s *bState) {
139 s.refill = amount
140 }:
141 case <-b.done:
154142 }
155143 }
156144
157145 // TraverseDecorators traverses all available decorators and calls cb func on each.
158146 func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) {
159 b.operateState <- func(s *bState) {
147 select {
148 case b.operateState <- func(s *bState) {
160149 for _, decorators := range [...][]decor.Decorator{
161150 s.pDecorators,
162151 s.aDecorators,
165154 cb(extractBaseDecorator(d))
166155 }
167156 }
157 }:
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) {
163 // If total is less than or equal to zero 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
177168 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
175 s.completed = true
185176 go b.refreshTillShutdown()
186177 }
187178 }:
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) {
196188 s.iterated = true
197189 s.lastN = current - s.current
198190 s.current = current
199 if s.total > 0 && s.current >= s.total {
191 if s.triggerComplete && s.current >= s.total {
200192 s.current = s.total
201 s.toComplete = true
193 s.completed = true
202194 go b.refreshTillShutdown()
203195 }
204196 }:
223215 s.iterated = true
224216 s.lastN = n
225217 s.current += n
226 if s.total > 0 && s.current >= s.total {
218 if s.triggerComplete && s.current >= s.total {
227219 s.current = s.total
228 s.toComplete = true
220 s.completed = true
229221 go b.refreshTillShutdown()
230222 }
231223 }:
289281 // Completed reports whether the bar is in completed state.
290282 func (b *Bar) Completed() bool {
291283 select {
292 case b.operateState <- func(s *bState) { b.completed <- s.toComplete }:
284 case b.operateState <- func(s *bState) { b.completed <- s.completed }:
293285 return <-b.completed
294286 case <-b.done:
295287 return true
315307 }
316308
317309 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) {
310 select {
311 case b.operateState <- func(s *bState) {
312 stat := newStatistics(tw, s)
325313 defer func() {
326314 // recovering if user defined decorator panics for example
327315 if p := recover(); p != nil {
316 if b.recoveredPanic == nil {
317 s.extender = makePanicExtender(p)
318 b.toShutdown = !b.toShutdown
319 b.recoveredPanic = p
320 }
321 frame, lines := s.extender(nil, s.reqWidth, stat)
322 b.extendedLines = lines
323 b.frameCh <- frame
328324 b.dlogger.Println(p)
329 b.recoveredPanic = p
330 b.toShutdown = !s.completeFlushed
331 b.frameCh <- b.panicToFrame(tw)
332325 }
326 s.completeFlushed = s.completed
333327 }()
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
328 frame, lines := s.extender(s.draw(stat), s.reqWidth, stat)
329 b.extendedLines = lines
330 b.toShutdown = s.completed && !s.completeFlushed
341331 b.frameCh <- frame
342332 }:
343333 case <-b.done:
344334 s := b.cacheState
345 st := newStatistics(s)
346 frame := s.draw(tw, st)
347 frame, b.extendedLines = s.extender(frame, tw, st)
335 stat := newStatistics(tw, s)
336 var r io.Reader
337 if b.recoveredPanic == nil {
338 r = s.draw(stat)
339 }
340 frame, lines := s.extender(r, s.reqWidth, stat)
341 b.extendedLines = lines
348342 b.frameCh <- frame
349343 }
350 }
351
352 func (b *Bar) panicToFrame(termWidth int) io.Reader {
353 return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%dv\n", termWidth), b.recoveredPanic))
354344 }
355345
356346 func (b *Bar) subscribeDecorators() {
368358 shutdownListeners = append(shutdownListeners, d)
369359 }
370360 })
371 b.operateState <- func(s *bState) {
361 select {
362 case b.operateState <- func(s *bState) {
372363 s.averageDecorators = averageDecorators
373364 s.ewmaDecorators = ewmaDecorators
374365 s.shutdownListeners = shutdownListeners
375 }
376 b.hasEwmaDecorators = len(ewmaDecorators) != 0
366 }:
367 b.hasEwmaDecorators = len(ewmaDecorators) != 0
368 case <-b.done:
369 }
377370 }
378371
379372 func (b *Bar) refreshTillShutdown() {
395388 }
396389 }
397390
398 func (s *bState) draw(termWidth int, stat *decor.Statistics) io.Reader {
391 func (s *bState) draw(stat decor.Statistics) io.Reader {
392 if !s.trimSpace {
393 stat.AvailableWidth -= 2
394 s.bufB.WriteByte(' ')
395 defer s.bufB.WriteByte(' ')
396 }
397
398 nlr := strings.NewReader("\n")
399 tw := stat.AvailableWidth
399400 for _, d := range s.pDecorators {
400 s.bufP.WriteString(d.Decor(stat))
401 }
402
401 str := d.Decor(stat)
402 stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str))
403 s.bufP.WriteString(str)
404 }
405 if stat.AvailableWidth <= 0 {
406 trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(s.bufP.String()), tw, "…"))
407 s.bufP.Reset()
408 return io.MultiReader(trunc, s.bufB, nlr)
409 }
410
411 tw = stat.AvailableWidth
403412 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)
413 str := d.Decor(stat)
414 stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str))
415 s.bufA.WriteString(str)
416 }
417 if stat.AvailableWidth <= 0 {
418 trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(s.bufA.String()), tw, "…"))
419 s.bufA.Reset()
420 return io.MultiReader(s.bufP, s.bufB, trunc, nlr)
421 }
422
423 s.filler.Fill(s.bufB, s.reqWidth, stat)
424
425 return io.MultiReader(s.bufP, s.bufB, s.bufA, nlr)
426426 }
427427
428428 func (s *bState) wSyncTable() [][]chan int {
447447 return table
448448 }
449449
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,
450 func newStatistics(tw int, s *bState) decor.Statistics {
451 return decor.Statistics{
452 ID: s.id,
453 AvailableWidth: tw,
454 Total: s.total,
455 Current: s.current,
456 Refill: s.refill,
457 Completed: s.completeFlushed,
456458 }
457459 }
458460
473475 d.EwmaUpdate(s.lastN, dur)
474476 }
475477 }
478
479 func makePanicExtender(p interface{}) extenderFunc {
480 pstr := fmt.Sprint(p)
481 stack := debug.Stack()
482 stackLines := bytes.Count(stack, []byte("\n"))
483 return func(_ io.Reader, _ int, st decor.Statistics) (io.Reader, int) {
484 mr := io.MultiReader(
485 strings.NewReader(runewidth.Truncate(pstr, st.AvailableWidth, "…")),
486 strings.NewReader(fmt.Sprintf("\n%#v\n", st)),
487 bytes.NewReader(stack),
488 )
489 return mr, stackLines + 1
490 }
491 }
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/v6/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
14 // Default implementations can be obtained via:
2615 //
27 // '3rd rune' stands for tip rune
16 // func NewBarFiller(style string) BarFiller
17 // func NewBarFillerRev(style string) BarFiller
18 // func NewBarFillerPick(style string, rev bool) BarFiller
19 // func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller
2820 //
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)
21 type BarFiller interface {
22 Fill(w io.Writer, reqWidth int, stat decor.Statistics)
4523 }
4624
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
25 // BarFillerFunc is function type adapter to convert function into BarFiller.
26 type BarFillerFunc func(w io.Writer, reqWidth int, stat decor.Statistics)
27
28 func (f BarFillerFunc) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
29 f(w, reqWidth, stat)
5830 }
59
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)
70 }
71
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
81 }
82
83 func (s *barFiller) SetRefill(amount int64) {
84 s.refill = amount
85 }
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 "bytes"
4 "io"
5 "unicode/utf8"
6
7 "github.com/mattn/go-runewidth"
8 "github.com/rivo/uniseg"
9 "github.com/vbauerster/mpb/v6/decor"
10 "github.com/vbauerster/mpb/v6/internal"
11 )
12
13 const (
14 rLeft = iota
15 rFill
16 rTip
17 rSpace
18 rRight
19 rRevTip
20 rRefill
21 )
22
23 // BarDefaultStyle is a style for rendering a progress bar.
24 // It consist of 7 ordered runes:
25 //
26 // '1st rune' stands for left boundary rune
27 //
28 // '2nd rune' stands for fill rune
29 //
30 // '3rd rune' stands for tip rune
31 //
32 // '4th rune' stands for space rune
33 //
34 // '5th rune' stands for right boundary rune
35 //
36 // '6th rune' stands for reverse tip rune
37 //
38 // '7th rune' stands for refill rune
39 //
40 const BarDefaultStyle string = "[=>-]<+"
41
42 type barFiller struct {
43 format [][]byte
44 rwidth []int
45 tip []byte
46 refill int64
47 reverse bool
48 flush func(io.Writer, *space, [][]byte)
49 }
50
51 type space struct {
52 space []byte
53 rwidth int
54 count int
55 }
56
57 // NewBarFiller returns a BarFiller implementation which renders a
58 // progress bar in regular direction. If style is empty string,
59 // BarDefaultStyle is applied. To be used with `*Progress.Add(...)
60 // *Bar` method.
61 func NewBarFiller(style string) BarFiller {
62 return newBarFiller(style, false)
63 }
64
65 // NewBarFillerRev returns a BarFiller implementation which renders a
66 // progress bar in reverse direction. If style is empty string,
67 // BarDefaultStyle is applied. To be used with `*Progress.Add(...)
68 // *Bar` method.
69 func NewBarFillerRev(style string) BarFiller {
70 return newBarFiller(style, true)
71 }
72
73 // NewBarFillerPick pick between regular and reverse BarFiller implementation
74 // based on rev param. To be used with `*Progress.Add(...) *Bar` method.
75 func NewBarFillerPick(style string, rev bool) BarFiller {
76 return newBarFiller(style, rev)
77 }
78
79 func newBarFiller(style string, rev bool) BarFiller {
80 bf := &barFiller{
81 format: make([][]byte, len(BarDefaultStyle)),
82 rwidth: make([]int, len(BarDefaultStyle)),
83 reverse: rev,
84 }
85 bf.parse(BarDefaultStyle)
86 if style != "" && style != BarDefaultStyle {
87 bf.parse(style)
88 }
89 return bf
90 }
91
92 func (s *barFiller) parse(style string) {
93 if !utf8.ValidString(style) {
94 panic("invalid bar style")
95 }
96 srcFormat := make([][]byte, len(BarDefaultStyle))
97 srcRwidth := make([]int, len(BarDefaultStyle))
98 i := 0
99 for gr := uniseg.NewGraphemes(style); i < len(BarDefaultStyle) && gr.Next(); i++ {
100 srcFormat[i] = gr.Bytes()
101 srcRwidth[i] = runewidth.StringWidth(gr.Str())
102 }
103 copy(s.format, srcFormat[:i])
104 copy(s.rwidth, srcRwidth[:i])
105 if s.reverse {
106 s.tip = s.format[rRevTip]
107 s.flush = reverseFlush
108 } else {
109 s.tip = s.format[rTip]
110 s.flush = regularFlush
111 }
112 }
113
114 func (s *barFiller) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
115 width := internal.CheckRequestedWidth(reqWidth, stat.AvailableWidth)
116 brackets := s.rwidth[rLeft] + s.rwidth[rRight]
117 if width < brackets {
118 return
119 }
120 // don't count brackets as progress
121 width -= brackets
122
123 w.Write(s.format[rLeft])
124 defer w.Write(s.format[rRight])
125
126 cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width))
127 space := &space{
128 space: s.format[rSpace],
129 rwidth: s.rwidth[rSpace],
130 count: width - cwidth,
131 }
132
133 index, refill := 0, 0
134 bb := make([][]byte, cwidth)
135
136 if cwidth > 0 && cwidth != width {
137 bb[index] = s.tip
138 cwidth -= s.rwidth[rTip]
139 index++
140 }
141
142 if stat.Refill > 0 {
143 refill = int(internal.PercentageRound(stat.Total, int64(stat.Refill), width))
144 if refill > cwidth {
145 refill = cwidth
146 }
147 cwidth -= refill
148 }
149
150 for cwidth > 0 {
151 bb[index] = s.format[rFill]
152 cwidth -= s.rwidth[rFill]
153 index++
154 }
155
156 for refill > 0 {
157 bb[index] = s.format[rRefill]
158 refill -= s.rwidth[rRefill]
159 index++
160 }
161
162 if cwidth+refill < 0 || space.rwidth > 1 {
163 buf := new(bytes.Buffer)
164 s.flush(buf, space, bb[:index])
165 io.WriteString(w, runewidth.Truncate(buf.String(), width, "…"))
166 return
167 }
168
169 s.flush(w, space, bb)
170 }
171
172 func regularFlush(w io.Writer, space *space, bb [][]byte) {
173 for i := len(bb) - 1; i >= 0; i-- {
174 w.Write(bb[i])
175 }
176 for space.count > 0 {
177 w.Write(space.space)
178 space.count -= space.rwidth
179 }
180 }
181
182 func reverseFlush(w io.Writer, space *space, bb [][]byte) {
183 for space.count > 0 {
184 w.Write(space.space)
185 space.count -= space.rwidth
186 }
187 for i := 0; i < len(bb); i++ {
188 w.Write(bb[i])
189 }
190 }
0 package mpb
1
2 import (
3 "io"
4 "strings"
5
6 "github.com/mattn/go-runewidth"
7 "github.com/vbauerster/mpb/v6/decor"
8 "github.com/vbauerster/mpb/v6/internal"
9 )
10
11 // SpinnerAlignment enum.
12 type SpinnerAlignment int
13
14 // SpinnerAlignment kinds.
15 const (
16 SpinnerOnLeft SpinnerAlignment = iota
17 SpinnerOnMiddle
18 SpinnerOnRight
19 )
20
21 // SpinnerDefaultStyle is a style for rendering a spinner.
22 var SpinnerDefaultStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
23
24 type spinnerFiller struct {
25 frames []string
26 count uint
27 alignment SpinnerAlignment
28 }
29
30 // NewSpinnerFiller returns a BarFiller implementation which renders
31 // a spinner. If style is nil or zero length, SpinnerDefaultStyle is
32 // applied. To be used with `*Progress.Add(...) *Bar` method.
33 func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller {
34 if len(style) == 0 {
35 style = SpinnerDefaultStyle
36 }
37 filler := &spinnerFiller{
38 frames: style,
39 alignment: alignment,
40 }
41 return filler
42 }
43
44 func (s *spinnerFiller) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
45 width := internal.CheckRequestedWidth(reqWidth, stat.AvailableWidth)
46
47 frame := s.frames[s.count%uint(len(s.frames))]
48 frameWidth := runewidth.StringWidth(frame)
49
50 if width < frameWidth {
51 return
52 }
53
54 switch rest := width - frameWidth; s.alignment {
55 case SpinnerOnLeft:
56 io.WriteString(w, frame+strings.Repeat(" ", rest))
57 case SpinnerOnMiddle:
58 str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2)
59 io.WriteString(w, str)
60 case SpinnerOnRight:
61 io.WriteString(w, strings.Repeat(" ", rest)+frame)
62 }
63 s.count++
64 }
33 "bytes"
44 "io"
55
6 "github.com/vbauerster/mpb/v5/decor"
6 "github.com/vbauerster/mpb/v6/decor"
7 "github.com/vbauerster/mpb/v6/internal"
78 )
89
9 // BarOption is a function option which changes the default behavior of a bar.
10 // BarOption is a func option to alter default behavior of a bar.
1011 type BarOption func(*bState)
1112
1213 func (s *bState) addDecorators(dest *[]decor.Decorator, decorators ...decor.Decorator) {
4546 // BarWidth sets bar width independent of the container.
4647 func BarWidth(width int) BarOption {
4748 return func(s *bState) {
48 s.width = width
49 s.reqWidth = width
4950 }
5051 }
5152
7677
7778 // BarFillerOnComplete replaces bar's filler with message, on complete event.
7879 func BarFillerOnComplete(message string) BarOption {
79 return func(s *bState) {
80 s.filler = makeBarFillerOnComplete(s.baseF, message)
81 }
80 return BarFillerMiddleware(func(base BarFiller) BarFiller {
81 return BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) {
82 if st.Completed {
83 io.WriteString(w, message)
84 } else {
85 base.Fill(w, reqWidth, st)
86 }
87 })
88 })
8289 }
8390
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 })
91 // BarFillerMiddleware provides a way to augment the underlying BarFiller.
92 func BarFillerMiddleware(middle func(BarFiller) BarFiller) BarOption {
93 return func(s *bState) {
94 s.middleware = middle
95 }
9296 }
9397
9498 // BarPriority sets bar's priority. Zero is highest priority, i.e. bar
100104 }
101105 }
102106
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 {
107 // BarExtender provides a way to extend bar to the next new line.
108 func BarExtender(filler BarFiller) BarOption {
109 if filler == nil {
107110 return nil
108111 }
109112 return func(s *bState) {
110 s.extender = makeExtFunc(extender)
113 s.extender = makeExtenderFunc(filler)
111114 }
112115 }
113116
114 func makeExtFunc(extender BarFiller) extFunc {
117 func makeExtenderFunc(filler BarFiller) extenderFunc {
115118 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)
119 return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) {
120 filler.Fill(buf, reqWidth, st)
121 return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n"))
120122 }
121123 }
122124
123 // TrimSpace trims bar's edge spaces.
124 func TrimSpace() BarOption {
125 // BarFillerTrim removes leading and trailing space around the underlying BarFiller.
126 func BarFillerTrim() BarOption {
125127 return func(s *bState) {
126128 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 }
144129 }
145130 }
146131
152137 }
153138 }
154139
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 }
140 // BarOptional will invoke provided option only when pick is true.
141 func BarOptional(option BarOption, pick bool) BarOption {
142 return BarOptOn(option, internal.Predicate(pick))
165143 }
166144
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() {
145 // BarOptOn will invoke provided option only when higher order predicate
146 // evaluates to true.
147 func BarOptOn(option BarOption, predicate func() bool) BarOption {
148 if predicate() {
200149 return option
201150 }
202151 return nil
99 "time"
1010 "unicode/utf8"
1111
12 . "github.com/vbauerster/mpb/v5"
13 "github.com/vbauerster/mpb/v5/decor"
12 "github.com/vbauerster/mpb/v6"
13 "github.com/vbauerster/mpb/v6/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 refillRune, _ := utf8.DecodeLastRuneInString(mpb.BarDefaultStyle)
64
65 bar := p.AddBar(int64(total), mpb.BarFillerTrim())
6766
6867 bar.SetRefill(int64(till))
6968 bar.IncrBy(till)
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))
147145 total := 80
148 bar := p.AddBar(int64(total), BarStyle(customFormat), TrimSpace())
146 p := mpb.New(mpb.WithWidth(total), mpb.WithOutput(&buf))
147 bar := p.Add(int64(total), mpb.NewBarFiller(customFormat), mpb.BarFillerTrim())
149148
150149 for i := 0; i < total; i++ {
151150 bar.Increment()
169168
170169 func TestBarPanicBeforeComplete(t *testing.T) {
171170 var buf bytes.Buffer
172 p := New(WithDebugOutput(&buf), WithOutput(ioutil.Discard))
171 p := mpb.New(
172 mpb.WithWidth(80),
173 mpb.WithDebugOutput(&buf),
174 mpb.WithOutput(ioutil.Discard),
175 )
173176
174177 total := 100
175178 panicMsg := "Upps!!!"
176179 var pCount uint32
177180 bar := p.AddBar(int64(total),
178 PrependDecorators(panicDecorator(panicMsg,
179 func(st *decor.Statistics) bool {
181 mpb.PrependDecorators(panicDecorator(panicMsg,
182 func(st decor.Statistics) bool {
180183 if st.Current >= 42 {
181184 atomic.AddUint32(&pCount, 1)
182185 return true
205208
206209 func TestBarPanicAfterComplete(t *testing.T) {
207210 var buf bytes.Buffer
208 p := New(WithDebugOutput(&buf), WithOutput(ioutil.Discard))
211 p := mpb.New(
212 mpb.WithWidth(80),
213 mpb.WithDebugOutput(&buf),
214 mpb.WithOutput(ioutil.Discard),
215 )
209216
210217 total := 100
211218 panicMsg := "Upps!!!"
212219 var pCount uint32
213220 bar := p.AddBar(int64(total),
214 PrependDecorators(panicDecorator(panicMsg,
215 func(st *decor.Statistics) bool {
221 mpb.PrependDecorators(panicDecorator(panicMsg,
222 func(st decor.Statistics) bool {
216223 if st.Completed {
217224 atomic.AddUint32(&pCount, 1)
218225 return true
229236
230237 p.Wait()
231238
232 if pCount != 1 {
233 t.Errorf("Decor called after panic %d times\n", pCount-1)
239 if pCount > 2 {
240 t.Error("Decor called after panic more than 2 times\n")
234241 }
235242
236243 barStr := buf.String()
239246 }
240247 }
241248
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 }
249 func panicDecorator(panicMsg string, cond func(decor.Statistics) bool) decor.Decorator {
250 return decor.Any(func(st decor.Statistics) string {
251 if cond(st) {
252 panic(panicMsg)
253 }
254 return ""
255 })
256 }
33 "io/ioutil"
44 "testing"
55
6 "github.com/vbauerster/mpb/v5/decor"
6 "github.com/vbauerster/mpb/v6/decor"
77 )
88
99 func BenchmarkIncrSingleBar(b *testing.B) {
10 p := New(WithOutput(ioutil.Discard))
10 p := New(WithOutput(ioutil.Discard), WithWidth(80))
1111 bar := p.AddBar(int64(b.N))
1212 for i := 0; i < b.N; i++ {
1313 bar.Increment()
1515 }
1616
1717 func BenchmarkIncrSingleBarWhileIsNotCompleted(b *testing.B) {
18 p := New(WithOutput(ioutil.Discard))
18 p := New(WithOutput(ioutil.Discard), WithWidth(80))
1919 bar := p.AddBar(int64(b.N))
2020 for !bar.Completed() {
2121 bar.Increment()
2323 }
2424
2525 func BenchmarkIncrSingleBarWithNameDecorator(b *testing.B) {
26 p := New(WithOutput(ioutil.Discard))
26 p := New(WithOutput(ioutil.Discard), WithWidth(80))
2727 bar := p.AddBar(int64(b.N), PrependDecorators(decor.Name("test")))
2828 for i := 0; i < b.N; i++ {
2929 bar.Increment()
3131 }
3232
3333 func BenchmarkIncrSingleBarWithNameAndEwmaETADecorator(b *testing.B) {
34 p := New(WithOutput(ioutil.Discard))
34 p := New(WithOutput(ioutil.Discard), WithWidth(80))
3535 bar := p.AddBar(int64(b.N),
3636 PrependDecorators(decor.Name("test")),
3737 AppendDecorators(decor.EwmaETA(decor.ET_STYLE_GO, 60)),
0 package mpb
1
2 import (
3 "io"
4 "io/ioutil"
5 "sync"
6 "time"
7
8 "github.com/vbauerster/mpb/v6/internal"
9 )
10
11 // ContainerOption is a func option to alter default behavior of a bar
12 // container. Container term refers to a Progress struct which can
13 // hold one or more Bars.
14 type ContainerOption func(*pState)
15
16 // WithWaitGroup provides means to have a single joint point. If
17 // *sync.WaitGroup is provided, you can safely call just p.Wait()
18 // without calling Wait() on provided *sync.WaitGroup. Makes sense
19 // when there are more than one bar to render.
20 func WithWaitGroup(wg *sync.WaitGroup) ContainerOption {
21 return func(s *pState) {
22 s.uwg = wg
23 }
24 }
25
26 // WithWidth sets container width. If not set it defaults to terminal
27 // width. A bar added to the container will inherit its width, unless
28 // overridden by `func BarWidth(int) BarOption`.
29 func WithWidth(width int) ContainerOption {
30 return func(s *pState) {
31 s.reqWidth = width
32 }
33 }
34
35 // WithRefreshRate overrides default 120ms refresh rate.
36 func WithRefreshRate(d time.Duration) ContainerOption {
37 return func(s *pState) {
38 s.rr = d
39 }
40 }
41
42 // WithManualRefresh disables internal auto refresh time.Ticker.
43 // Refresh will occur upon receive value from provided ch.
44 func WithManualRefresh(ch <-chan interface{}) ContainerOption {
45 return func(s *pState) {
46 s.externalRefresh = ch
47 }
48 }
49
50 // WithRenderDelay delays rendering. By default rendering starts as
51 // soon as bar is added, with this option it's possible to delay
52 // rendering process by keeping provided chan unclosed. In other words
53 // rendering will start as soon as provided chan is closed.
54 func WithRenderDelay(ch <-chan struct{}) ContainerOption {
55 return func(s *pState) {
56 s.renderDelay = ch
57 }
58 }
59
60 // WithShutdownNotifier provided chanel will be closed, after all bars
61 // have been rendered.
62 func WithShutdownNotifier(ch chan struct{}) ContainerOption {
63 return func(s *pState) {
64 s.shutdownNotifier = ch
65 }
66 }
67
68 // WithOutput overrides default os.Stdout output. Setting it to nil
69 // will effectively disable auto refresh rate and discard any output,
70 // useful if you want to disable progress bars with little overhead.
71 func WithOutput(w io.Writer) ContainerOption {
72 return func(s *pState) {
73 if w == nil {
74 s.output = ioutil.Discard
75 s.outputDiscarded = true
76 return
77 }
78 s.output = w
79 }
80 }
81
82 // WithDebugOutput sets debug output.
83 func WithDebugOutput(w io.Writer) ContainerOption {
84 if w == nil {
85 return nil
86 }
87 return func(s *pState) {
88 s.debugOut = w
89 }
90 }
91
92 // PopCompletedMode will pop and stop rendering completed bars.
93 func PopCompletedMode() ContainerOption {
94 return func(s *pState) {
95 s.popCompleted = true
96 }
97 }
98
99 // ContainerOptional will invoke provided option only when pick is true.
100 func ContainerOptional(option ContainerOption, pick bool) ContainerOption {
101 return ContainerOptOn(option, internal.Predicate(pick))
102 }
103
104 // ContainerOptOn will invoke provided option only when higher order
105 // predicate evaluates to true.
106 func ContainerOptOn(option ContainerOption, predicate func() bool) ContainerOption {
107 if predicate() {
108 return option
109 }
110 return nil
111 }
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.lineCount = 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
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.
2022 out io.Writer
2123 buf bytes.Buffer
2224 lineCount int
23 fd uintptr
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.
3840 func (w *Writer) Flush(lineCount int) (err error) {
41 // some terminals interpret 'cursor up 0' as 'cursor up 1'
3942 if w.lineCount > 0 {
40 w.clearLines()
43 err = w.clearLines()
44 if err != nil {
45 return
46 }
4147 }
4248 w.lineCount = lineCount
4349 _, err = w.buf.WriteTo(w.out)
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() (err error) {
79 buf := make([]byte, 8)
80 buf = strconv.AppendInt(buf[:copy(buf, escOpen)], int64(w.lineCount), 10)
81 _, err = w.out.Write(append(buf, cuuAndEd...))
82 return
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.lineCount)
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.lineCount)
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
5355 }
5456
5557 // 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.
58 // Most of the time there is no need to implement this interface
59 // manually, as decor package already provides a wide range of decorators
60 // which implement this interface. If however built-in decorators don't
61 // meet your needs, you're free to implement your own one by implementing
62 // this particular interface. The easy way to go is to convert a
63 // `DecorFunc` into a `Decorator` interface by using provided
64 // `func Any(DecorFunc, ...WC) Decorator`.
5965 type Decorator interface {
6066 Configurator
6167 Synchronizer
62 Decor(*Statistics) string
68 Decor(Statistics) string
6369 }
70
71 // DecorFunc func type.
72 // To be used with `func Any`(DecorFunc, ...WC) Decorator`.
73 type DecorFunc func(Statistics) string
6474
6575 // Synchronizer interface.
6676 // All decorators implement this interface implicitly. Its Sync
116126 // W represents width and C represents bit set of width related config.
117127 // A decorator should embed WC, to enable width synchronization.
118128 type WC struct {
119 W int
120 C int
121 dynFormat string
122 wsync chan int
129 W int
130 C int
131 fill func(s string, w int) string
132 wsync chan int
123133 }
124134
125135 // FormatMsg formats final message according to WC.W and WC.C.
126136 // Should be called by any Decorator implementation.
127137 func (wc *WC) FormatMsg(msg string) string {
128 var format string
129 runeCount := utf8.RuneCountInString(stripansi.Strip(msg))
130 ansiCount := utf8.RuneCountInString(msg) - runeCount
138 pureWidth := runewidth.StringWidth(msg)
139 stripWidth := runewidth.StringWidth(stripansi.Strip(msg))
140 maxCell := wc.W
131141 if (wc.C & DSyncWidth) != 0 {
142 cellCount := stripWidth
132143 if (wc.C & DextraSpace) != 0 {
133 runeCount++
144 cellCount++
134145 }
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)
146 wc.wsync <- cellCount
147 maxCell = <-wc.wsync
140148 }
141 return fmt.Sprintf(format, msg)
149 return wc.fill(msg, maxCell+(pureWidth-stripWidth))
142150 }
143151
144152 // Init initializes width related config.
145153 func (wc *WC) Init() WC {
146 wc.dynFormat = "%%"
154 wc.fill = runewidth.FillLeft
147155 if (wc.C & DidentRight) != 0 {
148 wc.dynFormat += "-"
156 wc.fill = runewidth.FillRight
149157 }
150 wc.dynFormat += "%ds"
151158 if (wc.C & DSyncWidth) != 0 {
152159 // it's deliberate choice to override wsync on each Init() call,
153160 // this way globals like WCSyncSpace can be reused
0 // Package decor provides common decorators for "github.com/vbauerster/mpb/v6" module.
01 /*
1 Package decor provides common decorators for "github.com/vbauerster/mpb/v5" module.
2
32 Some decorators returned by this package might have a closure state. It is ok to use
43 decorators concurrently, unless you share the same decorator among multiple
54 *mpb.Bar instances. To avoid data races, create new decorator per *mpb.Bar instance.
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
6364 return d.Decorator
6465 }
6566
66 func (d *mergeDecorator) Decor(s *Statistics) string {
67 func (d *mergeDecorator) Decor(s Statistics) string {
6768 msg := d.Decorator.Decor(s)
68 msgLen := utf8.RuneCountInString(msg)
69 pureWidth := runewidth.StringWidth(msg)
70 stripWidth := runewidth.StringWidth(stripansi.Strip(msg))
71 cellCount := stripWidth
6972 if (d.wc.C & DextraSpace) != 0 {
70 msgLen++
73 cellCount++
7174 }
7275
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)
76 total := runewidth.StringWidth(d.placeHolders[0].FormatMsg(""))
77 pw := (cellCount - total) / len(d.placeHolders)
78 rem := (cellCount - total) % len(d.placeHolders)
7879
7980 var diff int
8081 for i := 1; i < len(d.placeHolders); i++ {
8687 width = 0
8788 }
8889 }
89 max = utf8.RuneCountInString(ph.FormatMsg(strings.Repeat(" ", width)))
90 max := runewidth.StringWidth(ph.FormatMsg(strings.Repeat(" ", width)))
9091 total += max
9192 diff = max - pw
9293 }
9394
9495 d.wc.wsync <- pw + rem
95 max = <-d.wc.wsync
96 return fmt.Sprintf(fmt.Sprintf(d.wc.dynFormat, max+total), msg)
96 max := <-d.wc.wsync
97 return d.wc.fill(msg, max+total+(pureWidth-stripWidth))
9798 }
9899
99100 type placeHolderDecorator struct {
100101 WC
101102 }
102103
103 func (d *placeHolderDecorator) Decor(*Statistics) string {
104 func (d *placeHolderDecorator) Decor(Statistics) string {
104105 return ""
105106 }
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 }
2323 msg string
2424 }
2525
26 func (d *onCompleteWrapper) Decor(s *Statistics) string {
26 func (d *onCompleteWrapper) Decor(s Statistics) string {
2727 if s.Completed {
2828 wc := d.GetConf()
2929 return wc.FormatMsg(d.msg)
44 "io"
55 "strconv"
66
7 "github.com/vbauerster/mpb/v5/internal"
7 "github.com/vbauerster/mpb/v6/internal"
88 )
99
1010 type percentageType float64
4949 if format == "" {
5050 format = "% d"
5151 }
52 f := func(s *Statistics) string {
52 f := func(s Statistics) string {
5353 p := internal.Percentage(s.Total, s.Current, 100)
5454 return fmt.Sprintf(format, percentageType(p))
5555 }
7777 msg string
7878 }
7979
80 func (d *movingAverageSpeed) Decor(s *Statistics) string {
80 func (d *movingAverageSpeed) Decor(s Statistics) string {
8181 if !s.Completed {
8282 var speed float64
8383 if v := d.average.Value(); v > 0 {
139139 msg string
140140 }
141141
142 func (d *averageSpeed) Decor(s *Statistics) string {
142 func (d *averageSpeed) Decor(s Statistics) string {
143143 if !s.Completed {
144144 speed := float64(s.Current) / float64(time.Since(d.startTime))
145145 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/v6"
7 "github.com/vbauerster/mpb/v6/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
88 func TestDraw(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 name string
12 style string
13 total int64
14 current int64
15 refill int64
16 barWidth int
17 trim bool
18 reverse bool
19 want string
1820 }{
1921 0: {
2022 {
21 name: "t,c,bw{60,20,80}",
23 name: "t,c{60,20}",
24 total: 60,
25 current: 20,
26 want: "… ",
27 },
28 {
29 name: "t,c{60,20}trim",
30 total: 60,
31 current: 20,
32 trim: true,
33 want: "",
34 },
35 },
36 1: {
37 {
38 name: "t,c{60,20}",
39 total: 60,
40 current: 20,
41 want: "… ",
42 },
43 {
44 name: "t,c{60,20}trim",
45 total: 60,
46 current: 20,
47 trim: true,
48 want: "",
49 },
50 },
51 2: {
52 {
53 name: "t,c{60,20}",
54 total: 60,
55 current: 20,
56 want: " ",
57 },
58 {
59 name: "t,c{60,20}trim",
60 total: 60,
61 current: 20,
62 trim: true,
63 want: "[]",
64 },
65 },
66 3: {
67 {
68 name: "t,c{60,20}",
69 total: 60,
70 current: 20,
71 want: " ",
72 },
73 {
74 name: "t,c{60,20}trim",
75 total: 60,
76 current: 20,
77 trim: true,
78 want: "[-]",
79 },
80 },
81 4: {
82 {
83 name: "t,c{60,20}",
84 total: 60,
85 current: 20,
86 want: " [] ",
87 },
88 {
89 name: "t,c{60,20}trim",
90 total: 60,
91 current: 20,
92 trim: true,
93 want: "[>-]",
94 },
95 },
96 5: {
97 {
98 name: "t,c{60,20}",
99 total: 60,
100 current: 20,
101 want: " [-] ",
102 },
103 {
104 name: "t,c{60,20}trim",
105 total: 60,
106 current: 20,
107 trim: true,
108 want: "[>--]",
109 },
110 },
111 6: {
112 {
113 name: "t,c{60,20}",
114 total: 60,
115 current: 20,
116 want: " [>-] ",
117 },
118 {
119 name: "t,c{60,20}trim",
120 total: 60,
121 current: 20,
122 trim: true,
123 want: "[>---]",
124 },
125 },
126 7: {
127 {
128 name: "t,c{60,20}",
129 total: 60,
130 current: 20,
131 want: " [>--] ",
132 },
133 {
134 name: "t,c{60,20}trim",
135 total: 60,
136 current: 20,
137 trim: true,
138 want: "[=>---]",
139 },
140 },
141 8: {
142 {
143 name: "t,c{60,20}",
144 total: 60,
145 current: 20,
146 want: " [>---] ",
147 },
148 {
149 name: "t,c{60,20}trim",
150 total: 60,
151 current: 20,
152 trim: true,
153 want: "[=>----]",
154 },
155 },
156 80: {
157 {
158 name: "t,c{60,20}",
159 total: 60,
160 current: 20,
161 want: " [========================>---------------------------------------------------] ",
162 },
163 {
164 name: "t,c{60,20}trim",
165 total: 60,
166 current: 20,
167 trim: true,
168 want: "[=========================>----------------------------------------------------]",
169 },
170 {
171 name: "t,c,bw{60,20,60}",
22172 total: 60,
23173 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}",
174 barWidth: 60,
175 want: " [==================>---------------------------------------] ",
176 },
177 {
178 name: "t,c,bw{60,20,60}trim",
39179 total: 60,
40180 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}",
56 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}",
73 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}",
90 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}",
107 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: "[=========================>----------------------------------------------------]",
181 barWidth: 60,
182 trim: true,
183 want: "[==================>---------------------------------------]",
187184 },
188185 },
189186 100: {
190187 {
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: "[==================================================================================================]",
188 name: "t,c{100,0}",
189 total: 100,
190 current: 0,
191 want: " [------------------------------------------------------------------------------------------------] ",
192 },
193 {
194 name: "t,c{100,0}trim",
195 total: 100,
196 current: 0,
197 trim: true,
198 want: "[--------------------------------------------------------------------------------------------------]",
199 },
200 {
201 name: "t,c{100,1}",
202 total: 100,
203 current: 1,
204 want: " [>-----------------------------------------------------------------------------------------------] ",
205 },
206 {
207 name: "t,c{100,1}trim",
208 total: 100,
209 current: 1,
210 trim: true,
211 want: "[>-------------------------------------------------------------------------------------------------]",
212 },
213 {
214 name: "t,c{100,99}",
215 total: 100,
216 current: 99,
217 want: " [==============================================================================================>-] ",
218 },
219 {
220 name: "t,c{100,99}trim",
221 total: 100,
222 current: 99,
223 trim: true,
224 want: "[================================================================================================>-]",
225 },
226 {
227 name: "t,c{100,100}",
228 total: 100,
229 current: 100,
230 want: " [================================================================================================] ",
231 },
232 {
233 name: "t,c{100,100}trim",
234 total: 100,
235 current: 100,
236 trim: true,
237 want: "[==================================================================================================]",
238 },
239 {
240 name: "t,c,r{100,100,100}trim",
241 total: 100,
242 current: 100,
243 refill: 100,
244 trim: true,
245 want: "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]",
246 },
247 {
248 name: "t,c{100,33}",
249 total: 100,
250 current: 33,
251 want: " [===============================>----------------------------------------------------------------] ",
252 },
253 {
254 name: "t,c{100,33}trim",
255 total: 100,
256 current: 33,
257 trim: true,
258 want: "[===============================>------------------------------------------------------------------]",
259 },
260 {
261 name: "t,c{100,33}trim,rev",
262 total: 100,
263 current: 33,
264 trim: true,
265 reverse: true,
266 want: "[------------------------------------------------------------------<===============================]",
267 },
268 {
269 name: "t,c,r{100,33,33}",
270 total: 100,
271 current: 33,
272 refill: 33,
273 want: " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ",
274 },
275 {
276 name: "t,c,r{100,33,33}trim",
277 total: 100,
278 current: 33,
279 refill: 33,
280 trim: true,
281 want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]",
282 },
283 {
284 name: "t,c,r{100,33,33}trim,rev",
285 total: 100,
286 current: 33,
287 refill: 33,
288 trim: true,
289 reverse: true,
290 want: "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]",
291 },
292 {
293 name: "t,c,r{100,40,33}",
294 total: 100,
295 current: 40,
296 refill: 33,
297 want: " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ",
298 },
299 {
300 name: "t,c,r{100,40,33}trim",
301 total: 100,
302 current: 40,
303 refill: 33,
304 trim: true,
305 want: "[++++++++++++++++++++++++++++++++======>-----------------------------------------------------------]",
306 },
307 {
308 name: "t,c,r{100,40,33},rev",
309 total: 100,
310 current: 40,
311 refill: 33,
312 reverse: true,
313 want: " [----------------------------------------------------------<=====++++++++++++++++++++++++++++++++] ",
314 },
315 {
316 name: "t,c,r{100,40,33}trim,rev",
317 total: 100,
318 current: 40,
319 refill: 33,
320 trim: true,
321 reverse: true,
322 want: "[-----------------------------------------------------------<======++++++++++++++++++++++++++++++++]",
323 },
324 {
325 name: "[=の-] t,c{100,1}",
326 style: "[=の-]",
327 total: 100,
328 current: 1,
329 want: " [の---------------------------------------------------------------------------------------------…] ",
330 },
331 },
332 197: {
333 {
334 name: "t,c,r{97486999,2805950,2805483}trim",
335 total: 97486999,
336 current: 2805950,
337 refill: 2805483,
338 barWidth: 60,
339 trim: true,
340 want: "[+>--------------------------------------------------------]",
317341 },
318342 },
319343 }
320344
321345 var tmpBuf bytes.Buffer
322 for termWidth, cases := range testSuite {
346 for tw, cases := range testSuite {
323347 for _, tc := range cases {
324 s := newTestState(tc.reverse)
325 s.width = tc.barWidth
348 s := newTestState(tc.style, tc.reverse)
349 s.reqWidth = tc.barWidth
326350 s.total = tc.total
327351 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 }
352 s.trimSpace = tc.trim
353 s.refill = tc.refill
354 tmpBuf.Reset()
355 tmpBuf.ReadFrom(s.draw(newStatistics(tw, s)))
356 by := tmpBuf.Bytes()
357
358 got := string(by[:len(by)-1])
359 if !utf8.ValidString(got) {
360 t.Fail()
333361 }
334 tmpBuf.Reset()
335 tmpBuf.ReadFrom(s.draw(termWidth, newStatistics(s)))
336 by := tmpBuf.Bytes()
337 by = by[:len(by)-1]
338
339 if utf8.RuneCount(by) > termWidth {
340 t.Errorf("termWidth:%d %q barWidth:%d overflow termWidth\n", termWidth, tc.name, utf8.RuneCount(by))
341 }
342
343 got := string(by)
344362 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))
363 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))
346364 }
347365 }
348366 }
349367 }
350368
351 func newTestState(reverse bool) *bState {
369 func newTestState(style string, rev bool) *bState {
352370 s := &bState{
353 filler: NewBarFiller(DefaultBarStyle, reverse),
371 filler: NewBarFillerPick(style, rev),
354372 bufP: new(bytes.Buffer),
355373 bufB: new(bytes.Buffer),
356374 bufA: new(bytes.Buffer),
66 "math/rand"
77 "time"
88
9 "github.com/vbauerster/mpb/v5"
10 "github.com/vbauerster/mpb/v5/decor"
9 "github.com/vbauerster/mpb/v6"
10 "github.com/vbauerster/mpb/v6/decor"
1111 )
1212
1313 func Example() {
1717 total := 100
1818 name := "Single Bar:"
1919 // adding a single bar, which will inherit container's width
20 bar := p.AddBar(int64(total),
21 // override DefaultBarStyle, which is "[=>-]<+"
22 mpb.BarStyle("╢▌▌░╟"),
20 bar := p.Add(int64(total),
21 // progress bar filler with customized style
22 mpb.NewBarFiller("╢▌▌░╟"),
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}),
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/v6
11
22 require (
33 github.com/VividCortex/ewma v1.1.1
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.10
6 github.com/rivo/uniseg v0.2.0
7 golang.org/x/sys v0.0.0-20210324051608-47abb6519492
78 )
89
910 go 1.14
11 github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
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.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
5 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
6 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
7 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
8 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
9 golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
10 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
66 if total <= 0 {
77 return 0
88 }
9 if current >= total {
10 return float64(width)
11 }
912 return float64(int64(width)*current) / float64(total)
1013 }
1114
15 // PercentageRound same as Percentage but with math.Round.
1216 func PercentageRound(total, current int64, width int) float64 {
1317 return math.Round(Percentage(total, current, width))
1418 }
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 // Predicate helper for internal use.
3 func Predicate(pick bool) func() bool {
4 return func() bool { return pick }
5 }
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 <= 0 || 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 }
77 "io"
88 "io/ioutil"
99 "log"
10 "math"
1011 "os"
1112 "sync"
1213 "time"
1314
14 "github.com/vbauerster/mpb/v5/cwriter"
15 "github.com/vbauerster/mpb/v5/decor"
15 "github.com/vbauerster/mpb/v6/cwriter"
16 "github.com/vbauerster/mpb/v6/decor"
1617 )
1718
1819 const (
1920 // default RefreshRate
2021 prr = 120 * time.Millisecond
21 // default width
22 pwidth = 80
2322 )
2423
25 // Progress represents the container that renders Progress bars
24 // Progress represents a container that renders one or more progress
25 // bars.
2626 type Progress struct {
2727 ctx context.Context
2828 uwg *sync.WaitGroup
3535 dlogger *log.Logger
3636 }
3737
38 // pState holds bars in its priorityQueue. It gets passed to
39 // *Progress.serve(...) monitor goroutine.
3840 type pState struct {
3941 bHeap priorityQueue
4042 heapUpdated bool
4143 pMatrix map[int][]chan int
4244 aMatrix map[int][]chan int
4345 barShutdownQueue []*Bar
44 barPopQueue []*Bar
4546
4647 // following are provided/overrided by user
4748 idCount int
48 width int
49 reqWidth int
4950 popCompleted bool
51 outputDiscarded bool
5052 rr time.Duration
5153 uwg *sync.WaitGroup
52 refreshSrc <-chan time.Time
54 externalRefresh <-chan interface{}
5355 renderDelay <-chan struct{}
5456 shutdownNotifier chan struct{}
5557 parkedBars map[*Bar]*Bar
6971 func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress {
7072 s := &pState{
7173 bHeap: priorityQueue{},
72 width: pwidth,
7374 rr: prr,
7475 parkedBars: make(map[*Bar]*Bar),
7576 output: os.Stdout,
9798 return p
9899 }
99100
100 // AddBar creates a new progress bar and adds it to the rendering queue.
101 // AddBar creates a bar with default bar filler. Different filler can
102 // be choosen and applied via `*Progress.Add(...) *Bar` method.
101103 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.
104 return p.Add(total, NewBarFiller(BarDefaultStyle), options...)
105 }
106
107 // AddSpinner creates a bar with default spinner filler. Different
108 // filler can be choosen and applied via `*Progress.Add(...) *Bar`
109 // method.
106110 func (p *Progress) AddSpinner(total int64, alignment SpinnerAlignment, options ...BarOption) *Bar {
107 return p.Add(total, NewSpinnerFiller(DefaultSpinnerStyle, alignment), options...)
111 return p.Add(total, NewSpinnerFiller(SpinnerDefaultStyle, alignment), options...)
108112 }
109113
110114 // Add creates a bar which renders itself by provided filler.
111 // Set total to 0, if you plan to update it later.
115 // If `total <= 0` trigger complete event is disabled until reset with *bar.SetTotal(int64, bool).
112116 // Panics if *Progress instance is done, i.e. called after *Progress.Wait().
113117 func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) *Bar {
114118 if filler == nil {
115 filler = NewBarFiller(DefaultBarStyle, false)
119 filler = BarFillerFunc(func(io.Writer, int, decor.Statistics) {})
116120 }
117121 p.bwg.Add(1)
118122 result := make(chan *Bar)
170174 p.setBarPriority(b, priority)
171175 }
172176
173 // BarCount returns bars count
177 // BarCount returns bars count.
174178 func (p *Progress) BarCount() int {
175179 result := make(chan int, 1)
176180 select {
181185 }
182186 }
183187
184 // Wait waits far all bars to complete and finally shutdowns container.
188 // Wait waits for all bars to complete and finally shutdowns container.
185189 // After this method has been called, there is no way to reuse *Progress
186190 // instance.
187191 func (p *Progress) Wait() {
214218 op(s)
215219 case <-p.refreshCh:
216220 if err := s.render(cw); err != nil {
217 go p.dlogger.Println(err)
221 p.dlogger.Println(err)
218222 }
219223 case <-s.shutdownNotifier:
224 if s.heapUpdated {
225 if err := s.render(cw); err != nil {
226 p.dlogger.Println(err)
227 }
228 }
220229 return
221230 }
222231 }
232 }
233
234 func (s *pState) newTicker(done <-chan struct{}) chan time.Time {
235 ch := make(chan time.Time)
236 if s.shutdownNotifier == nil {
237 s.shutdownNotifier = make(chan struct{})
238 }
239 go func() {
240 if s.renderDelay != nil {
241 <-s.renderDelay
242 }
243 var internalRefresh <-chan time.Time
244 if !s.outputDiscarded {
245 if s.externalRefresh == nil {
246 ticker := time.NewTicker(s.rr)
247 defer ticker.Stop()
248 internalRefresh = ticker.C
249 }
250 } else {
251 s.externalRefresh = nil
252 }
253 for {
254 select {
255 case t := <-internalRefresh:
256 ch <- t
257 case x := <-s.externalRefresh:
258 if t, ok := x.(time.Time); ok {
259 ch <- t
260 } else {
261 ch <- time.Now()
262 }
263 case <-done:
264 close(s.shutdownNotifier)
265 return
266 }
267 }
268 }()
269 return ch
223270 }
224271
225272 func (s *pState) render(cw *cwriter.Writer) error {
232279
233280 tw, err := cw.GetWidth()
234281 if err != nil {
235 tw = s.width
282 tw = s.reqWidth
236283 }
237284 for i := 0; i < s.bHeap.Len(); i++ {
238285 bar := s.bHeap[i]
249296 b := heap.Pop(&s.bHeap).(*Bar)
250297 cw.ReadFrom(<-b.frameCh)
251298 if b.toShutdown {
252 // shutdown at next flush
253 // this ensures no bar ends up with less than 100% rendered
254 defer func() {
299 if b.recoveredPanic != nil {
255300 s.barShutdownQueue = append(s.barShutdownQueue, b)
256 }()
301 b.toShutdown = false
302 } else {
303 // shutdown at next flush
304 // this ensures no bar ends up with less than 100% rendered
305 defer func() {
306 s.barShutdownQueue = append(s.barShutdownQueue, b)
307 }()
308 }
257309 }
258310 lineCount += b.extendedLines + 1
259311 bm[b] = struct{}{}
266318 delete(s.parkedBars, b)
267319 b.toDrop = true
268320 }
321 if s.popCompleted && !b.noPop {
322 lineCount -= b.extendedLines + 1
323 b.toDrop = true
324 }
269325 if b.toDrop {
270326 delete(bm, b)
271327 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 }
278328 }
279329 b.cancel()
280330 }
281331 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]
289332
290333 for b := range bm {
291334 heap.Push(&s.bHeap, b)
292335 }
293336
294337 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
322338 }
323339
324340 func (s *pState) updateSyncMatrix() {
341357
342358 func (s *pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState {
343359 bs := &bState{
360 id: s.idCount,
361 priority: s.idCount,
362 reqWidth: s.reqWidth,
344363 total: total,
345 baseF: extractBaseFiller(filler),
346364 filler: filler,
347 priority: s.idCount,
348 id: s.idCount,
349 width: s.width,
365 extender: func(r io.Reader, _ int, _ decor.Statistics) (io.Reader, int) { return r, 0 },
350366 debugOut: s.debugOut,
351 extender: func(r io.Reader, _ int, _ *decor.Statistics) (io.Reader, int) {
352 return r, 0
353 },
367 }
368
369 if total > 0 {
370 bs.triggerComplete = true
354371 }
355372
356373 for _, opt := range options {
359376 }
360377 }
361378
379 if bs.middleware != nil {
380 bs.filler = bs.middleware(filler)
381 bs.middleware = nil
382 }
383
362384 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))
385 bs.priority = -(math.MaxInt32 - s.idCount)
386 }
387
388 bs.bufP = bytes.NewBuffer(make([]byte, 0, 128))
389 bs.bufB = bytes.NewBuffer(make([]byte, 0, 256))
390 bs.bufA = bytes.NewBuffer(make([]byte, 0, 128))
369391
370392 return bs
371393 }
372394
373395 func syncWidth(matrix map[int][]chan int) {
374396 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 }
397 go maxWidthDistributor(column)
398 }
399 }
400
401 var maxWidthDistributor = func(column []chan int) {
402 var maxWidth int
403 for _, ch := range column {
404 if w := <-ch; w > maxWidth {
405 maxWidth = w
406 }
407 }
408 for _, ch := range column {
409 ch <- maxWidth
410 }
411 }
88 "testing"
99 "time"
1010
11 "github.com/vbauerster/mpb/v5"
11 "github.com/vbauerster/mpb/v6"
12 "github.com/vbauerster/mpb/v6/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()
106109 }
107110 }
108111
112 // MaxWidthDistributor shouldn't stuck in the middle while removing or aborting a bar
113 func TestMaxWidthDistributor(t *testing.T) {
114
115 makeWrapper := func(f func([]chan int), start, end chan struct{}) func([]chan int) {
116 return func(column []chan int) {
117 start <- struct{}{}
118 f(column)
119 <-end
120 }
121 }
122
123 ready := make(chan struct{})
124 start := make(chan struct{})
125 end := make(chan struct{})
126 *mpb.MaxWidthDistributor = makeWrapper(*mpb.MaxWidthDistributor, start, end)
127
128 total := 80
129 numBars := 3
130 p := mpb.New(mpb.WithOutput(ioutil.Discard))
131 for i := 0; i < numBars; i++ {
132 bar := p.AddBar(int64(total),
133 mpb.BarOptional(mpb.BarRemoveOnComplete(), i == 0),
134 mpb.PrependDecorators(
135 decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace),
136 ),
137 )
138 go func() {
139 <-ready
140 rng := rand.New(rand.NewSource(time.Now().UnixNano()))
141 for i := 0; i < total; i++ {
142 start := time.Now()
143 if bar.ID() >= numBars-1 && i >= 42 {
144 bar.Abort(true)
145 }
146 time.Sleep((time.Duration(rng.Intn(10)+1) * (10 * time.Millisecond)) / 2)
147 bar.Increment()
148 bar.DecoratorEwmaUpdate(time.Since(start))
149 }
150 }()
151 }
152
153 go func() {
154 <-ready
155 p.Wait()
156 close(start)
157 }()
158
159 res := t.Run("maxWidthDistributor", func(t *testing.T) {
160 close(ready)
161 for v := range start {
162 timer := time.NewTimer(100 * time.Millisecond)
163 select {
164 case end <- v:
165 timer.Stop()
166 case <-timer.C:
167 t.FailNow()
168 }
169 }
170 })
171
172 if !res {
173 t.Error("maxWidthDistributor stuck in the middle")
174 }
175 }
176
109177 func getLastLine(bb []byte) []byte {
110178 split := bytes.Split(bb, []byte("\n"))
111179 return split[len(split)-2]
66 "strings"
77 "testing"
88
9 "github.com/vbauerster/mpb/v5"
9 "github.com/vbauerster/mpb/v6"
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))
6969 wt := reader.(io.WriterTo)
7070 tReader := &testWriterTo{reader, wt, false}
7171
72 bar := p.AddBar(int64(len(content)), mpb.TrimSpace())
72 bar := p.AddBar(int64(len(content)), mpb.BarFillerTrim())
7373
7474 var buf bytes.Buffer
7575 _, 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 }