New upstream version 6.0.3
Reinhard Tartler
5 years ago
| 0 | 0 | # Multi Progress Bar |
| 1 | 1 | |
| 2 | [](https://godoc.org/github.com/vbauerster/mpb) | |
| 2 | [](https://pkg.go.dev/github.com/vbauerster/mpb/v6) | |
| 3 | 3 | [](https://travis-ci.org/vbauerster/mpb) |
| 4 | 4 | [](https://goreportcard.com/report/github.com/vbauerster/mpb) |
| 5 | 5 | |
| 7 | 7 | |
| 8 | 8 | ## Features |
| 9 | 9 | |
| 10 | * __Multiple Bars__: Multiple progress bars are supported | |
| 11 | * __Dynamic Total__: Set total while bar is running | |
| 12 | * __Dynamic Add/Remove__: Dynamically add or remove bars | |
| 13 | * __Cancellation__: Cancel whole rendering process | |
| 14 | * __Predefined Decorators__: Elapsed time, [ewma](https://github.com/VividCortex/ewma) based ETA, Percentage, Bytes counter | |
| 15 | * __Decorator's width sync__: Synchronized decorator's width among multiple bars | |
| 10 | - **Multiple Bars**: Multiple progress bars are supported | |
| 11 | - **Dynamic Total**: Set total while bar is running | |
| 12 | - **Dynamic Add/Remove**: Dynamically add or remove bars | |
| 13 | - **Cancellation**: Cancel whole rendering process | |
| 14 | - **Predefined Decorators**: Elapsed time, [ewma](https://github.com/VividCortex/ewma) based ETA, Percentage, Bytes counter | |
| 15 | - **Decorator's width sync**: Synchronized decorator's width among multiple bars | |
| 16 | 16 | |
| 17 | 17 | ## Usage |
| 18 | 18 | |
| 19 | 19 | #### [Rendering single bar](_examples/singleBar/main.go) |
| 20 | ||
| 20 | 21 | ```go |
| 21 | 22 | package main |
| 22 | 23 | |
| 24 | 25 | "math/rand" |
| 25 | 26 | "time" |
| 26 | 27 | |
| 27 | "github.com/vbauerster/mpb/v5" | |
| 28 | "github.com/vbauerster/mpb/v5/decor" | |
| 28 | "github.com/vbauerster/mpb/v6" | |
| 29 | "github.com/vbauerster/mpb/v6/decor" | |
| 29 | 30 | ) |
| 30 | 31 | |
| 31 | 32 | func main() { |
| 35 | 36 | total := 100 |
| 36 | 37 | name := "Single Bar:" |
| 37 | 38 | // 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("╢▌▌░╟"), | |
| 41 | 42 | mpb.PrependDecorators( |
| 42 | 43 | // display our name with one space on the right |
| 43 | 44 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 60 | 61 | ``` |
| 61 | 62 | |
| 62 | 63 | #### [Rendering multiple bars](_examples/multiBars/main.go) |
| 64 | ||
| 63 | 65 | ```go |
| 64 | 66 | var wg sync.WaitGroup |
| 65 | 67 | // pass &wg (optional), so p will wait for it eventually |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 6 | 6 | "sync" |
| 7 | 7 | "time" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v6" | |
| 10 | "github.com/vbauerster/mpb/v6/decor" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | func main() { |
| 18 | 18 | |
| 19 | 19 | for i := 0; i < numBars; i++ { |
| 20 | 20 | 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) { | |
| 22 | 22 | if s.Completed { |
| 23 | 23 | fmt.Fprintf(w, "Bar id: %d has been completed\n", s.ID) |
| 24 | 24 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 6 | 6 | "sync" |
| 7 | 7 | "time" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v6" | |
| 10 | "github.com/vbauerster/mpb/v6/decor" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | func main() { |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func init() { |
| 15 | 15 | |
| 16 | 16 | func main() { |
| 17 | 17 | doneWg := new(sync.WaitGroup) |
| 18 | p := mpb.New(mpb.WithWidth(64), mpb.WithWaitGroup(doneWg)) | |
| 18 | p := mpb.New(mpb.WithWaitGroup(doneWg)) | |
| 19 | 19 | numBars := 4 |
| 20 | 20 | |
| 21 | 21 | var bars []*mpb.Bar |
| 43 | 43 | i := i |
| 44 | 44 | go func() { |
| 45 | 45 | 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" | |
| 47 | 48 | // preparing delayed bars |
| 48 | 49 | b := p.AddBar(rand.Int63n(101)+100, |
| 49 | 50 | 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 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 23 | 23 | name := fmt.Sprintf("Bar#%d:", i) |
| 24 | 24 | bar := p.AddBar(int64(total), |
| 25 | 25 | // set BarWidth 40 for bar 1 and 2 |
| 26 | mpb.BarOptOn(mpb.BarWidth(40), func() bool { return i > 0 }), | |
| 26 | mpb.BarOptional(mpb.BarWidth(40), i > 0), | |
| 27 | 27 | mpb.PrependDecorators( |
| 28 | 28 | // simple name decorator |
| 29 | 29 | decor.Name(name), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 4 | 4 | "math/rand" |
| 5 | 5 | "time" |
| 6 | 6 | |
| 7 | "github.com/vbauerster/mpb/v5" | |
| 8 | "github.com/vbauerster/mpb/v5/decor" | |
| 7 | "github.com/vbauerster/mpb/v6" | |
| 8 | "github.com/vbauerster/mpb/v6/decor" | |
| 9 | 9 | ) |
| 10 | 10 | |
| 11 | 11 | func init() { |
| 16 | 16 | p := mpb.New(mpb.WithWidth(64)) |
| 17 | 17 | |
| 18 | 18 | var total int64 |
| 19 | // new bar with 'trigger complete event' disabled, because total is zero | |
| 19 | 20 | bar := p.AddBar(total, |
| 20 | 21 | mpb.PrependDecorators(decor.Counters(decor.UnitKiB, "% .1f / % .1f")), |
| 21 | 22 | mpb.AppendDecorators(decor.Percentage()), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "io/ioutil" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 18 | 18 | mpb.WithRefreshRate(180*time.Millisecond), |
| 19 | 19 | ) |
| 20 | 20 | |
| 21 | bar := p.AddBar(total, mpb.BarStyle("[=>-|"), | |
| 21 | bar := p.Add(total, | |
| 22 | mpb.NewBarFiller("[=>-|"), | |
| 22 | 23 | mpb.PrependDecorators( |
| 23 | 24 | decor.CountersKibiByte("% .2f / % .2f"), |
| 24 | 25 | ), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 26 | 26 | "done", |
| 27 | 27 | ), |
| 28 | 28 | decor.WCSyncSpace, // Placeholder |
| 29 | decor.WCSyncSpace, // Placeholder | |
| 29 | 30 | ), |
| 30 | 31 | ) |
| 31 | 32 | } else { |
| 32 | 33 | pdecorators = mpb.PrependDecorators( |
| 33 | 34 | decor.CountersNoUnit("% .1d / % .1d", decor.WCSyncSpace), |
| 35 | decor.OnComplete(decor.Spinner(nil, decor.WCSyncSpace), "done"), | |
| 34 | 36 | decor.OnComplete(decor.Spinner(nil, decor.WCSyncSpace), "done"), |
| 35 | 37 | ) |
| 36 | 38 | } |
| 62 | 64 | |
| 63 | 65 | func newVariadicSpinner(wc decor.WC) decor.Decorator { |
| 64 | 66 | spinner := decor.Spinner(nil) |
| 65 | f := func(s *decor.Statistics) string { | |
| 67 | fn := func(s decor.Statistics) string { | |
| 66 | 68 | return strings.Repeat(spinner.Decor(s), int(s.Current/3)) |
| 67 | 69 | } |
| 68 | return decor.Any(f, wc) | |
| 70 | return decor.Any(fn, wc) | |
| 69 | 71 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | 4 | "os" |
| 5 | "strings" | |
| 5 | 6 | "sync" |
| 6 | 7 | "time" |
| 7 | 8 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v6" | |
| 10 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 11 | ) |
| 11 | 12 | |
| 12 | 13 | func main() { |
| 13 | 14 | var wg sync.WaitGroup |
| 14 | 15 | p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithDebugOutput(os.Stderr)) |
| 15 | 16 | |
| 16 | wantPanic := "Some really long panic panic panic panic panic panic panic, really it is very long" | |
| 17 | wantPanic := strings.Repeat("Panic ", 64) | |
| 17 | 18 | numBars := 3 |
| 18 | 19 | wg.Add(numBars) |
| 19 | 20 | |
| 34 | 35 | } |
| 35 | 36 | |
| 36 | 37 | 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 { | |
| 39 | 40 | panic(panicMsg) |
| 40 | 41 | } |
| 41 | 42 | return name |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | "io" | |
| 5 | 4 | "math/rand" |
| 6 | "sync" | |
| 7 | 5 | "time" |
| 8 | 6 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 7 | "github.com/vbauerster/mpb/v6" | |
| 8 | "github.com/vbauerster/mpb/v6/decor" | |
| 11 | 9 | ) |
| 12 | 10 | |
| 13 | 11 | func main() { |
| 14 | 12 | p := mpb.New(mpb.PopCompletedMode()) |
| 15 | 13 | |
| 16 | total, numBars := 100, 2 | |
| 14 | total, numBars := 100, 4 | |
| 17 | 15 | for i := 0; i < numBars; i++ { |
| 18 | 16 | name := fmt.Sprintf("Bar#%d:", i) |
| 19 | 17 | bar := p.AddBar(int64(total), |
| 20 | mpb.BarNoPop(), | |
| 18 | mpb.BarFillerOnComplete(fmt.Sprintf("%s has been completed", name)), | |
| 19 | mpb.BarFillerTrim(), | |
| 21 | 20 | mpb.PrependDecorators( |
| 22 | decor.Name(name), | |
| 23 | decor.Percentage(decor.WCSyncSpace), | |
| 21 | decor.OnComplete(decor.Name(name), ""), | |
| 22 | decor.OnComplete(decor.NewPercentage(" % d "), ""), | |
| 24 | 23 | ), |
| 25 | 24 | mpb.AppendDecorators( |
| 26 | decor.OnComplete( | |
| 27 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done!", | |
| 28 | ), | |
| 25 | decor.OnComplete(decor.Name(" "), ""), | |
| 26 | decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 60), ""), | |
| 29 | 27 | ), |
| 30 | 28 | ) |
| 31 | 29 | // simulating some work |
| 32 | go func() { | |
| 33 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 34 | max := 100 * time.Millisecond | |
| 35 | for i := 0; i < total; i++ { | |
| 36 | // start variable is solely for EWMA calculation | |
| 37 | // EWMA's unit of measure is an iteration's duration | |
| 38 | start := time.Now() | |
| 39 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) | |
| 40 | bar.Increment() | |
| 41 | // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract | |
| 42 | bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 43 | } | |
| 44 | }() | |
| 30 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 31 | max := 100 * time.Millisecond | |
| 32 | for i := 0; i < total; i++ { | |
| 33 | // start variable is solely for EWMA calculation | |
| 34 | // EWMA's unit of measure is an iteration's duration | |
| 35 | start := time.Now() | |
| 36 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) | |
| 37 | bar.Increment() | |
| 38 | // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract | |
| 39 | bar.DecoratorEwmaUpdate(time.Since(start)) | |
| 40 | } | |
| 45 | 41 | } |
| 46 | 42 | |
| 47 | var wg sync.WaitGroup | |
| 48 | wg.Add(1) | |
| 49 | go func() { | |
| 50 | defer wg.Done() | |
| 51 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 52 | max := 3000 * time.Millisecond | |
| 53 | for i := 0; i < 10; i++ { | |
| 54 | filler := makeLogBar(fmt.Sprintf("some log: %d", i)) | |
| 55 | p.Add(0, filler).SetTotal(0, true) | |
| 56 | time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) | |
| 57 | } | |
| 58 | }() | |
| 59 | ||
| 60 | wg.Wait() | |
| 61 | 43 | p.Wait() |
| 62 | 44 | } |
| 63 | ||
| 64 | func makeLogBar(msg string) mpb.BarFiller { | |
| 65 | limit := "%%.%ds" | |
| 66 | return mpb.BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { | |
| 67 | fmt.Fprintf(w, fmt.Sprintf(limit, width), msg) | |
| 68 | }) | |
| 69 | } |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 6 | 6 | "sync" |
| 7 | 7 | "time" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v6" | |
| 10 | "github.com/vbauerster/mpb/v6/decor" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | var quietMode bool |
| 22 | 22 | // pass &wg (optional), so p will wait for it eventually |
| 23 | 23 | p := mpb.New( |
| 24 | 24 | mpb.WithWaitGroup(&wg), |
| 25 | mpb.ContainerOptOn( | |
| 25 | mpb.ContainerOptional( | |
| 26 | 26 | // setting to nil will: |
| 27 | // set output to ioutil.Discard and disable internal refresh rate | |
| 28 | // cycling, in order to not consume much CPU, hovewer a single refresh | |
| 29 | // still will be triggered on bar complete event, per each bar. | |
| 27 | // set output to ioutil.Discard and disable refresh rate cycle, in | |
| 28 | // order not to consume much CPU. Hovewer a single refresh still will | |
| 29 | // be triggered on bar complete event, per each bar. | |
| 30 | 30 | mpb.WithOutput(nil), |
| 31 | func() bool { return quietMode }, | |
| 31 | quietMode, | |
| 32 | 32 | ), |
| 33 | 33 | ) |
| 34 | 34 | total, numBars := 100, 3 |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 20 | 20 | name := fmt.Sprintf("Bar#%d:", i) |
| 21 | 21 | bar := p.AddBar(int64(total), |
| 22 | 22 | mpb.BarID(i), |
| 23 | mpb.BarOptOn(mpb.BarRemoveOnComplete(), func() bool { return i == 0 }), | |
| 23 | mpb.BarOptional(mpb.BarRemoveOnComplete(), i == 0), | |
| 24 | 24 | mpb.PrependDecorators( |
| 25 | 25 | decor.Name(name), |
| 26 | 26 | decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 18 | 18 | |
| 19 | 19 | for i := 0; i < numBars; i++ { |
| 20 | 20 | name := fmt.Sprintf("Bar#%d:", i) |
| 21 | bar := p.AddBar(int64(total), | |
| 21 | bar := p.Add(int64(total), | |
| 22 | 22 | // reverse Bar#1 |
| 23 | mpb.BarOptOn(mpb.BarReverse(), func() bool { return i == 1 }), | |
| 23 | mpb.NewBarFillerPick("", i == 1), | |
| 24 | 24 | mpb.PrependDecorators( |
| 25 | 25 | // simple name decorator |
| 26 | 26 | decor.Name(name), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 3 | 3 | "math/rand" |
| 4 | 4 | "time" |
| 5 | 5 | |
| 6 | "github.com/vbauerster/mpb/v5" | |
| 7 | "github.com/vbauerster/mpb/v5/decor" | |
| 6 | "github.com/vbauerster/mpb/v6" | |
| 7 | "github.com/vbauerster/mpb/v6/decor" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | 10 | func main() { |
| 14 | 14 | total := 100 |
| 15 | 15 | name := "Single Bar:" |
| 16 | 16 | // adding a single bar, which will inherit container's width |
| 17 | bar := p.AddBar(int64(total), | |
| 18 | // override DefaultBarStyle, which is "[=>-]<+" | |
| 19 | mpb.BarStyle("╢▌▌░╟"), | |
| 17 | bar := p.Add(int64(total), | |
| 18 | // progress bar filler with customized style | |
| 19 | mpb.NewBarFiller("╢▌▌░╟"), | |
| 20 | 20 | mpb.PrependDecorators( |
| 21 | 21 | // display our name with one space on the right |
| 22 | 22 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 13 | 13 | var wg sync.WaitGroup |
| 14 | 14 | p := mpb.New( |
| 15 | 15 | mpb.WithWaitGroup(&wg), |
| 16 | mpb.WithWidth(13), | |
| 16 | mpb.WithWidth(14), | |
| 17 | 17 | ) |
| 18 | 18 | total, numBars := 101, 3 |
| 19 | 19 | wg.Add(numBars) |
| 20 | ||
| 21 | spinnerStyle := []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"} | |
| 20 | 22 | |
| 21 | 23 | for i := 0; i < numBars; i++ { |
| 22 | 24 | name := fmt.Sprintf("Bar#%d:", i) |
| 23 | 25 | var bar *mpb.Bar |
| 24 | 26 | 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("╢▌▌░╟"), | |
| 28 | 29 | mpb.PrependDecorators( |
| 29 | 30 | // simple name decorator |
| 30 | 31 | decor.Name(name), |
| 38 | 39 | ), |
| 39 | 40 | ) |
| 40 | 41 | } 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), | |
| 44 | 44 | mpb.PrependDecorators( |
| 45 | 45 | // simple name decorator |
| 46 | 46 | decor.Name(name), |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func main() { |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require github.com/vbauerster/mpb/v6 v6.0.3 |
| 5 | 5 | "sync" |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | "github.com/vbauerster/mpb/v6" | |
| 9 | "github.com/vbauerster/mpb/v6/decor" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | const ( |
| 1 | 1 | |
| 2 | 2 | go 1.14 |
| 3 | 3 | |
| 4 | require github.com/vbauerster/mpb/v5 v5.0.3 | |
| 4 | require ( | |
| 5 | github.com/mattn/go-runewidth v0.0.10 | |
| 6 | github.com/vbauerster/mpb/v6 v6.0.3 | |
| 7 | ) |
| 7 | 7 | "sync" |
| 8 | 8 | "time" |
| 9 | 9 | |
| 10 | "github.com/vbauerster/mpb/v5" | |
| 11 | "github.com/vbauerster/mpb/v5/decor" | |
| 10 | "github.com/mattn/go-runewidth" | |
| 11 | "github.com/vbauerster/mpb/v6" | |
| 12 | "github.com/vbauerster/mpb/v6/decor" | |
| 12 | 13 | ) |
| 13 | 14 | |
| 14 | 15 | func main() { |
| 17 | 18 | total := 100 |
| 18 | 19 | msgCh := make(chan string) |
| 19 | 20 | resumeCh := make(chan struct{}) |
| 20 | filler, nextCh := newCustomFiller(msgCh, resumeCh) | |
| 21 | bar := p.Add(int64(total), filler, | |
| 22 | mpb.PrependDecorators( | |
| 23 | decor.Name("my bar:"), | |
| 24 | ), | |
| 25 | mpb.AppendDecorators( | |
| 26 | newCustomPercentage(nextCh), | |
| 27 | ), | |
| 21 | nextCh := make(chan struct{}, 1) | |
| 22 | bar := p.AddBar(int64(total), | |
| 23 | mpb.BarFillerMiddleware(func(base mpb.BarFiller) mpb.BarFiller { | |
| 24 | var msg *string | |
| 25 | return mpb.BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) { | |
| 26 | select { | |
| 27 | case m := <-msgCh: | |
| 28 | defer func() { | |
| 29 | msg = &m | |
| 30 | }() | |
| 31 | nextCh <- struct{}{} | |
| 32 | case <-resumeCh: | |
| 33 | msg = nil | |
| 34 | default: | |
| 35 | } | |
| 36 | if msg != nil { | |
| 37 | io.WriteString(w, runewidth.Truncate(*msg, st.AvailableWidth, "…")) | |
| 38 | nextCh <- struct{}{} | |
| 39 | } else { | |
| 40 | base.Fill(w, reqWidth, st) | |
| 41 | } | |
| 42 | }) | |
| 43 | }), | |
| 44 | mpb.PrependDecorators(decor.Name("my bar:")), | |
| 45 | mpb.AppendDecorators(newCustomPercentage(nextCh)), | |
| 28 | 46 | ) |
| 29 | 47 | ew := &errorWrapper{} |
| 30 | 48 | time.AfterFunc(2*time.Second, func() { |
| 75 | 93 | ew.Unlock() |
| 76 | 94 | } |
| 77 | 95 | |
| 78 | type myBarFiller struct { | |
| 79 | mpb.BarFiller | |
| 80 | base mpb.BarFiller | |
| 81 | } | |
| 82 | ||
| 83 | func (cf *myBarFiller) Base() mpb.BarFiller { | |
| 84 | return cf.base | |
| 85 | } | |
| 86 | ||
| 87 | func newCustomFiller(ch <-chan string, resume <-chan struct{}) (mpb.BarFiller, <-chan struct{}) { | |
| 88 | base := mpb.NewBarFiller(mpb.DefaultBarStyle, false) | |
| 89 | nextCh := make(chan struct{}, 1) | |
| 90 | var msg *string | |
| 91 | filler := mpb.BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { | |
| 96 | func newCustomPercentage(nextCh <-chan struct{}) decor.Decorator { | |
| 97 | base := decor.Percentage() | |
| 98 | fn := func(s decor.Statistics) string { | |
| 92 | 99 | select { |
| 93 | case m := <-ch: | |
| 94 | defer func() { | |
| 95 | msg = &m | |
| 96 | }() | |
| 97 | nextCh <- struct{}{} | |
| 98 | case <-resume: | |
| 99 | msg = nil | |
| 100 | default: | |
| 101 | } | |
| 102 | if msg != nil { | |
| 103 | limitFmt := fmt.Sprintf("%%.%ds", width) | |
| 104 | fmt.Fprintf(w, limitFmt, *msg) | |
| 105 | nextCh <- struct{}{} | |
| 106 | } else { | |
| 107 | base.Fill(w, width, st) | |
| 108 | } | |
| 109 | }) | |
| 110 | cf := &myBarFiller{ | |
| 111 | BarFiller: filler, | |
| 112 | base: base, | |
| 113 | } | |
| 114 | return cf, nextCh | |
| 115 | } | |
| 116 | ||
| 117 | func newCustomPercentage(ch <-chan struct{}) decor.Decorator { | |
| 118 | base := decor.Percentage() | |
| 119 | f := func(s *decor.Statistics) string { | |
| 120 | select { | |
| 121 | case <-ch: | |
| 100 | case <-nextCh: | |
| 122 | 101 | return "" |
| 123 | 102 | default: |
| 124 | 103 | return base.Decor(s) |
| 125 | 104 | } |
| 126 | 105 | } |
| 127 | return decor.Any(f) | |
| 106 | return decor.Any(fn) | |
| 128 | 107 | } |
| 5 | 5 | "fmt" |
| 6 | 6 | "io" |
| 7 | 7 | "log" |
| 8 | "runtime/debug" | |
| 8 | 9 | "strings" |
| 9 | 10 | "time" |
| 10 | "unicode/utf8" | |
| 11 | ||
| 12 | "github.com/vbauerster/mpb/v5/decor" | |
| 11 | ||
| 12 | "github.com/acarl005/stripansi" | |
| 13 | "github.com/mattn/go-runewidth" | |
| 14 | "github.com/vbauerster/mpb/v6/decor" | |
| 13 | 15 | ) |
| 14 | 16 | |
| 15 | // BarFiller interface. | |
| 16 | // Bar renders itself by calling BarFiller's Fill method. You can | |
| 17 | // literally have any bar kind, by implementing this interface and | |
| 18 | // passing it to the *Progress.Add(...) *Bar method. | |
| 19 | type BarFiller interface { | |
| 20 | Fill(w io.Writer, width int, stat *decor.Statistics) | |
| 21 | } | |
| 22 | ||
| 23 | // BarFillerFunc is function type adapter to convert function into Filler. | |
| 24 | type BarFillerFunc func(w io.Writer, width int, stat *decor.Statistics) | |
| 25 | ||
| 26 | func (f BarFillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 27 | f(w, width, stat) | |
| 28 | } | |
| 29 | ||
| 30 | // Bar represents a progress Bar. | |
| 17 | // Bar represents a progress bar. | |
| 31 | 18 | type Bar struct { |
| 32 | 19 | priority int // used by heap |
| 33 | 20 | index int // used by heap |
| 54 | 41 | recoveredPanic interface{} |
| 55 | 42 | } |
| 56 | 43 | |
| 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. | |
| 59 | 48 | type bState struct { |
| 60 | baseF BarFiller | |
| 61 | filler BarFiller | |
| 62 | 49 | id int |
| 63 | width int | |
| 50 | priority int | |
| 51 | reqWidth int | |
| 64 | 52 | total int64 |
| 65 | 53 | current int64 |
| 54 | refill int64 | |
| 66 | 55 | lastN int64 |
| 67 | 56 | iterated bool |
| 68 | 57 | trimSpace bool |
| 69 | toComplete bool | |
| 58 | completed bool | |
| 70 | 59 | completeFlushed bool |
| 60 | triggerComplete bool | |
| 61 | dropOnComplete bool | |
| 71 | 62 | noPop bool |
| 72 | 63 | aDecorators []decor.Decorator |
| 73 | 64 | pDecorators []decor.Decorator |
| 75 | 66 | ewmaDecorators []decor.EwmaDecorator |
| 76 | 67 | shutdownListeners []decor.ShutdownListener |
| 77 | 68 | 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 | ||
| 84 | 73 | // runningBar is a key for *pState.parkedBars |
| 85 | 74 | runningBar *Bar |
| 86 | 75 | |
| 98 | 87 | noPop: bs.noPop, |
| 99 | 88 | operateState: make(chan func(*bState)), |
| 100 | 89 | frameCh: make(chan io.Reader, 1), |
| 101 | syncTableCh: make(chan [][]chan int), | |
| 90 | syncTableCh: make(chan [][]chan int, 1), | |
| 102 | 91 | completed: make(chan bool, 1), |
| 103 | 92 | done: make(chan struct{}), |
| 104 | 93 | cancel: cancel, |
| 140 | 129 | } |
| 141 | 130 | } |
| 142 | 131 | |
| 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. | |
| 146 | 136 | 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: | |
| 154 | 142 | } |
| 155 | 143 | } |
| 156 | 144 | |
| 157 | 145 | // TraverseDecorators traverses all available decorators and calls cb func on each. |
| 158 | 146 | func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) { |
| 159 | b.operateState <- func(s *bState) { | |
| 147 | select { | |
| 148 | case b.operateState <- func(s *bState) { | |
| 160 | 149 | for _, decorators := range [...][]decor.Decorator{ |
| 161 | 150 | s.pDecorators, |
| 162 | 151 | s.aDecorators, |
| 165 | 154 | cb(extractBaseDecorator(d)) |
| 166 | 155 | } |
| 167 | 156 | } |
| 157 | }: | |
| 158 | case <-b.done: | |
| 168 | 159 | } |
| 169 | 160 | } |
| 170 | 161 | |
| 171 | 162 | // SetTotal sets total dynamically. |
| 172 | // If total is less or equal to zero it takes progress' current value. | |
| 173 | // If complete is true, complete event will be triggered. | |
| 174 | func (b *Bar) SetTotal(total int64, complete bool) { | |
| 175 | select { | |
| 176 | case b.operateState <- func(s *bState) { | |
| 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 | |
| 177 | 168 | if total <= 0 { |
| 178 | 169 | s.total = s.current |
| 179 | 170 | } else { |
| 180 | 171 | s.total = total |
| 181 | 172 | } |
| 182 | if complete && !s.toComplete { | |
| 173 | if s.triggerComplete && !s.completed { | |
| 183 | 174 | s.current = s.total |
| 184 | s.toComplete = true | |
| 175 | s.completed = true | |
| 185 | 176 | go b.refreshTillShutdown() |
| 186 | 177 | } |
| 187 | 178 | }: |
| 190 | 181 | } |
| 191 | 182 | |
| 192 | 183 | // SetCurrent sets progress' current to an arbitrary value. |
| 184 | // Setting a negative value will cause a panic. | |
| 193 | 185 | func (b *Bar) SetCurrent(current int64) { |
| 194 | 186 | select { |
| 195 | 187 | case b.operateState <- func(s *bState) { |
| 196 | 188 | s.iterated = true |
| 197 | 189 | s.lastN = current - s.current |
| 198 | 190 | s.current = current |
| 199 | if s.total > 0 && s.current >= s.total { | |
| 191 | if s.triggerComplete && s.current >= s.total { | |
| 200 | 192 | s.current = s.total |
| 201 | s.toComplete = true | |
| 193 | s.completed = true | |
| 202 | 194 | go b.refreshTillShutdown() |
| 203 | 195 | } |
| 204 | 196 | }: |
| 223 | 215 | s.iterated = true |
| 224 | 216 | s.lastN = n |
| 225 | 217 | s.current += n |
| 226 | if s.total > 0 && s.current >= s.total { | |
| 218 | if s.triggerComplete && s.current >= s.total { | |
| 227 | 219 | s.current = s.total |
| 228 | s.toComplete = true | |
| 220 | s.completed = true | |
| 229 | 221 | go b.refreshTillShutdown() |
| 230 | 222 | } |
| 231 | 223 | }: |
| 289 | 281 | // Completed reports whether the bar is in completed state. |
| 290 | 282 | func (b *Bar) Completed() bool { |
| 291 | 283 | select { |
| 292 | case b.operateState <- func(s *bState) { b.completed <- s.toComplete }: | |
| 284 | case b.operateState <- func(s *bState) { b.completed <- s.completed }: | |
| 293 | 285 | return <-b.completed |
| 294 | 286 | case <-b.done: |
| 295 | 287 | return true |
| 315 | 307 | } |
| 316 | 308 | |
| 317 | 309 | 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) | |
| 325 | 313 | defer func() { |
| 326 | 314 | // recovering if user defined decorator panics for example |
| 327 | 315 | 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 | |
| 328 | 324 | b.dlogger.Println(p) |
| 329 | b.recoveredPanic = p | |
| 330 | b.toShutdown = !s.completeFlushed | |
| 331 | b.frameCh <- b.panicToFrame(tw) | |
| 332 | 325 | } |
| 326 | s.completeFlushed = s.completed | |
| 333 | 327 | }() |
| 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 | |
| 341 | 331 | b.frameCh <- frame |
| 342 | 332 | }: |
| 343 | 333 | case <-b.done: |
| 344 | 334 | 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 | |
| 348 | 342 | b.frameCh <- frame |
| 349 | 343 | } |
| 350 | } | |
| 351 | ||
| 352 | func (b *Bar) panicToFrame(termWidth int) io.Reader { | |
| 353 | return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%dv\n", termWidth), b.recoveredPanic)) | |
| 354 | 344 | } |
| 355 | 345 | |
| 356 | 346 | func (b *Bar) subscribeDecorators() { |
| 368 | 358 | shutdownListeners = append(shutdownListeners, d) |
| 369 | 359 | } |
| 370 | 360 | }) |
| 371 | b.operateState <- func(s *bState) { | |
| 361 | select { | |
| 362 | case b.operateState <- func(s *bState) { | |
| 372 | 363 | s.averageDecorators = averageDecorators |
| 373 | 364 | s.ewmaDecorators = ewmaDecorators |
| 374 | 365 | s.shutdownListeners = shutdownListeners |
| 375 | } | |
| 376 | b.hasEwmaDecorators = len(ewmaDecorators) != 0 | |
| 366 | }: | |
| 367 | b.hasEwmaDecorators = len(ewmaDecorators) != 0 | |
| 368 | case <-b.done: | |
| 369 | } | |
| 377 | 370 | } |
| 378 | 371 | |
| 379 | 372 | func (b *Bar) refreshTillShutdown() { |
| 395 | 388 | } |
| 396 | 389 | } |
| 397 | 390 | |
| 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 | |
| 399 | 400 | 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 | |
| 403 | 412 | 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) | |
| 426 | 426 | } |
| 427 | 427 | |
| 428 | 428 | func (s *bState) wSyncTable() [][]chan int { |
| 447 | 447 | return table |
| 448 | 448 | } |
| 449 | 449 | |
| 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, | |
| 456 | 458 | } |
| 457 | 459 | } |
| 458 | 460 | |
| 473 | 475 | d.EwmaUpdate(s.lastN, dur) |
| 474 | 476 | } |
| 475 | 477 | } |
| 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 | } | |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "io" |
| 4 | "unicode/utf8" | |
| 5 | 4 | |
| 6 | "github.com/vbauerster/mpb/v5/decor" | |
| 7 | "github.com/vbauerster/mpb/v5/internal" | |
| 5 | "github.com/vbauerster/mpb/v6/decor" | |
| 8 | 6 | ) |
| 9 | 7 | |
| 10 | const ( | |
| 11 | rLeft = iota | |
| 12 | rFill | |
| 13 | rTip | |
| 14 | rEmpty | |
| 15 | rRight | |
| 16 | rRevTip | |
| 17 | rRefill | |
| 18 | ) | |
| 19 | ||
| 20 | // DefaultBarStyle is a string containing 7 runes. | |
| 21 | // Each rune is a building block of a progress bar. | |
| 8 | // BarFiller interface. | |
| 9 | // Bar (without decorators) renders itself by calling BarFiller's Fill method. | |
| 22 | 10 | // |
| 23 | // '1st rune' stands for left boundary rune | |
| 11 | // reqWidth is requested width, set by `func WithWidth(int) ContainerOption`. | |
| 12 | // If not set, it defaults to terminal width. | |
| 24 | 13 | // |
| 25 | // '2nd rune' stands for fill rune | |
| 14 | // Default implementations can be obtained via: | |
| 26 | 15 | // |
| 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 | |
| 28 | 20 | // |
| 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) | |
| 45 | 23 | } |
| 46 | 24 | |
| 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) | |
| 58 | 30 | } |
| 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 | } |
| 3 | 3 | "bytes" |
| 4 | 4 | "io" |
| 5 | 5 | |
| 6 | "github.com/vbauerster/mpb/v5/decor" | |
| 6 | "github.com/vbauerster/mpb/v6/decor" | |
| 7 | "github.com/vbauerster/mpb/v6/internal" | |
| 7 | 8 | ) |
| 8 | 9 | |
| 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. | |
| 10 | 11 | type BarOption func(*bState) |
| 11 | 12 | |
| 12 | 13 | func (s *bState) addDecorators(dest *[]decor.Decorator, decorators ...decor.Decorator) { |
| 45 | 46 | // BarWidth sets bar width independent of the container. |
| 46 | 47 | func BarWidth(width int) BarOption { |
| 47 | 48 | return func(s *bState) { |
| 48 | s.width = width | |
| 49 | s.reqWidth = width | |
| 49 | 50 | } |
| 50 | 51 | } |
| 51 | 52 | |
| 76 | 77 | |
| 77 | 78 | // BarFillerOnComplete replaces bar's filler with message, on complete event. |
| 78 | 79 | 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 | }) | |
| 82 | 89 | } |
| 83 | 90 | |
| 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 | } | |
| 92 | 96 | } |
| 93 | 97 | |
| 94 | 98 | // BarPriority sets bar's priority. Zero is highest priority, i.e. bar |
| 100 | 104 | } |
| 101 | 105 | } |
| 102 | 106 | |
| 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 { | |
| 107 | 110 | return nil |
| 108 | 111 | } |
| 109 | 112 | return func(s *bState) { |
| 110 | s.extender = makeExtFunc(extender) | |
| 113 | s.extender = makeExtenderFunc(filler) | |
| 111 | 114 | } |
| 112 | 115 | } |
| 113 | 116 | |
| 114 | func makeExtFunc(extender BarFiller) extFunc { | |
| 117 | func makeExtenderFunc(filler BarFiller) extenderFunc { | |
| 115 | 118 | 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")) | |
| 120 | 122 | } |
| 121 | 123 | } |
| 122 | 124 | |
| 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 { | |
| 125 | 127 | return func(s *bState) { |
| 126 | 128 | s.trimSpace = true |
| 127 | } | |
| 128 | } | |
| 129 | ||
| 130 | // BarStyle overrides mpb.DefaultBarStyle which is "[=>-]<+". | |
| 131 | // It's ok to pass string containing just 5 runes, for example "╢▌▌░╟", | |
| 132 | // if you don't need to override '<' (reverse tip) and '+' (refill rune). | |
| 133 | func BarStyle(style string) BarOption { | |
| 134 | if style == "" { | |
| 135 | return nil | |
| 136 | } | |
| 137 | type styleSetter interface { | |
| 138 | SetStyle(string) | |
| 139 | } | |
| 140 | return func(s *bState) { | |
| 141 | if t, ok := s.baseF.(styleSetter); ok { | |
| 142 | t.SetStyle(style) | |
| 143 | } | |
| 144 | 129 | } |
| 145 | 130 | } |
| 146 | 131 | |
| 152 | 137 | } |
| 153 | 138 | } |
| 154 | 139 | |
| 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)) | |
| 165 | 143 | } |
| 166 | 144 | |
| 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() { | |
| 200 | 149 | return option |
| 201 | 150 | } |
| 202 | 151 | return nil |
| 9 | 9 | "time" |
| 10 | 10 | "unicode/utf8" |
| 11 | 11 | |
| 12 | . "github.com/vbauerster/mpb/v5" | |
| 13 | "github.com/vbauerster/mpb/v5/decor" | |
| 12 | "github.com/vbauerster/mpb/v6" | |
| 13 | "github.com/vbauerster/mpb/v6/decor" | |
| 14 | 14 | ) |
| 15 | 15 | |
| 16 | 16 | func TestBarCompleted(t *testing.T) { |
| 17 | p := New(WithOutput(ioutil.Discard)) | |
| 17 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(ioutil.Discard)) | |
| 18 | 18 | total := 80 |
| 19 | 19 | bar := p.AddBar(int64(total)) |
| 20 | 20 | |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | func TestBarID(t *testing.T) { |
| 35 | p := New(WithOutput(ioutil.Discard)) | |
| 35 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(ioutil.Discard)) | |
| 36 | 36 | total := 100 |
| 37 | 37 | wantID := 11 |
| 38 | bar := p.AddBar(int64(total), BarID(wantID)) | |
| 39 | ||
| 40 | go func(total int) { | |
| 38 | bar := p.AddBar(int64(total), mpb.BarID(wantID)) | |
| 39 | ||
| 40 | go func() { | |
| 41 | 41 | for i := 0; i < total; i++ { |
| 42 | 42 | time.Sleep(50 * time.Millisecond) |
| 43 | 43 | bar.Increment() |
| 44 | 44 | } |
| 45 | }(total) | |
| 45 | }() | |
| 46 | 46 | |
| 47 | 47 | gotID := bar.ID() |
| 48 | 48 | if gotID != wantID { |
| 56 | 56 | func TestBarSetRefill(t *testing.T) { |
| 57 | 57 | var buf bytes.Buffer |
| 58 | 58 | |
| 59 | width := 100 | |
| 60 | p := New(WithOutput(&buf), WithWidth(width)) | |
| 59 | p := mpb.New(mpb.WithOutput(&buf), mpb.WithWidth(100)) | |
| 61 | 60 | |
| 62 | 61 | total := 100 |
| 63 | 62 | till := 30 |
| 64 | refillRune, _ := utf8.DecodeLastRuneInString(DefaultBarStyle) | |
| 65 | ||
| 66 | bar := p.AddBar(int64(total), TrimSpace()) | |
| 63 | refillRune, _ := utf8.DecodeLastRuneInString(mpb.BarDefaultStyle) | |
| 64 | ||
| 65 | bar := p.AddBar(int64(total), mpb.BarFillerTrim()) | |
| 67 | 66 | |
| 68 | 67 | bar.SetRefill(int64(till)) |
| 69 | 68 | bar.IncrBy(till) |
| 90 | 89 | func TestBarHas100PercentWithOnCompleteDecorator(t *testing.T) { |
| 91 | 90 | var buf bytes.Buffer |
| 92 | 91 | |
| 93 | p := New(WithOutput(&buf)) | |
| 92 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(&buf)) | |
| 94 | 93 | |
| 95 | 94 | total := 50 |
| 96 | 95 | |
| 97 | 96 | bar := p.AddBar(int64(total), |
| 98 | AppendDecorators( | |
| 97 | mpb.AppendDecorators( | |
| 99 | 98 | decor.OnComplete( |
| 100 | 99 | decor.Percentage(), "done", |
| 101 | 100 | ), |
| 118 | 117 | func TestBarHas100PercentWithBarRemoveOnComplete(t *testing.T) { |
| 119 | 118 | var buf bytes.Buffer |
| 120 | 119 | |
| 121 | p := New(WithOutput(&buf)) | |
| 120 | p := mpb.New(mpb.WithWidth(80), mpb.WithOutput(&buf)) | |
| 122 | 121 | |
| 123 | 122 | total := 50 |
| 124 | 123 | |
| 125 | 124 | bar := p.AddBar(int64(total), |
| 126 | BarRemoveOnComplete(), | |
| 127 | AppendDecorators(decor.Percentage()), | |
| 125 | mpb.BarRemoveOnComplete(), | |
| 126 | mpb.AppendDecorators(decor.Percentage()), | |
| 128 | 127 | ) |
| 129 | 128 | |
| 130 | 129 | for i := 0; i < total; i++ { |
| 143 | 142 | func TestBarStyle(t *testing.T) { |
| 144 | 143 | var buf bytes.Buffer |
| 145 | 144 | customFormat := "╢▌▌░╟" |
| 146 | p := New(WithOutput(&buf)) | |
| 147 | 145 | 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()) | |
| 149 | 148 | |
| 150 | 149 | for i := 0; i < total; i++ { |
| 151 | 150 | bar.Increment() |
| 169 | 168 | |
| 170 | 169 | func TestBarPanicBeforeComplete(t *testing.T) { |
| 171 | 170 | 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 | ) | |
| 173 | 176 | |
| 174 | 177 | total := 100 |
| 175 | 178 | panicMsg := "Upps!!!" |
| 176 | 179 | var pCount uint32 |
| 177 | 180 | 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 { | |
| 180 | 183 | if st.Current >= 42 { |
| 181 | 184 | atomic.AddUint32(&pCount, 1) |
| 182 | 185 | return true |
| 205 | 208 | |
| 206 | 209 | func TestBarPanicAfterComplete(t *testing.T) { |
| 207 | 210 | 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 | ) | |
| 209 | 216 | |
| 210 | 217 | total := 100 |
| 211 | 218 | panicMsg := "Upps!!!" |
| 212 | 219 | var pCount uint32 |
| 213 | 220 | 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 { | |
| 216 | 223 | if st.Completed { |
| 217 | 224 | atomic.AddUint32(&pCount, 1) |
| 218 | 225 | return true |
| 229 | 236 | |
| 230 | 237 | p.Wait() |
| 231 | 238 | |
| 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") | |
| 234 | 241 | } |
| 235 | 242 | |
| 236 | 243 | barStr := buf.String() |
| 239 | 246 | } |
| 240 | 247 | } |
| 241 | 248 | |
| 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 | } | |
| 3 | 3 | "io/ioutil" |
| 4 | 4 | "testing" |
| 5 | 5 | |
| 6 | "github.com/vbauerster/mpb/v5/decor" | |
| 6 | "github.com/vbauerster/mpb/v6/decor" | |
| 7 | 7 | ) |
| 8 | 8 | |
| 9 | 9 | func BenchmarkIncrSingleBar(b *testing.B) { |
| 10 | p := New(WithOutput(ioutil.Discard)) | |
| 10 | p := New(WithOutput(ioutil.Discard), WithWidth(80)) | |
| 11 | 11 | bar := p.AddBar(int64(b.N)) |
| 12 | 12 | for i := 0; i < b.N; i++ { |
| 13 | 13 | bar.Increment() |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | 17 | func BenchmarkIncrSingleBarWhileIsNotCompleted(b *testing.B) { |
| 18 | p := New(WithOutput(ioutil.Discard)) | |
| 18 | p := New(WithOutput(ioutil.Discard), WithWidth(80)) | |
| 19 | 19 | bar := p.AddBar(int64(b.N)) |
| 20 | 20 | for !bar.Completed() { |
| 21 | 21 | bar.Increment() |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | 25 | func BenchmarkIncrSingleBarWithNameDecorator(b *testing.B) { |
| 26 | p := New(WithOutput(ioutil.Discard)) | |
| 26 | p := New(WithOutput(ioutil.Discard), WithWidth(80)) | |
| 27 | 27 | bar := p.AddBar(int64(b.N), PrependDecorators(decor.Name("test"))) |
| 28 | 28 | for i := 0; i < b.N; i++ { |
| 29 | 29 | bar.Increment() |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | func BenchmarkIncrSingleBarWithNameAndEwmaETADecorator(b *testing.B) { |
| 34 | p := New(WithOutput(ioutil.Discard)) | |
| 34 | p := New(WithOutput(ioutil.Discard), WithWidth(80)) | |
| 35 | 35 | bar := p.AddBar(int64(b.N), |
| 36 | 36 | PrependDecorators(decor.Name("test")), |
| 37 | 37 | 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 | // +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 |
| 2 | 2 | import ( |
| 3 | 3 | "bytes" |
| 4 | 4 | "errors" |
| 5 | "fmt" | |
| 6 | 5 | "io" |
| 7 | 6 | "os" |
| 8 | ||
| 9 | "golang.org/x/crypto/ssh/terminal" | |
| 7 | "strconv" | |
| 10 | 8 | ) |
| 11 | 9 | |
| 12 | // NotATTY not a TeleTYpewriter error. | |
| 13 | var NotATTY = errors.New("not a terminal") | |
| 10 | // ErrNotTTY not a TeleTYpewriter error. | |
| 11 | var ErrNotTTY = errors.New("not a terminal") | |
| 14 | 12 | |
| 15 | var cuuAndEd = fmt.Sprintf("%c[%%dA%[1]c[J", 27) | |
| 13 | // http://ascii-table.com/ansi-escape-sequences.php | |
| 14 | const ( | |
| 15 | escOpen = "\x1b[" | |
| 16 | cuuAndEd = "A\x1b[J" | |
| 17 | ) | |
| 16 | 18 | |
| 17 | 19 | // Writer is a buffered the writer that updates the terminal. The |
| 18 | 20 | // contents of writer will be flushed when Flush is called. |
| 20 | 22 | out io.Writer |
| 21 | 23 | buf bytes.Buffer |
| 22 | 24 | lineCount int |
| 23 | fd uintptr | |
| 25 | fd int | |
| 24 | 26 | isTerminal bool |
| 25 | 27 | } |
| 26 | 28 | |
| 28 | 30 | func New(out io.Writer) *Writer { |
| 29 | 31 | w := &Writer{out: out} |
| 30 | 32 | if f, ok := out.(*os.File); ok { |
| 31 | w.fd = f.Fd() | |
| 32 | w.isTerminal = terminal.IsTerminal(int(w.fd)) | |
| 33 | w.fd = int(f.Fd()) | |
| 34 | w.isTerminal = IsTerminal(w.fd) | |
| 33 | 35 | } |
| 34 | 36 | return w |
| 35 | 37 | } |
| 36 | 38 | |
| 37 | 39 | // Flush flushes the underlying buffer. |
| 38 | 40 | func (w *Writer) Flush(lineCount int) (err error) { |
| 41 | // some terminals interpret 'cursor up 0' as 'cursor up 1' | |
| 39 | 42 | if w.lineCount > 0 { |
| 40 | w.clearLines() | |
| 43 | err = w.clearLines() | |
| 44 | if err != nil { | |
| 45 | return | |
| 46 | } | |
| 41 | 47 | } |
| 42 | 48 | w.lineCount = lineCount |
| 43 | 49 | _, err = w.buf.WriteTo(w.out) |
| 62 | 68 | |
| 63 | 69 | // GetWidth returns width of underlying terminal. |
| 64 | 70 | func (w *Writer) GetWidth() (int, error) { |
| 65 | if w.isTerminal { | |
| 66 | tw, _, err := terminal.GetSize(int(w.fd)) | |
| 67 | return tw, err | |
| 71 | if !w.isTerminal { | |
| 72 | return -1, ErrNotTTY | |
| 68 | 73 | } |
| 69 | return -1, NotATTY | |
| 74 | tw, _, err := GetSize(w.fd) | |
| 75 | return tw, err | |
| 70 | 76 | } |
| 77 | ||
| 78 | func (w *Writer) ansiCuuAndEd() (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 | } | |
| 1 | 1 | |
| 2 | 2 | package cwriter |
| 3 | 3 | |
| 4 | import "fmt" | |
| 4 | import ( | |
| 5 | "golang.org/x/sys/unix" | |
| 6 | ) | |
| 5 | 7 | |
| 6 | func (w *Writer) clearLines() { | |
| 7 | fmt.Fprintf(w.out, cuuAndEd, w.lineCount) | |
| 8 | func (w *Writer) clearLines() error { | |
| 9 | return w.ansiCuuAndEd() | |
| 8 | 10 | } |
| 11 | ||
| 12 | // GetSize returns the dimensions of the given terminal. | |
| 13 | func GetSize(fd int) (width, height int, err error) { | |
| 14 | ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) | |
| 15 | if err != nil { | |
| 16 | return -1, -1, err | |
| 17 | } | |
| 18 | return int(ws.Col), int(ws.Row), nil | |
| 19 | } | |
| 20 | ||
| 21 | // IsTerminal returns whether the given file descriptor is a terminal. | |
| 22 | func IsTerminal(fd int) bool { | |
| 23 | _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) | |
| 24 | return err == nil | |
| 25 | } |
| 2 | 2 | package cwriter |
| 3 | 3 | |
| 4 | 4 | import ( |
| 5 | "fmt" | |
| 6 | "syscall" | |
| 7 | 5 | "unsafe" |
| 6 | ||
| 7 | "golang.org/x/sys/windows" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") | |
| 10 | var kernel32 = windows.NewLazySystemDLL("kernel32.dll") | |
| 11 | 11 | |
| 12 | 12 | var ( |
| 13 | procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") | |
| 14 | 13 | procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") |
| 15 | 14 | procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") |
| 16 | procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") | |
| 17 | 15 | ) |
| 18 | 16 | |
| 19 | type coord struct { | |
| 20 | x int16 | |
| 21 | y int16 | |
| 17 | func (w *Writer) clearLines() error { | |
| 18 | if !w.isTerminal { | |
| 19 | // hope it's cygwin or similar | |
| 20 | return w.ansiCuuAndEd() | |
| 21 | } | |
| 22 | ||
| 23 | var info windows.ConsoleScreenBufferInfo | |
| 24 | if err := windows.GetConsoleScreenBufferInfo(windows.Handle(w.fd), &info); err != nil { | |
| 25 | return err | |
| 26 | } | |
| 27 | ||
| 28 | info.CursorPosition.Y -= int16(w.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 | |
| 22 | 51 | } |
| 23 | 52 | |
| 24 | type smallRect struct { | |
| 25 | left int16 | |
| 26 | top int16 | |
| 27 | right int16 | |
| 28 | bottom int16 | |
| 53 | // GetSize returns the visible dimensions of the given terminal. | |
| 54 | // | |
| 55 | // These dimensions don't include any scrollback buffer height. | |
| 56 | func GetSize(fd int) (width, height int, err error) { | |
| 57 | var info windows.ConsoleScreenBufferInfo | |
| 58 | if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { | |
| 59 | return 0, 0, err | |
| 60 | } | |
| 61 | // terminal.GetSize from crypto/ssh adds "+ 1" to both width and height: | |
| 62 | // https://go.googlesource.com/crypto/+/refs/heads/release-branch.go1.14/ssh/terminal/util_windows.go#75 | |
| 63 | // but looks like this is a root cause of issue #66, so removing both "+ 1" have fixed it. | |
| 64 | return int(info.Window.Right - info.Window.Left), int(info.Window.Bottom - info.Window.Top), nil | |
| 29 | 65 | } |
| 30 | 66 | |
| 31 | type consoleScreenBufferInfo struct { | |
| 32 | size coord | |
| 33 | cursorPosition coord | |
| 34 | attributes uint16 | |
| 35 | window smallRect | |
| 36 | maximumWindowSize coord | |
| 67 | // IsTerminal returns whether the given file descriptor is a terminal. | |
| 68 | func IsTerminal(fd int) bool { | |
| 69 | var st uint32 | |
| 70 | err := windows.GetConsoleMode(windows.Handle(fd), &st) | |
| 71 | return err == nil | |
| 37 | 72 | } |
| 38 | ||
| 39 | func (w *Writer) clearLines() { | |
| 40 | if !w.isTerminal { | |
| 41 | fmt.Fprintf(w.out, cuuAndEd, w.lineCount) | |
| 42 | } | |
| 43 | var info consoleScreenBufferInfo | |
| 44 | procGetConsoleScreenBufferInfo.Call(w.fd, uintptr(unsafe.Pointer(&info))) | |
| 45 | ||
| 46 | info.cursorPosition.y -= int16(w.lineCount) | |
| 47 | if info.cursorPosition.y < 0 { | |
| 48 | info.cursorPosition.y = 0 | |
| 49 | } | |
| 50 | procSetConsoleCursorPosition.Call(w.fd, uintptr(uint32(uint16(info.cursorPosition.y))<<16|uint32(uint16(info.cursorPosition.x)))) | |
| 51 | ||
| 52 | // clear the lines | |
| 53 | cursor := coord{ | |
| 54 | x: info.window.left, | |
| 55 | y: info.cursorPosition.y, | |
| 56 | } | |
| 57 | count := uint32(info.size.x) * uint32(w.lineCount) | |
| 58 | procFillConsoleOutputCharacter.Call(w.fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(new(uint32)))) | |
| 59 | } |
| 0 | 0 | package decor |
| 1 | 1 | |
| 2 | 2 | // Any decorator displays text, that can be changed during decorator's |
| 3 | // lifetime via provided func call back. | |
| 3 | // lifetime via provided DecorFunc. | |
| 4 | 4 | // |
| 5 | // `f` call back which provides string to display | |
| 5 | // `fn` DecorFunc callback | |
| 6 | 6 | // |
| 7 | 7 | // `wcc` optional WC config |
| 8 | 8 | // |
| 9 | func Any(f func(*Statistics) string, wcc ...WC) Decorator { | |
| 10 | return &any{initWC(wcc...), f} | |
| 9 | func Any(fn DecorFunc, wcc ...WC) Decorator { | |
| 10 | return &any{initWC(wcc...), fn} | |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | 13 | type any struct { |
| 14 | 14 | WC |
| 15 | f func(*Statistics) string | |
| 15 | fn DecorFunc | |
| 16 | 16 | } |
| 17 | 17 | |
| 18 | func (d *any) Decor(s *Statistics) string { | |
| 19 | return d.FormatMsg(d.f(s)) | |
| 18 | func (d *any) Decor(s Statistics) string { | |
| 19 | return d.FormatMsg(d.fn(s)) | |
| 20 | 20 | } |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | "strings" | |
| 4 | 5 | ) |
| 5 | 6 | |
| 6 | 7 | const ( |
| 30 | 31 | // |
| 31 | 32 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit |
| 32 | 33 | // |
| 33 | // `pairFmt` printf compatible verbs for current and total, like "%f" or "%d" | |
| 34 | // `pairFmt` printf compatible verbs for current and total pair | |
| 34 | 35 | // |
| 35 | 36 | // `wcc` optional WC config |
| 36 | 37 | // |
| 42 | 43 | // pairFmt="% d / % d" output: "1 MB / 12 MB" |
| 43 | 44 | // |
| 44 | 45 | func Counters(unit int, pairFmt string, wcc ...WC) Decorator { |
| 45 | return Any(chooseSizeProducer(unit, pairFmt), wcc...) | |
| 46 | } | |
| 47 | ||
| 48 | func chooseSizeProducer(unit int, format string) func(*Statistics) string { | |
| 49 | if format == "" { | |
| 50 | format = "%d / %d" | |
| 51 | } | |
| 52 | switch unit { | |
| 53 | case UnitKiB: | |
| 54 | return func(s *Statistics) string { | |
| 55 | return fmt.Sprintf(format, SizeB1024(s.Current), SizeB1024(s.Total)) | |
| 56 | } | |
| 57 | case UnitKB: | |
| 58 | return func(s *Statistics) string { | |
| 59 | return fmt.Sprintf(format, SizeB1000(s.Current), SizeB1000(s.Total)) | |
| 60 | } | |
| 61 | default: | |
| 62 | return func(s *Statistics) string { | |
| 63 | return fmt.Sprintf(format, s.Current, s.Total) | |
| 64 | } | |
| 65 | } | |
| 66 | } | |
| 46 | producer := func(unit int, pairFmt string) DecorFunc { | |
| 47 | if pairFmt == "" { | |
| 48 | pairFmt = "%d / %d" | |
| 49 | } else if strings.Count(pairFmt, "%") != 2 { | |
| 50 | panic("expected pairFmt with exactly 2 verbs") | |
| 51 | } | |
| 52 | switch unit { | |
| 53 | case UnitKiB: | |
| 54 | return func(s Statistics) string { | |
| 55 | return fmt.Sprintf(pairFmt, SizeB1024(s.Current), SizeB1024(s.Total)) | |
| 56 | } | |
| 57 | case UnitKB: | |
| 58 | return func(s Statistics) string { | |
| 59 | return fmt.Sprintf(pairFmt, SizeB1000(s.Current), SizeB1000(s.Total)) | |
| 60 | } | |
| 61 | default: | |
| 62 | return func(s Statistics) string { | |
| 63 | return fmt.Sprintf(pairFmt, s.Current, s.Total) | |
| 64 | } | |
| 65 | } | |
| 66 | } | |
| 67 | return Any(producer(unit, pairFmt), wcc...) | |
| 68 | } | |
| 69 | ||
| 70 | // TotalNoUnit is a wrapper around Total with no unit param. | |
| 71 | func TotalNoUnit(format string, wcc ...WC) Decorator { | |
| 72 | return Total(0, format, wcc...) | |
| 73 | } | |
| 74 | ||
| 75 | // TotalKibiByte is a wrapper around Total with predefined unit | |
| 76 | // UnitKiB (bytes/1024). | |
| 77 | func TotalKibiByte(format string, wcc ...WC) Decorator { | |
| 78 | return Total(UnitKiB, format, wcc...) | |
| 79 | } | |
| 80 | ||
| 81 | // TotalKiloByte is a wrapper around Total with predefined unit | |
| 82 | // UnitKB (bytes/1000). | |
| 83 | func TotalKiloByte(format string, wcc ...WC) Decorator { | |
| 84 | return Total(UnitKB, format, wcc...) | |
| 85 | } | |
| 86 | ||
| 87 | // Total decorator with dynamic unit measure adjustment. | |
| 88 | // | |
| 89 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit | |
| 90 | // | |
| 91 | // `format` printf compatible verb for Total | |
| 92 | // | |
| 93 | // `wcc` optional WC config | |
| 94 | // | |
| 95 | // format example if unit=UnitKiB: | |
| 96 | // | |
| 97 | // format="%.1f" output: "12.0MiB" | |
| 98 | // format="% .1f" output: "12.0 MiB" | |
| 99 | // format="%d" output: "12MiB" | |
| 100 | // format="% d" output: "12 MiB" | |
| 101 | // | |
| 102 | func Total(unit int, format string, wcc ...WC) Decorator { | |
| 103 | producer := func(unit int, format string) DecorFunc { | |
| 104 | if format == "" { | |
| 105 | format = "%d" | |
| 106 | } else if strings.Count(format, "%") != 1 { | |
| 107 | panic("expected format with exactly 1 verb") | |
| 108 | } | |
| 109 | ||
| 110 | switch unit { | |
| 111 | case UnitKiB: | |
| 112 | return func(s Statistics) string { | |
| 113 | return fmt.Sprintf(format, SizeB1024(s.Total)) | |
| 114 | } | |
| 115 | case UnitKB: | |
| 116 | return func(s Statistics) string { | |
| 117 | return fmt.Sprintf(format, SizeB1000(s.Total)) | |
| 118 | } | |
| 119 | default: | |
| 120 | return func(s Statistics) string { | |
| 121 | return fmt.Sprintf(format, s.Total) | |
| 122 | } | |
| 123 | } | |
| 124 | } | |
| 125 | return Any(producer(unit, format), wcc...) | |
| 126 | } | |
| 127 | ||
| 128 | // CurrentNoUnit is a wrapper around Current with no unit param. | |
| 129 | func CurrentNoUnit(format string, wcc ...WC) Decorator { | |
| 130 | return Current(0, format, wcc...) | |
| 131 | } | |
| 132 | ||
| 133 | // CurrentKibiByte is a wrapper around Current with predefined unit | |
| 134 | // UnitKiB (bytes/1024). | |
| 135 | func CurrentKibiByte(format string, wcc ...WC) Decorator { | |
| 136 | return Current(UnitKiB, format, wcc...) | |
| 137 | } | |
| 138 | ||
| 139 | // CurrentKiloByte is a wrapper around Current with predefined unit | |
| 140 | // UnitKB (bytes/1000). | |
| 141 | func CurrentKiloByte(format string, wcc ...WC) Decorator { | |
| 142 | return Current(UnitKB, format, wcc...) | |
| 143 | } | |
| 144 | ||
| 145 | // Current decorator with dynamic unit measure adjustment. | |
| 146 | // | |
| 147 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit | |
| 148 | // | |
| 149 | // `format` printf compatible verb for Current | |
| 150 | // | |
| 151 | // `wcc` optional WC config | |
| 152 | // | |
| 153 | // format example if unit=UnitKiB: | |
| 154 | // | |
| 155 | // format="%.1f" output: "12.0MiB" | |
| 156 | // format="% .1f" output: "12.0 MiB" | |
| 157 | // format="%d" output: "12MiB" | |
| 158 | // format="% d" output: "12 MiB" | |
| 159 | // | |
| 160 | func Current(unit int, format string, wcc ...WC) Decorator { | |
| 161 | producer := func(unit int, format string) DecorFunc { | |
| 162 | if format == "" { | |
| 163 | format = "%d" | |
| 164 | } else if strings.Count(format, "%") != 1 { | |
| 165 | panic("expected format with exactly 1 verb") | |
| 166 | } | |
| 167 | ||
| 168 | switch unit { | |
| 169 | case UnitKiB: | |
| 170 | return func(s Statistics) string { | |
| 171 | return fmt.Sprintf(format, SizeB1024(s.Current)) | |
| 172 | } | |
| 173 | case UnitKB: | |
| 174 | return func(s Statistics) string { | |
| 175 | return fmt.Sprintf(format, SizeB1000(s.Current)) | |
| 176 | } | |
| 177 | default: | |
| 178 | return func(s Statistics) string { | |
| 179 | return fmt.Sprintf(format, s.Current) | |
| 180 | } | |
| 181 | } | |
| 182 | } | |
| 183 | return Any(producer(unit, format), wcc...) | |
| 184 | } | |
| 185 | ||
| 186 | // InvertedCurrentNoUnit is a wrapper around InvertedCurrent with no unit param. | |
| 187 | func InvertedCurrentNoUnit(format string, wcc ...WC) Decorator { | |
| 188 | return InvertedCurrent(0, format, wcc...) | |
| 189 | } | |
| 190 | ||
| 191 | // InvertedCurrentKibiByte is a wrapper around InvertedCurrent with predefined unit | |
| 192 | // UnitKiB (bytes/1024). | |
| 193 | func InvertedCurrentKibiByte(format string, wcc ...WC) Decorator { | |
| 194 | return InvertedCurrent(UnitKiB, format, wcc...) | |
| 195 | } | |
| 196 | ||
| 197 | // InvertedCurrentKiloByte is a wrapper around InvertedCurrent with predefined unit | |
| 198 | // UnitKB (bytes/1000). | |
| 199 | func InvertedCurrentKiloByte(format string, wcc ...WC) Decorator { | |
| 200 | return InvertedCurrent(UnitKB, format, wcc...) | |
| 201 | } | |
| 202 | ||
| 203 | // InvertedCurrent decorator with dynamic unit measure adjustment. | |
| 204 | // | |
| 205 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit | |
| 206 | // | |
| 207 | // `format` printf compatible verb for InvertedCurrent | |
| 208 | // | |
| 209 | // `wcc` optional WC config | |
| 210 | // | |
| 211 | // format example if unit=UnitKiB: | |
| 212 | // | |
| 213 | // format="%.1f" output: "12.0MiB" | |
| 214 | // format="% .1f" output: "12.0 MiB" | |
| 215 | // format="%d" output: "12MiB" | |
| 216 | // format="% d" output: "12 MiB" | |
| 217 | // | |
| 218 | func InvertedCurrent(unit int, format string, wcc ...WC) Decorator { | |
| 219 | producer := func(unit int, format string) DecorFunc { | |
| 220 | if format == "" { | |
| 221 | format = "%d" | |
| 222 | } else if strings.Count(format, "%") != 1 { | |
| 223 | panic("expected format with exactly 1 verb") | |
| 224 | } | |
| 225 | ||
| 226 | switch unit { | |
| 227 | case UnitKiB: | |
| 228 | return func(s Statistics) string { | |
| 229 | return fmt.Sprintf(format, SizeB1024(s.Total-s.Current)) | |
| 230 | } | |
| 231 | case UnitKB: | |
| 232 | return func(s Statistics) string { | |
| 233 | return fmt.Sprintf(format, SizeB1000(s.Total-s.Current)) | |
| 234 | } | |
| 235 | default: | |
| 236 | return func(s Statistics) string { | |
| 237 | return fmt.Sprintf(format, s.Total-s.Current) | |
| 238 | } | |
| 239 | } | |
| 240 | } | |
| 241 | return Any(producer(unit, format), wcc...) | |
| 242 | } | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | 4 | "time" |
| 5 | "unicode/utf8" | |
| 6 | 5 | |
| 7 | 6 | "github.com/acarl005/stripansi" |
| 7 | "github.com/mattn/go-runewidth" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | 10 | const ( |
| 46 | 46 | // Statistics consists of progress related statistics, that Decorator |
| 47 | 47 | // may need. |
| 48 | 48 | type Statistics struct { |
| 49 | ID int | |
| 50 | Completed bool | |
| 51 | Total int64 | |
| 52 | Current int64 | |
| 49 | ID int | |
| 50 | AvailableWidth int | |
| 51 | Total int64 | |
| 52 | Current int64 | |
| 53 | Refill int64 | |
| 54 | Completed bool | |
| 53 | 55 | } |
| 54 | 56 | |
| 55 | 57 | // 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`. | |
| 59 | 65 | type Decorator interface { |
| 60 | 66 | Configurator |
| 61 | 67 | Synchronizer |
| 62 | Decor(*Statistics) string | |
| 68 | Decor(Statistics) string | |
| 63 | 69 | } |
| 70 | ||
| 71 | // DecorFunc func type. | |
| 72 | // To be used with `func Any`(DecorFunc, ...WC) Decorator`. | |
| 73 | type DecorFunc func(Statistics) string | |
| 64 | 74 | |
| 65 | 75 | // Synchronizer interface. |
| 66 | 76 | // All decorators implement this interface implicitly. Its Sync |
| 116 | 126 | // W represents width and C represents bit set of width related config. |
| 117 | 127 | // A decorator should embed WC, to enable width synchronization. |
| 118 | 128 | 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 | |
| 123 | 133 | } |
| 124 | 134 | |
| 125 | 135 | // FormatMsg formats final message according to WC.W and WC.C. |
| 126 | 136 | // Should be called by any Decorator implementation. |
| 127 | 137 | 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 | |
| 131 | 141 | if (wc.C & DSyncWidth) != 0 { |
| 142 | cellCount := stripWidth | |
| 132 | 143 | if (wc.C & DextraSpace) != 0 { |
| 133 | runeCount++ | |
| 144 | cellCount++ | |
| 134 | 145 | } |
| 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 | |
| 140 | 148 | } |
| 141 | return fmt.Sprintf(format, msg) | |
| 149 | return wc.fill(msg, maxCell+(pureWidth-stripWidth)) | |
| 142 | 150 | } |
| 143 | 151 | |
| 144 | 152 | // Init initializes width related config. |
| 145 | 153 | func (wc *WC) Init() WC { |
| 146 | wc.dynFormat = "%%" | |
| 154 | wc.fill = runewidth.FillLeft | |
| 147 | 155 | if (wc.C & DidentRight) != 0 { |
| 148 | wc.dynFormat += "-" | |
| 156 | wc.fill = runewidth.FillRight | |
| 149 | 157 | } |
| 150 | wc.dynFormat += "%ds" | |
| 151 | 158 | if (wc.C & DSyncWidth) != 0 { |
| 152 | 159 | // it's deliberate choice to override wsync on each Init() call, |
| 153 | 160 | // this way globals like WCSyncSpace can be reused |
| 0 | // Package decor provides common decorators for "github.com/vbauerster/mpb/v6" module. | |
| 0 | 1 | /* |
| 1 | Package decor provides common decorators for "github.com/vbauerster/mpb/v5" module. | |
| 2 | ||
| 3 | 2 | Some decorators returned by this package might have a closure state. It is ok to use |
| 4 | 3 | decorators concurrently, unless you share the same decorator among multiple |
| 5 | 4 | *mpb.Bar instances. To avoid data races, create new decorator per *mpb.Bar instance. |
| 24 | 24 | func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator { |
| 25 | 25 | var msg string |
| 26 | 26 | producer := chooseTimeProducer(style) |
| 27 | f := func(s *Statistics) string { | |
| 27 | fn := func(s Statistics) string { | |
| 28 | 28 | if !s.Completed { |
| 29 | 29 | msg = producer(time.Since(startTime)) |
| 30 | 30 | } |
| 31 | 31 | return msg |
| 32 | 32 | } |
| 33 | return Any(f, wcc...) | |
| 33 | return Any(fn, wcc...) | |
| 34 | 34 | } |
| 62 | 62 | producer func(time.Duration) string |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | func (d *movingAverageETA) Decor(s *Statistics) string { | |
| 65 | func (d *movingAverageETA) Decor(s Statistics) string { | |
| 66 | 66 | v := math.Round(d.average.Value()) |
| 67 | 67 | remaining := time.Duration((s.Total - s.Current) * int64(v)) |
| 68 | 68 | if d.normalizer != nil { |
| 116 | 116 | producer func(time.Duration) string |
| 117 | 117 | } |
| 118 | 118 | |
| 119 | func (d *averageETA) Decor(s *Statistics) string { | |
| 119 | func (d *averageETA) Decor(s Statistics) string { | |
| 120 | 120 | var remaining time.Duration |
| 121 | 121 | if s.Current != 0 { |
| 122 | 122 | durPerItem := float64(time.Since(d.startTime)) / float64(s.Current) |
| 0 | 0 | package decor |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | "fmt" | |
| 4 | 3 | "strings" |
| 5 | "unicode/utf8" | |
| 4 | ||
| 5 | "github.com/acarl005/stripansi" | |
| 6 | "github.com/mattn/go-runewidth" | |
| 6 | 7 | ) |
| 7 | 8 | |
| 8 | 9 | // Merge wraps its decorator argument with intention to sync width |
| 63 | 64 | return d.Decorator |
| 64 | 65 | } |
| 65 | 66 | |
| 66 | func (d *mergeDecorator) Decor(s *Statistics) string { | |
| 67 | func (d *mergeDecorator) Decor(s Statistics) string { | |
| 67 | 68 | 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 | |
| 69 | 72 | if (d.wc.C & DextraSpace) != 0 { |
| 70 | msgLen++ | |
| 73 | cellCount++ | |
| 71 | 74 | } |
| 72 | 75 | |
| 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) | |
| 78 | 79 | |
| 79 | 80 | var diff int |
| 80 | 81 | for i := 1; i < len(d.placeHolders); i++ { |
| 86 | 87 | width = 0 |
| 87 | 88 | } |
| 88 | 89 | } |
| 89 | max = utf8.RuneCountInString(ph.FormatMsg(strings.Repeat(" ", width))) | |
| 90 | max := runewidth.StringWidth(ph.FormatMsg(strings.Repeat(" ", width))) | |
| 90 | 91 | total += max |
| 91 | 92 | diff = max - pw |
| 92 | 93 | } |
| 93 | 94 | |
| 94 | 95 | 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)) | |
| 97 | 98 | } |
| 98 | 99 | |
| 99 | 100 | type placeHolderDecorator struct { |
| 100 | 101 | WC |
| 101 | 102 | } |
| 102 | 103 | |
| 103 | func (d *placeHolderDecorator) Decor(*Statistics) string { | |
| 104 | func (d *placeHolderDecorator) Decor(Statistics) string { | |
| 104 | 105 | return "" |
| 105 | 106 | } |
| 7 | 7 | // `wcc` optional WC config |
| 8 | 8 | // |
| 9 | 9 | func Name(str string, wcc ...WC) Decorator { |
| 10 | return Any(func(*Statistics) string { return str }, wcc...) | |
| 10 | return Any(func(Statistics) string { return str }, wcc...) | |
| 11 | 11 | } |
| 23 | 23 | msg string |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | func (d *onCompleteWrapper) Decor(s *Statistics) string { | |
| 26 | func (d *onCompleteWrapper) Decor(s Statistics) string { | |
| 27 | 27 | if s.Completed { |
| 28 | 28 | wc := d.GetConf() |
| 29 | 29 | return wc.FormatMsg(d.msg) |
| 4 | 4 | "io" |
| 5 | 5 | "strconv" |
| 6 | 6 | |
| 7 | "github.com/vbauerster/mpb/v5/internal" | |
| 7 | "github.com/vbauerster/mpb/v6/internal" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | 10 | type percentageType float64 |
| 49 | 49 | if format == "" { |
| 50 | 50 | format = "% d" |
| 51 | 51 | } |
| 52 | f := func(s *Statistics) string { | |
| 52 | f := func(s Statistics) string { | |
| 53 | 53 | p := internal.Percentage(s.Total, s.Current, 100) |
| 54 | 54 | return fmt.Sprintf(format, percentageType(p)) |
| 55 | 55 | } |
| 77 | 77 | msg string |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | func (d *movingAverageSpeed) Decor(s *Statistics) string { | |
| 80 | func (d *movingAverageSpeed) Decor(s Statistics) string { | |
| 81 | 81 | if !s.Completed { |
| 82 | 82 | var speed float64 |
| 83 | 83 | if v := d.average.Value(); v > 0 { |
| 139 | 139 | msg string |
| 140 | 140 | } |
| 141 | 141 | |
| 142 | func (d *averageSpeed) Decor(s *Statistics) string { | |
| 142 | func (d *averageSpeed) Decor(s Statistics) string { | |
| 143 | 143 | if !s.Completed { |
| 144 | 144 | speed := float64(s.Current) / float64(time.Since(d.startTime)) |
| 145 | 145 | d.msg = d.producer(speed * 1e9) |
| 121 | 121 | for _, tc := range cases { |
| 122 | 122 | t.Run(tc.name, func(t *testing.T) { |
| 123 | 123 | decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) |
| 124 | stat := &Statistics{ | |
| 124 | stat := Statistics{ | |
| 125 | 125 | Current: tc.current, |
| 126 | 126 | } |
| 127 | 127 | res := decor.Decor(stat) |
| 249 | 249 | for _, tc := range cases { |
| 250 | 250 | t.Run(tc.name, func(t *testing.T) { |
| 251 | 251 | decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) |
| 252 | stat := &Statistics{ | |
| 252 | stat := Statistics{ | |
| 253 | 253 | Current: tc.current, |
| 254 | 254 | } |
| 255 | 255 | res := decor.Decor(stat) |
| 11 | 11 | frames = defaultSpinnerStyle |
| 12 | 12 | } |
| 13 | 13 | var count uint |
| 14 | f := func(s *Statistics) string { | |
| 14 | f := func(s Statistics) string { | |
| 15 | 15 | frame := frames[count%uint(len(frames))] |
| 16 | 16 | count++ |
| 17 | 17 | return frame |
| 3 | 3 | "sync" |
| 4 | 4 | "testing" |
| 5 | 5 | |
| 6 | . "github.com/vbauerster/mpb/v5" | |
| 7 | "github.com/vbauerster/mpb/v5/decor" | |
| 6 | "github.com/vbauerster/mpb/v6" | |
| 7 | "github.com/vbauerster/mpb/v6/decor" | |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | 10 | func TestNameDecorator(t *testing.T) { |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | for _, test := range tests { |
| 34 | got := test.decorator.Decor(new(decor.Statistics)) | |
| 34 | got := test.decorator.Decor(decor.Statistics{}) | |
| 35 | 35 | if got != test.want { |
| 36 | 36 | t.Errorf("Want: %q, Got: %q\n", test.want, got) |
| 37 | 37 | } |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | 41 | type step struct { |
| 42 | stat *decor.Statistics | |
| 42 | stat decor.Statistics | |
| 43 | 43 | decorator decor.Decorator |
| 44 | 44 | want string |
| 45 | 45 | } |
| 49 | 49 | testCases := [][]step{ |
| 50 | 50 | { |
| 51 | 51 | { |
| 52 | &decor.Statistics{Total: 100, Current: 8}, | |
| 52 | decor.Statistics{Total: 100, Current: 8}, | |
| 53 | 53 | decor.Percentage(decor.WCSyncWidth), |
| 54 | 54 | "8 %", |
| 55 | 55 | }, |
| 56 | 56 | { |
| 57 | &decor.Statistics{Total: 100, Current: 9}, | |
| 57 | decor.Statistics{Total: 100, Current: 9}, | |
| 58 | 58 | decor.Percentage(decor.WCSyncWidth), |
| 59 | 59 | "9 %", |
| 60 | 60 | }, |
| 61 | 61 | }, |
| 62 | 62 | { |
| 63 | 63 | { |
| 64 | &decor.Statistics{Total: 100, Current: 9}, | |
| 64 | decor.Statistics{Total: 100, Current: 9}, | |
| 65 | 65 | decor.Percentage(decor.WCSyncWidth), |
| 66 | 66 | " 9 %", |
| 67 | 67 | }, |
| 68 | 68 | { |
| 69 | &decor.Statistics{Total: 100, Current: 10}, | |
| 69 | decor.Statistics{Total: 100, Current: 10}, | |
| 70 | 70 | decor.Percentage(decor.WCSyncWidth), |
| 71 | 71 | "10 %", |
| 72 | 72 | }, |
| 73 | 73 | }, |
| 74 | 74 | { |
| 75 | 75 | { |
| 76 | &decor.Statistics{Total: 100, Current: 9}, | |
| 76 | decor.Statistics{Total: 100, Current: 9}, | |
| 77 | 77 | decor.Percentage(decor.WCSyncWidth), |
| 78 | 78 | " 9 %", |
| 79 | 79 | }, |
| 80 | 80 | { |
| 81 | &decor.Statistics{Total: 100, Current: 100}, | |
| 81 | decor.Statistics{Total: 100, Current: 100}, | |
| 82 | 82 | decor.Percentage(decor.WCSyncWidth), |
| 83 | 83 | "100 %", |
| 84 | 84 | }, |
| 93 | 93 | testCases := [][]step{ |
| 94 | 94 | { |
| 95 | 95 | { |
| 96 | &decor.Statistics{Total: 100, Current: 8}, | |
| 96 | decor.Statistics{Total: 100, Current: 8}, | |
| 97 | 97 | decor.Percentage(decor.WCSyncWidthR), |
| 98 | 98 | "8 %", |
| 99 | 99 | }, |
| 100 | 100 | { |
| 101 | &decor.Statistics{Total: 100, Current: 9}, | |
| 101 | decor.Statistics{Total: 100, Current: 9}, | |
| 102 | 102 | decor.Percentage(decor.WCSyncWidthR), |
| 103 | 103 | "9 %", |
| 104 | 104 | }, |
| 105 | 105 | }, |
| 106 | 106 | { |
| 107 | 107 | { |
| 108 | &decor.Statistics{Total: 100, Current: 9}, | |
| 108 | decor.Statistics{Total: 100, Current: 9}, | |
| 109 | 109 | decor.Percentage(decor.WCSyncWidthR), |
| 110 | 110 | "9 % ", |
| 111 | 111 | }, |
| 112 | 112 | { |
| 113 | &decor.Statistics{Total: 100, Current: 10}, | |
| 113 | decor.Statistics{Total: 100, Current: 10}, | |
| 114 | 114 | decor.Percentage(decor.WCSyncWidthR), |
| 115 | 115 | "10 %", |
| 116 | 116 | }, |
| 117 | 117 | }, |
| 118 | 118 | { |
| 119 | 119 | { |
| 120 | &decor.Statistics{Total: 100, Current: 9}, | |
| 120 | decor.Statistics{Total: 100, Current: 9}, | |
| 121 | 121 | decor.Percentage(decor.WCSyncWidthR), |
| 122 | 122 | "9 % ", |
| 123 | 123 | }, |
| 124 | 124 | { |
| 125 | &decor.Statistics{Total: 100, Current: 100}, | |
| 125 | decor.Statistics{Total: 100, Current: 100}, | |
| 126 | 126 | decor.Percentage(decor.WCSyncWidthR), |
| 127 | 127 | "100 %", |
| 128 | 128 | }, |
| 137 | 137 | testCases := [][]step{ |
| 138 | 138 | { |
| 139 | 139 | { |
| 140 | &decor.Statistics{Total: 100, Current: 8}, | |
| 140 | decor.Statistics{Total: 100, Current: 8}, | |
| 141 | 141 | decor.Percentage(decor.WCSyncSpace), |
| 142 | 142 | " 8 %", |
| 143 | 143 | }, |
| 144 | 144 | { |
| 145 | &decor.Statistics{Total: 100, Current: 9}, | |
| 145 | decor.Statistics{Total: 100, Current: 9}, | |
| 146 | 146 | decor.Percentage(decor.WCSyncSpace), |
| 147 | 147 | " 9 %", |
| 148 | 148 | }, |
| 149 | 149 | }, |
| 150 | 150 | { |
| 151 | 151 | { |
| 152 | &decor.Statistics{Total: 100, Current: 9}, | |
| 152 | decor.Statistics{Total: 100, Current: 9}, | |
| 153 | 153 | decor.Percentage(decor.WCSyncSpace), |
| 154 | 154 | " 9 %", |
| 155 | 155 | }, |
| 156 | 156 | { |
| 157 | &decor.Statistics{Total: 100, Current: 10}, | |
| 157 | decor.Statistics{Total: 100, Current: 10}, | |
| 158 | 158 | decor.Percentage(decor.WCSyncSpace), |
| 159 | 159 | " 10 %", |
| 160 | 160 | }, |
| 161 | 161 | }, |
| 162 | 162 | { |
| 163 | 163 | { |
| 164 | &decor.Statistics{Total: 100, Current: 9}, | |
| 164 | decor.Statistics{Total: 100, Current: 9}, | |
| 165 | 165 | decor.Percentage(decor.WCSyncSpace), |
| 166 | 166 | " 9 %", |
| 167 | 167 | }, |
| 168 | 168 | { |
| 169 | &decor.Statistics{Total: 100, Current: 100}, | |
| 169 | decor.Statistics{Total: 100, Current: 100}, | |
| 170 | 170 | decor.Percentage(decor.WCSyncSpace), |
| 171 | 171 | " 100 %", |
| 172 | 172 | }, |
| 181 | 181 | t.Fail() |
| 182 | 182 | } |
| 183 | 183 | |
| 184 | numBars := len(testCases[0]) | |
| 185 | var wg sync.WaitGroup | |
| 186 | 184 | for _, columnCase := range testCases { |
| 185 | mpb.SyncWidth(toSyncMatrix(columnCase)) | |
| 186 | numBars := len(columnCase) | |
| 187 | gott := make([]chan string, numBars) | |
| 188 | wg := new(sync.WaitGroup) | |
| 187 | 189 | wg.Add(numBars) |
| 188 | SyncWidth(toSyncMatrix(columnCase)) | |
| 189 | gott := make([]chan string, numBars) | |
| 190 | for i := 0; i < numBars; i++ { | |
| 191 | gott[i] = make(chan string, 1) | |
| 192 | go func(s step, ch chan string) { | |
| 190 | for i, step := range columnCase { | |
| 191 | step := step | |
| 192 | ch := make(chan string, 1) | |
| 193 | go func() { | |
| 193 | 194 | defer wg.Done() |
| 194 | ch <- s.decorator.Decor(s.stat) | |
| 195 | }(columnCase[i], gott[i]) | |
| 195 | ch <- step.decorator.Decor(step.stat) | |
| 196 | }() | |
| 197 | gott[i] = ch | |
| 196 | 198 | } |
| 197 | 199 | wg.Wait() |
| 198 | 200 | |
| 8 | 8 | func TestDraw(t *testing.T) { |
| 9 | 9 | // key is termWidth |
| 10 | 10 | testSuite := map[int][]struct { |
| 11 | name string | |
| 12 | total, current int64 | |
| 13 | barWidth int | |
| 14 | trimSpace bool | |
| 15 | reverse bool | |
| 16 | rup int64 | |
| 17 | want string | |
| 11 | 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 | |
| 18 | 20 | }{ |
| 19 | 21 | 0: { |
| 20 | 22 | { |
| 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}", | |
| 22 | 172 | total: 60, |
| 23 | 173 | 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", | |
| 39 | 179 | total: 60, |
| 40 | 180 | 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: "[==================>---------------------------------------]", | |
| 187 | 184 | }, |
| 188 | 185 | }, |
| 189 | 186 | 100: { |
| 190 | 187 | { |
| 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: "[+>--------------------------------------------------------]", | |
| 317 | 341 | }, |
| 318 | 342 | }, |
| 319 | 343 | } |
| 320 | 344 | |
| 321 | 345 | var tmpBuf bytes.Buffer |
| 322 | for termWidth, cases := range testSuite { | |
| 346 | for tw, cases := range testSuite { | |
| 323 | 347 | 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 | |
| 326 | 350 | s.total = tc.total |
| 327 | 351 | 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() | |
| 333 | 361 | } |
| 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) | |
| 344 | 362 | 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)) | |
| 346 | 364 | } |
| 347 | 365 | } |
| 348 | 366 | } |
| 349 | 367 | } |
| 350 | 368 | |
| 351 | func newTestState(reverse bool) *bState { | |
| 369 | func newTestState(style string, rev bool) *bState { | |
| 352 | 370 | s := &bState{ |
| 353 | filler: NewBarFiller(DefaultBarStyle, reverse), | |
| 371 | filler: NewBarFillerPick(style, rev), | |
| 354 | 372 | bufP: new(bytes.Buffer), |
| 355 | 373 | bufB: new(bytes.Buffer), |
| 356 | 374 | bufA: new(bytes.Buffer), |
| 6 | 6 | "math/rand" |
| 7 | 7 | "time" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 10 | "github.com/vbauerster/mpb/v5/decor" | |
| 9 | "github.com/vbauerster/mpb/v6" | |
| 10 | "github.com/vbauerster/mpb/v6/decor" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | func Example() { |
| 17 | 17 | total := 100 |
| 18 | 18 | name := "Single Bar:" |
| 19 | 19 | // adding a single bar, which will inherit container's width |
| 20 | bar := p.AddBar(int64(total), | |
| 21 | // override DefaultBarStyle, which is "[=>-]<+" | |
| 22 | mpb.BarStyle("╢▌▌░╟"), | |
| 20 | bar := p.Add(int64(total), | |
| 21 | // progress bar filler with customized style | |
| 22 | mpb.NewBarFiller("╢▌▌░╟"), | |
| 23 | 23 | mpb.PrependDecorators( |
| 24 | 24 | // display our name with one space on the right |
| 25 | 25 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 1 | 1 | |
| 2 | 2 | // make syncWidth func public in test |
| 3 | 3 | var SyncWidth = syncWidth |
| 4 | var MaxWidthDistributor = &maxWidthDistributor |
| 0 | module github.com/vbauerster/mpb/v5 | |
| 0 | module github.com/vbauerster/mpb/v6 | |
| 1 | 1 | |
| 2 | 2 | require ( |
| 3 | 3 | github.com/VividCortex/ewma v1.1.1 |
| 4 | 4 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d |
| 5 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 | |
| 6 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect | |
| 5 | github.com/mattn/go-runewidth v0.0.10 | |
| 6 | github.com/rivo/uniseg v0.2.0 | |
| 7 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492 | |
| 7 | 8 | ) |
| 8 | 9 | |
| 9 | 10 | go 1.14 |
| 1 | 1 | github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= |
| 2 | 2 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= |
| 3 | 3 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= |
| 4 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |
| 5 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= | |
| 6 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |
| 7 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
| 8 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
| 9 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
| 10 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= | |
| 11 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
| 12 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |
| 4 | github.com/mattn/go-runewidth v0.0.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= |
| 6 | 6 | if total <= 0 { |
| 7 | 7 | return 0 |
| 8 | 8 | } |
| 9 | if current >= total { | |
| 10 | return float64(width) | |
| 11 | } | |
| 9 | 12 | return float64(int64(width)*current) / float64(total) |
| 10 | 13 | } |
| 11 | 14 | |
| 15 | // PercentageRound same as Percentage but with math.Round. | |
| 12 | 16 | func PercentageRound(total, current int64, width int) float64 { |
| 13 | 17 | return math.Round(Percentage(total, current, width)) |
| 14 | 18 | } |
| 20 | 20 | {"t,c,e{100,50,50}", 100, 50, 50}, |
| 21 | 21 | {"t,c,e{100,99,99}", 100, 99, 99}, |
| 22 | 22 | {"t,c,e{100,100,100}", 100, 100, 100}, |
| 23 | {"t,c,e{100,101,101}", 100, 101, 101}, | |
| 24 | {"t,c,e{100,102,101}", 100, 102, 102}, | |
| 23 | {"t,c,e{100,101,101}", 100, 101, 100}, | |
| 25 | 24 | {"t,c,e{120,0,0}", 120, 0, 0}, |
| 26 | 25 | {"t,c,e{120,10,8}", 120, 10, 8}, |
| 27 | 26 | {"t,c,e{120,15,13}", 120, 15, 13}, |
| 32 | 31 | {"t,c,e{120,118,98}", 120, 118, 98}, |
| 33 | 32 | {"t,c,e{120,119,99}", 120, 119, 99}, |
| 34 | 33 | {"t,c,e{120,120,100}", 120, 120, 100}, |
| 35 | {"t,c,e{120,121,101}", 120, 121, 101}, | |
| 36 | {"t,c,e{120,122,101}", 120, 122, 102}, | |
| 34 | {"t,c,e{120,121,101}", 120, 121, 100}, | |
| 37 | 35 | }, |
| 38 | 36 | 80: { |
| 39 | 37 | {"t,c,e{-1,-1,0}", -1, -1, 0}, |
| 46 | 44 | {"t,c,e{100,50,40}", 100, 50, 40}, |
| 47 | 45 | {"t,c,e{100,99,79}", 100, 99, 79}, |
| 48 | 46 | {"t,c,e{100,100,80}", 100, 100, 80}, |
| 49 | {"t,c,e{100,101,81}", 100, 101, 81}, | |
| 50 | {"t,c,e{100,102,82}", 100, 102, 82}, | |
| 47 | {"t,c,e{100,101,81}", 100, 101, 80}, | |
| 51 | 48 | {"t,c,e{120,0,0}", 120, 0, 0}, |
| 52 | 49 | {"t,c,e{120,10,7}", 120, 10, 7}, |
| 53 | 50 | {"t,c,e{120,15,10}", 120, 15, 10}, |
| 58 | 55 | {"t,c,e{120,118,79}", 120, 118, 79}, |
| 59 | 56 | {"t,c,e{120,119,79}", 120, 119, 79}, |
| 60 | 57 | {"t,c,e{120,120,80}", 120, 120, 80}, |
| 61 | {"t,c,e{120,121,81}", 120, 121, 81}, | |
| 62 | {"t,c,e{120,122,81}", 120, 122, 81}, | |
| 58 | {"t,c,e{120,121,81}", 120, 121, 80}, | |
| 63 | 59 | }, |
| 64 | 60 | } |
| 65 | 61 | |
| 0 | package internal | |
| 1 | ||
| 2 | // 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 | 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 | } |
| 7 | 7 | "io" |
| 8 | 8 | "io/ioutil" |
| 9 | 9 | "log" |
| 10 | "math" | |
| 10 | 11 | "os" |
| 11 | 12 | "sync" |
| 12 | 13 | "time" |
| 13 | 14 | |
| 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" | |
| 16 | 17 | ) |
| 17 | 18 | |
| 18 | 19 | const ( |
| 19 | 20 | // default RefreshRate |
| 20 | 21 | prr = 120 * time.Millisecond |
| 21 | // default width | |
| 22 | pwidth = 80 | |
| 23 | 22 | ) |
| 24 | 23 | |
| 25 | // Progress represents the container that renders Progress bars | |
| 24 | // Progress represents a container that renders one or more progress | |
| 25 | // bars. | |
| 26 | 26 | type Progress struct { |
| 27 | 27 | ctx context.Context |
| 28 | 28 | uwg *sync.WaitGroup |
| 35 | 35 | dlogger *log.Logger |
| 36 | 36 | } |
| 37 | 37 | |
| 38 | // pState holds bars in its priorityQueue. It gets passed to | |
| 39 | // *Progress.serve(...) monitor goroutine. | |
| 38 | 40 | type pState struct { |
| 39 | 41 | bHeap priorityQueue |
| 40 | 42 | heapUpdated bool |
| 41 | 43 | pMatrix map[int][]chan int |
| 42 | 44 | aMatrix map[int][]chan int |
| 43 | 45 | barShutdownQueue []*Bar |
| 44 | barPopQueue []*Bar | |
| 45 | 46 | |
| 46 | 47 | // following are provided/overrided by user |
| 47 | 48 | idCount int |
| 48 | width int | |
| 49 | reqWidth int | |
| 49 | 50 | popCompleted bool |
| 51 | outputDiscarded bool | |
| 50 | 52 | rr time.Duration |
| 51 | 53 | uwg *sync.WaitGroup |
| 52 | refreshSrc <-chan time.Time | |
| 54 | externalRefresh <-chan interface{} | |
| 53 | 55 | renderDelay <-chan struct{} |
| 54 | 56 | shutdownNotifier chan struct{} |
| 55 | 57 | parkedBars map[*Bar]*Bar |
| 69 | 71 | func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { |
| 70 | 72 | s := &pState{ |
| 71 | 73 | bHeap: priorityQueue{}, |
| 72 | width: pwidth, | |
| 73 | 74 | rr: prr, |
| 74 | 75 | parkedBars: make(map[*Bar]*Bar), |
| 75 | 76 | output: os.Stdout, |
| 97 | 98 | return p |
| 98 | 99 | } |
| 99 | 100 | |
| 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. | |
| 101 | 103 | 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. | |
| 106 | 110 | 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...) | |
| 108 | 112 | } |
| 109 | 113 | |
| 110 | 114 | // 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). | |
| 112 | 116 | // Panics if *Progress instance is done, i.e. called after *Progress.Wait(). |
| 113 | 117 | func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) *Bar { |
| 114 | 118 | if filler == nil { |
| 115 | filler = NewBarFiller(DefaultBarStyle, false) | |
| 119 | filler = BarFillerFunc(func(io.Writer, int, decor.Statistics) {}) | |
| 116 | 120 | } |
| 117 | 121 | p.bwg.Add(1) |
| 118 | 122 | result := make(chan *Bar) |
| 170 | 174 | p.setBarPriority(b, priority) |
| 171 | 175 | } |
| 172 | 176 | |
| 173 | // BarCount returns bars count | |
| 177 | // BarCount returns bars count. | |
| 174 | 178 | func (p *Progress) BarCount() int { |
| 175 | 179 | result := make(chan int, 1) |
| 176 | 180 | select { |
| 181 | 185 | } |
| 182 | 186 | } |
| 183 | 187 | |
| 184 | // Wait waits far all bars to complete and finally shutdowns container. | |
| 188 | // Wait waits for all bars to complete and finally shutdowns container. | |
| 185 | 189 | // After this method has been called, there is no way to reuse *Progress |
| 186 | 190 | // instance. |
| 187 | 191 | func (p *Progress) Wait() { |
| 214 | 218 | op(s) |
| 215 | 219 | case <-p.refreshCh: |
| 216 | 220 | if err := s.render(cw); err != nil { |
| 217 | go p.dlogger.Println(err) | |
| 221 | p.dlogger.Println(err) | |
| 218 | 222 | } |
| 219 | 223 | case <-s.shutdownNotifier: |
| 224 | if s.heapUpdated { | |
| 225 | if err := s.render(cw); err != nil { | |
| 226 | p.dlogger.Println(err) | |
| 227 | } | |
| 228 | } | |
| 220 | 229 | return |
| 221 | 230 | } |
| 222 | 231 | } |
| 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 | |
| 223 | 270 | } |
| 224 | 271 | |
| 225 | 272 | func (s *pState) render(cw *cwriter.Writer) error { |
| 232 | 279 | |
| 233 | 280 | tw, err := cw.GetWidth() |
| 234 | 281 | if err != nil { |
| 235 | tw = s.width | |
| 282 | tw = s.reqWidth | |
| 236 | 283 | } |
| 237 | 284 | for i := 0; i < s.bHeap.Len(); i++ { |
| 238 | 285 | bar := s.bHeap[i] |
| 249 | 296 | b := heap.Pop(&s.bHeap).(*Bar) |
| 250 | 297 | cw.ReadFrom(<-b.frameCh) |
| 251 | 298 | 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 { | |
| 255 | 300 | 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 | } | |
| 257 | 309 | } |
| 258 | 310 | lineCount += b.extendedLines + 1 |
| 259 | 311 | bm[b] = struct{}{} |
| 266 | 318 | delete(s.parkedBars, b) |
| 267 | 319 | b.toDrop = true |
| 268 | 320 | } |
| 321 | if s.popCompleted && !b.noPop { | |
| 322 | lineCount -= b.extendedLines + 1 | |
| 323 | b.toDrop = true | |
| 324 | } | |
| 269 | 325 | if b.toDrop { |
| 270 | 326 | delete(bm, b) |
| 271 | 327 | s.heapUpdated = true |
| 272 | } else if s.popCompleted { | |
| 273 | if b := b; !b.noPop { | |
| 274 | defer func() { | |
| 275 | s.barPopQueue = append(s.barPopQueue, b) | |
| 276 | }() | |
| 277 | } | |
| 278 | 328 | } |
| 279 | 329 | b.cancel() |
| 280 | 330 | } |
| 281 | 331 | s.barShutdownQueue = s.barShutdownQueue[0:0] |
| 282 | ||
| 283 | for _, b := range s.barPopQueue { | |
| 284 | delete(bm, b) | |
| 285 | s.heapUpdated = true | |
| 286 | lineCount -= b.extendedLines + 1 | |
| 287 | } | |
| 288 | s.barPopQueue = s.barPopQueue[0:0] | |
| 289 | 332 | |
| 290 | 333 | for b := range bm { |
| 291 | 334 | heap.Push(&s.bHeap, b) |
| 292 | 335 | } |
| 293 | 336 | |
| 294 | 337 | 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 | |
| 322 | 338 | } |
| 323 | 339 | |
| 324 | 340 | func (s *pState) updateSyncMatrix() { |
| 341 | 357 | |
| 342 | 358 | func (s *pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState { |
| 343 | 359 | bs := &bState{ |
| 360 | id: s.idCount, | |
| 361 | priority: s.idCount, | |
| 362 | reqWidth: s.reqWidth, | |
| 344 | 363 | total: total, |
| 345 | baseF: extractBaseFiller(filler), | |
| 346 | 364 | 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 }, | |
| 350 | 366 | 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 | |
| 354 | 371 | } |
| 355 | 372 | |
| 356 | 373 | for _, opt := range options { |
| 359 | 376 | } |
| 360 | 377 | } |
| 361 | 378 | |
| 379 | if bs.middleware != nil { | |
| 380 | bs.filler = bs.middleware(filler) | |
| 381 | bs.middleware = nil | |
| 382 | } | |
| 383 | ||
| 362 | 384 | 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)) | |
| 369 | 391 | |
| 370 | 392 | return bs |
| 371 | 393 | } |
| 372 | 394 | |
| 373 | 395 | func syncWidth(matrix map[int][]chan int) { |
| 374 | 396 | 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 | } | |
| 8 | 8 | "testing" |
| 9 | 9 | "time" |
| 10 | 10 | |
| 11 | "github.com/vbauerster/mpb/v5" | |
| 11 | "github.com/vbauerster/mpb/v6" | |
| 12 | "github.com/vbauerster/mpb/v6/decor" | |
| 12 | 13 | ) |
| 13 | 14 | |
| 14 | 15 | func init() { |
| 22 | 23 | wg.Add(1) |
| 23 | 24 | b := p.AddBar(100) |
| 24 | 25 | go func() { |
| 26 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 25 | 27 | for i := 0; i < 100; i++ { |
| 26 | 28 | if i == 33 { |
| 27 | 29 | wg.Done() |
| 28 | 30 | } |
| 29 | 31 | b.Increment() |
| 30 | time.Sleep(randomDuration(100 * time.Millisecond)) | |
| 32 | time.Sleep((time.Duration(rng.Intn(10)+1) * (10 * time.Millisecond)) / 2) | |
| 31 | 33 | } |
| 32 | 34 | }() |
| 33 | 35 | |
| 49 | 51 | bars := make([]*mpb.Bar, 3) |
| 50 | 52 | for i := 0; i < 3; i++ { |
| 51 | 53 | b := p.AddBar(100) |
| 52 | bars[i] = b | |
| 54 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| 53 | 55 | go func(n int) { |
| 54 | 56 | for i := 0; !b.Completed(); i++ { |
| 55 | 57 | if n == 0 && i >= 33 { |
| 57 | 59 | wg.Done() |
| 58 | 60 | } |
| 59 | 61 | b.Increment() |
| 60 | time.Sleep(randomDuration(100 * time.Millisecond)) | |
| 62 | time.Sleep((time.Duration(rng.Intn(10)+1) * (10 * time.Millisecond)) / 2) | |
| 61 | 63 | } |
| 62 | 64 | }(i) |
| 65 | bars[i] = b | |
| 63 | 66 | } |
| 64 | 67 | |
| 65 | 68 | wg.Wait() |
| 106 | 109 | } |
| 107 | 110 | } |
| 108 | 111 | |
| 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 | ||
| 109 | 177 | func getLastLine(bb []byte) []byte { |
| 110 | 178 | split := bytes.Split(bb, []byte("\n")) |
| 111 | 179 | return split[len(split)-2] |
| 6 | 6 | "strings" |
| 7 | 7 | "testing" |
| 8 | 8 | |
| 9 | "github.com/vbauerster/mpb/v5" | |
| 9 | "github.com/vbauerster/mpb/v6" | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | const content = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do |
| 32 | 32 | |
| 33 | 33 | tReader := &testReader{strings.NewReader(content), false} |
| 34 | 34 | |
| 35 | bar := p.AddBar(int64(len(content)), mpb.TrimSpace()) | |
| 35 | bar := p.AddBar(int64(len(content)), mpb.BarFillerTrim()) | |
| 36 | 36 | |
| 37 | 37 | var buf bytes.Buffer |
| 38 | 38 | _, err := io.Copy(&buf, bar.ProxyReader(tReader)) |
| 69 | 69 | wt := reader.(io.WriterTo) |
| 70 | 70 | tReader := &testWriterTo{reader, wt, false} |
| 71 | 71 | |
| 72 | bar := p.AddBar(int64(len(content)), mpb.TrimSpace()) | |
| 72 | bar := p.AddBar(int64(len(content)), mpb.BarFillerTrim()) | |
| 73 | 73 | |
| 74 | 74 | var buf bytes.Buffer |
| 75 | 75 | _, err := io.Copy(&buf, bar.ProxyReader(tReader)) |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "strings" | |
| 5 | "unicode/utf8" | |
| 6 | ||
| 7 | "github.com/vbauerster/mpb/v5/decor" | |
| 8 | ) | |
| 9 | ||
| 10 | // SpinnerAlignment enum. | |
| 11 | type SpinnerAlignment int | |
| 12 | ||
| 13 | // SpinnerAlignment kinds. | |
| 14 | const ( | |
| 15 | SpinnerOnLeft SpinnerAlignment = iota | |
| 16 | SpinnerOnMiddle | |
| 17 | SpinnerOnRight | |
| 18 | ) | |
| 19 | ||
| 20 | // DefaultSpinnerStyle is a slice of strings, which makes a spinner. | |
| 21 | var DefaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 22 | ||
| 23 | type spinnerFiller struct { | |
| 24 | frames []string | |
| 25 | count uint | |
| 26 | alignment SpinnerAlignment | |
| 27 | } | |
| 28 | ||
| 29 | // NewSpinnerFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. | |
| 30 | func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller { | |
| 31 | if len(style) == 0 { | |
| 32 | style = DefaultSpinnerStyle | |
| 33 | } | |
| 34 | filler := &spinnerFiller{ | |
| 35 | frames: style, | |
| 36 | alignment: alignment, | |
| 37 | } | |
| 38 | return filler | |
| 39 | } | |
| 40 | ||
| 41 | func (s *spinnerFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 42 | ||
| 43 | frame := s.frames[s.count%uint(len(s.frames))] | |
| 44 | frameWidth := utf8.RuneCountInString(frame) | |
| 45 | ||
| 46 | if width < frameWidth { | |
| 47 | return | |
| 48 | } | |
| 49 | ||
| 50 | switch rest := width - frameWidth; s.alignment { | |
| 51 | case SpinnerOnLeft: | |
| 52 | io.WriteString(w, frame+strings.Repeat(" ", rest)) | |
| 53 | case SpinnerOnMiddle: | |
| 54 | str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) | |
| 55 | io.WriteString(w, str) | |
| 56 | case SpinnerOnRight: | |
| 57 | io.WriteString(w, strings.Repeat(" ", rest)+frame) | |
| 58 | } | |
| 59 | s.count++ | |
| 60 | } |