diff --git a/.travis.yml b/.travis.yml index c982d1f..0eb0f2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,8 @@ language: go -sudo: false + go: - - 1.10.x - - tip - -before_install: - - go get -t -v ./... + - 1.14.x script: - - go test -race -coverprofile=coverage.txt -covermode=atomic - -after_success: - - bash <(curl -s https://codecov.io/bash) + - go test -race ./... + - for i in _examples/*/; do go build $i/*.go || exit 1; done diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 5e68ed2..0000000 --- a/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (C) 2016-2018 Vladimir Bauer -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index f96857c..bfb0c4d 100644 --- a/README.md +++ b/README.md @@ -3,51 +3,48 @@ [![GoDoc](https://godoc.org/github.com/vbauerster/mpb?status.svg)](https://godoc.org/github.com/vbauerster/mpb) [![Build Status](https://travis-ci.org/vbauerster/mpb.svg?branch=master)](https://travis-ci.org/vbauerster/mpb) [![Go Report Card](https://goreportcard.com/badge/github.com/vbauerster/mpb)](https://goreportcard.com/report/github.com/vbauerster/mpb) -[![codecov](https://codecov.io/gh/vbauerster/mpb/branch/master/graph/badge.svg)](https://codecov.io/gh/vbauerster/mpb) **mpb** is a Go lib for rendering progress bars in terminal applications. ## Features * __Multiple Bars__: Multiple progress bars are supported -* __Dynamic Total__: [Set total](https://github.com/vbauerster/mpb/issues/9#issuecomment-344448984) while bar is running +* __Dynamic Total__: Set total while bar is running * __Dynamic Add/Remove__: Dynamically add or remove bars * __Cancellation__: Cancel whole rendering process * __Predefined Decorators__: Elapsed time, [ewma](https://github.com/VividCortex/ewma) based ETA, Percentage, Bytes counter * __Decorator's width sync__: Synchronized decorator's width among multiple bars -## Installation - -```sh -go get github.com/vbauerster/mpb -``` - -_Note:_ it is preferable to go get from github.com, rather than gopkg.in. See issue [#11](https://github.com/vbauerster/mpb/issues/11). - ## Usage -#### [Rendering single bar](examples/singleBar/main.go) +#### [Rendering single bar](_examples/singleBar/main.go) ```go - p := mpb.New( - // override default (80) width - mpb.WithWidth(64), - // override default 120ms refresh rate - mpb.WithRefreshRate(180*time.Millisecond), - ) +package main + +import ( + "math/rand" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + // initialize progress container, with custom width + p := mpb.New(mpb.WithWidth(64)) total := 100 name := "Single Bar:" - // adding a single bar + // adding a single bar, which will inherit container's width bar := p.AddBar(int64(total), - // override default "[=>-]" style + // override DefaultBarStyle, which is "[=>-]<+" mpb.BarStyle("╢▌▌░╟"), mpb.PrependDecorators( // display our name with one space on the right decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), // replace ETA decorator with "done" message, OnComplete event decor.OnComplete( - // ETA decorator with ewma age of 60, and width reservation of 4 - decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WC{W: 4}), "done", + decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done", ), ), mpb.AppendDecorators(decor.Percentage()), @@ -55,18 +52,18 @@ // simulating some work max := 100 * time.Millisecond for i := 0; i < total; i++ { - start := time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) + bar.Increment() } // wait for our bar to complete and flush p.Wait() +} ``` -#### [Rendering multiple bars](examples/simple/main.go) +#### [Rendering multiple bars](_examples/multiBars/main.go) ```go var wg sync.WaitGroup + // pass &wg (optional), so p will wait for it eventually p := mpb.New(mpb.WithWaitGroup(&wg)) total, numBars := 100, 3 wg.Add(numBars) @@ -91,27 +88,31 @@ // simulating some work go func() { defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) max := 100 * time.Millisecond for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) } }() } - // wait for all bars to complete and flush + // Waiting for passed &wg and for all bars to complete and flush p.Wait() ``` -#### [Dynamic total](examples/dynTotal/main.go) +#### [Dynamic total](_examples/dynTotal/main.go) -![dynamic total](examples/gifs/godEMrCZmJkHYH1X9dN4Nm0U7.svg) +![dynamic total](_svg/godEMrCZmJkHYH1X9dN4Nm0U7.svg) -#### [Complex example](examples/complex/main.go) +#### [Complex example](_examples/complex/main.go) -![complex](examples/gifs/wHzf1M7sd7B3zVa2scBMnjqRf.svg) +![complex](_svg/wHzf1M7sd7B3zVa2scBMnjqRf.svg) -#### [Bytes counters](examples/io/single/main.go) +#### [Bytes counters](_examples/io/main.go) -![byte counters](examples/gifs/hIpTa3A5rQz65ssiVuRJu87X6.svg) +![byte counters](_svg/hIpTa3A5rQz65ssiVuRJu87X6.svg) diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/_examples/.gitignore b/_examples/.gitignore new file mode 100644 index 0000000..08cb523 --- /dev/null +++ b/_examples/.gitignore @@ -0,0 +1 @@ +go.sum diff --git a/_examples/barExtender/go.mod b/_examples/barExtender/go.mod new file mode 100644 index 0000000..cb71f2a --- /dev/null +++ b/_examples/barExtender/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/barExtender + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/barExtender/main.go b/_examples/barExtender/main.go new file mode 100644 index 0000000..0d5d2d5 --- /dev/null +++ b/_examples/barExtender/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "io" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg)) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + efn := func(w io.Writer, width int, s *decor.Statistics) { + if s.Completed { + fmt.Fprintf(w, "Bar id: %d has been completed\n", s.ID) + } + } + bar := p.AddBar(int64(total), mpb.BarExtender(mpb.BarFillerFunc(efn)), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + // decor.DSyncWidth bit enables column width synchronization + decor.Percentage(decor.WCSyncSpace), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + // simulating some work + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // since EWMA based decorator is used, DecoratorEwmaUpdate should be called + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + // wait for all bars to complete and flush + p.Wait() +} diff --git a/_examples/cancel/go.mod b/_examples/cancel/go.mod new file mode 100644 index 0000000..f1a7f39 --- /dev/null +++ b/_examples/cancel/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/cancel + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/cancel/main.go b/_examples/cancel/main.go new file mode 100644 index 0000000..b507601 --- /dev/null +++ b/_examples/cancel/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var wg sync.WaitGroup + p := mpb.NewWithContext(ctx, mpb.WithWaitGroup(&wg)) + total := 300 + numBars := 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + mpb.PrependDecorators( + decor.Name(name), + decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), + ), + mpb.AppendDecorators( + // note that OnComplete will not be fired, because of cancel + decor.OnComplete(decor.Percentage(decor.WC{W: 5}), "done"), + ), + ) + + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for !bar.Completed() { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // since EWMA based decorator is used, DecoratorEwmaUpdate should be called + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + + p.Wait() +} diff --git a/_examples/complex/go.mod b/_examples/complex/go.mod new file mode 100644 index 0000000..da328e4 --- /dev/null +++ b/_examples/complex/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/complex + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/complex/main.go b/_examples/complex/main.go new file mode 100644 index 0000000..aa45b74 --- /dev/null +++ b/_examples/complex/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func main() { + doneWg := new(sync.WaitGroup) + p := mpb.New(mpb.WithWidth(64), mpb.WithWaitGroup(doneWg)) + numBars := 4 + + var bars []*mpb.Bar + var downloadWgg []*sync.WaitGroup + for i := 0; i < numBars; i++ { + wg := new(sync.WaitGroup) + wg.Add(1) + downloadWgg = append(downloadWgg, wg) + task := fmt.Sprintf("Task#%02d:", i) + job := "downloading" + b := p.AddBar(rand.Int63n(201)+100, + mpb.PrependDecorators( + decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), + decor.Name(job, decor.WCSyncSpaceR), + decor.CountersNoUnit("%d / %d", decor.WCSyncWidth), + ), + mpb.AppendDecorators(decor.Percentage(decor.WC{W: 5})), + ) + go newTask(wg, b, i+1) + bars = append(bars, b) + } + + for i := 0; i < numBars; i++ { + doneWg.Add(1) + i := i + go func() { + task := fmt.Sprintf("Task#%02d:", i) + job := "\x1b[31;1;4minstalling\x1b[0m" + // preparing delayed bars + b := p.AddBar(rand.Int63n(101)+100, + mpb.BarQueueAfter(bars[i]), + mpb.BarFillerClearOnComplete(), + mpb.PrependDecorators( + decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), + decor.OnComplete(decor.Name(job, decor.WCSyncSpaceR), "done!"), + decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_MMSS, 0, decor.WCSyncWidth), ""), + ), + mpb.AppendDecorators( + decor.OnComplete(decor.Percentage(decor.WC{W: 5}), ""), + ), + ) + // waiting for download to complete, before starting install job + downloadWgg[i].Wait() + go newTask(doneWg, b, numBars-i) + }() + } + + p.Wait() +} + +func newTask(wg *sync.WaitGroup, bar *mpb.Bar, incrBy int) { + defer wg.Done() + max := 100 * time.Millisecond + for !bar.Completed() { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) + bar.IncrBy(incrBy) + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } +} diff --git a/_examples/differentWidth/go.mod b/_examples/differentWidth/go.mod new file mode 100644 index 0000000..eb3ed39 --- /dev/null +++ b/_examples/differentWidth/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/differentWidth + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/differentWidth/main.go b/_examples/differentWidth/main.go new file mode 100644 index 0000000..90b2c30 --- /dev/null +++ b/_examples/differentWidth/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + p := mpb.New( + mpb.WithWaitGroup(&wg), + // container's width. + mpb.WithWidth(60), + ) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + // set BarWidth 40 for bar 1 and 2 + mpb.BarOptOn(mpb.BarWidth(40), func() bool { return i > 0 }), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + // decor.DSyncWidth bit enables column width synchronization + decor.Percentage(decor.WCSyncSpace), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + // simulating some work + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + // wait for all bars to complete and flush + p.Wait() +} diff --git a/_examples/dynTotal/go.mod b/_examples/dynTotal/go.mod new file mode 100644 index 0000000..76f18f3 --- /dev/null +++ b/_examples/dynTotal/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/dynTotal + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/dynTotal/main.go b/_examples/dynTotal/main.go new file mode 100644 index 0000000..55e8c0e --- /dev/null +++ b/_examples/dynTotal/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "io" + "math/rand" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func main() { + p := mpb.New(mpb.WithWidth(64)) + + var total int64 + bar := p.AddBar(total, + mpb.PrependDecorators(decor.Counters(decor.UnitKiB, "% .1f / % .1f")), + mpb.AppendDecorators(decor.Percentage()), + ) + + maxSleep := 100 * time.Millisecond + read := makeStream(200) + for { + n, err := read() + total += int64(n) + if err == io.EOF { + break + } + // while total is unknown, + // set it to a positive number which is greater than current total, + // to make sure no complete event is triggered by next IncrBy call. + bar.SetTotal(total+2048, false) + bar.IncrBy(n) + time.Sleep(time.Duration(rand.Intn(10)+1) * maxSleep / 10) + } + + // force bar complete event, note true flag + bar.SetTotal(total, true) + + p.Wait() +} + +func makeStream(limit int) func() (int, error) { + return func() (int, error) { + if limit <= 0 { + return 0, io.EOF + } + limit-- + return rand.Intn(1024) + 1, nil + } +} diff --git a/_examples/io/go.mod b/_examples/io/go.mod new file mode 100644 index 0000000..5789e76 --- /dev/null +++ b/_examples/io/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/io + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/io/main.go b/_examples/io/main.go new file mode 100644 index 0000000..00a6dcd --- /dev/null +++ b/_examples/io/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "crypto/rand" + "io" + "io/ioutil" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var total int64 = 1024 * 1024 * 500 + reader := io.LimitReader(rand.Reader, total) + + p := mpb.New( + mpb.WithWidth(60), + mpb.WithRefreshRate(180*time.Millisecond), + ) + + bar := p.AddBar(total, mpb.BarStyle("[=>-|"), + mpb.PrependDecorators( + decor.CountersKibiByte("% .2f / % .2f"), + ), + mpb.AppendDecorators( + decor.EwmaETA(decor.ET_STYLE_GO, 90), + decor.Name(" ] "), + decor.EwmaSpeed(decor.UnitKiB, "% .2f", 60), + ), + ) + + // create proxy reader + proxyReader := bar.ProxyReader(reader) + defer proxyReader.Close() + + // copy from proxyReader, ignoring errors + io.Copy(ioutil.Discard, proxyReader) + + p.Wait() +} diff --git a/_examples/merge/go.mod b/_examples/merge/go.mod new file mode 100644 index 0000000..50502ea --- /dev/null +++ b/_examples/merge/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/merge + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/merge/main.go b/_examples/merge/main.go new file mode 100644 index 0000000..03a5817 --- /dev/null +++ b/_examples/merge/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "math/rand" + "strings" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + // pass &wg (optional), so p will wait for it eventually + p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(60)) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + var pdecorators mpb.BarOption + if i == 0 { + pdecorators = mpb.PrependDecorators( + decor.Merge( + decor.OnComplete( + newVariadicSpinner(decor.WCSyncSpace), + "done", + ), + decor.WCSyncSpace, // Placeholder + ), + ) + } else { + pdecorators = mpb.PrependDecorators( + decor.CountersNoUnit("% .1d / % .1d", decor.WCSyncSpace), + decor.OnComplete(decor.Spinner(nil, decor.WCSyncSpace), "done"), + ) + } + bar := p.AddBar(int64(total), + pdecorators, + mpb.AppendDecorators( + decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 60), "done"), + ), + ) + // simulating some work + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + // Waiting for passed &wg and for all bars to complete and flush + p.Wait() +} + +func newVariadicSpinner(wc decor.WC) decor.Decorator { + spinner := decor.Spinner(nil) + f := func(s *decor.Statistics) string { + return strings.Repeat(spinner.Decor(s), int(s.Current/3)) + } + return decor.Any(f, wc) +} diff --git a/_examples/multiBars/go.mod b/_examples/multiBars/go.mod new file mode 100644 index 0000000..bb356b6 --- /dev/null +++ b/_examples/multiBars/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/multiBars + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/multiBars/main.go b/_examples/multiBars/main.go new file mode 100644 index 0000000..38dc864 --- /dev/null +++ b/_examples/multiBars/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + // pass &wg (optional), so p will wait for it eventually + p := mpb.New(mpb.WithWaitGroup(&wg)) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + // decor.DSyncWidth bit enables column width synchronization + decor.Percentage(decor.WCSyncSpace), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + // simulating some work + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + // Waiting for passed &wg and for all bars to complete and flush + p.Wait() +} diff --git a/_examples/panic/go.mod b/_examples/panic/go.mod new file mode 100644 index 0000000..3846072 --- /dev/null +++ b/_examples/panic/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/panic + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/panic/main.go b/_examples/panic/main.go new file mode 100644 index 0000000..1a7cfc1 --- /dev/null +++ b/_examples/panic/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "os" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithDebugOutput(os.Stderr)) + + wantPanic := "Some really long panic panic panic panic panic panic panic, really it is very long" + numBars := 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("b#%02d:", i) + bar := p.AddBar(100, mpb.BarID(i), mpb.PrependDecorators(panicDecorator(name, wantPanic))) + + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + time.Sleep(50 * time.Millisecond) + bar.Increment() + } + }() + } + + p.Wait() +} + +func panicDecorator(name, panicMsg string) decor.Decorator { + return decor.Any(func(s *decor.Statistics) string { + if s.ID == 1 && s.Current >= 42 { + panic(panicMsg) + } + return name + }) +} diff --git a/_examples/poplog/go.mod b/_examples/poplog/go.mod new file mode 100644 index 0000000..a6ab5a9 --- /dev/null +++ b/_examples/poplog/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/poplog + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/poplog/main.go b/_examples/poplog/main.go new file mode 100644 index 0000000..3ff054a --- /dev/null +++ b/_examples/poplog/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "fmt" + "io" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + p := mpb.New(mpb.PopCompletedMode()) + + total, numBars := 100, 2 + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + mpb.BarNoPop(), + mpb.PrependDecorators( + decor.Name(name), + decor.Percentage(decor.WCSyncSpace), + ), + mpb.AppendDecorators( + decor.OnComplete( + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done!", + ), + ), + ) + // simulating some work + go func() { + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 3000 * time.Millisecond + for i := 0; i < 10; i++ { + filler := makeLogBar(fmt.Sprintf("some log: %d", i)) + p.Add(0, filler).SetTotal(0, true) + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + } + }() + + wg.Wait() + p.Wait() +} + +func makeLogBar(msg string) mpb.BarFiller { + limit := "%%.%ds" + return mpb.BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { + fmt.Fprintf(w, fmt.Sprintf(limit, width), msg) + }) +} diff --git a/_examples/quietMode/go.mod b/_examples/quietMode/go.mod new file mode 100644 index 0000000..c856e05 --- /dev/null +++ b/_examples/quietMode/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/quietMode + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/quietMode/main.go b/_examples/quietMode/main.go new file mode 100644 index 0000000..a918175 --- /dev/null +++ b/_examples/quietMode/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "flag" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +var quietMode bool + +func init() { + flag.BoolVar(&quietMode, "q", false, "quiet mode") +} + +func main() { + flag.Parse() + var wg sync.WaitGroup + // pass &wg (optional), so p will wait for it eventually + p := mpb.New( + mpb.WithWaitGroup(&wg), + mpb.ContainerOptOn( + // setting to nil will: + // set output to ioutil.Discard and disable internal refresh rate + // cycling, in order to not consume much CPU, hovewer a single refresh + // still will be triggered on bar complete event, per each bar. + mpb.WithOutput(nil), + func() bool { return quietMode }, + ), + ) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + // decor.DSyncWidth bit enables column width synchronization + decor.Percentage(decor.WCSyncSpace), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + // simulating some work + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + // Waiting for passed &wg and for all bars to complete and flush + p.Wait() + fmt.Println("done") +} diff --git a/_examples/remove/go.mod b/_examples/remove/go.mod new file mode 100644 index 0000000..2f0ce70 --- /dev/null +++ b/_examples/remove/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/remove + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/remove/main.go b/_examples/remove/main.go new file mode 100644 index 0000000..1836ee8 --- /dev/null +++ b/_examples/remove/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg)) + total := 100 + numBars := 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + mpb.BarID(i), + mpb.BarOptOn(mpb.BarRemoveOnComplete(), func() bool { return i == 0 }), + mpb.PrependDecorators( + decor.Name(name), + decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), + ), + mpb.AppendDecorators(decor.Percentage()), + ) + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; !bar.Completed(); i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + if bar.ID() == 2 && i >= 42 { + // aborting and removing while bar is running + bar.Abort(true) + } + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + + p.Wait() +} diff --git a/_examples/reverseBar/go.mod b/_examples/reverseBar/go.mod new file mode 100644 index 0000000..b279926 --- /dev/null +++ b/_examples/reverseBar/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/reverseBar + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/reverseBar/main.go b/_examples/reverseBar/main.go new file mode 100644 index 0000000..2993f50 --- /dev/null +++ b/_examples/reverseBar/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + // pass &wg (optional), so p will wait for it eventually + p := mpb.New(mpb.WithWaitGroup(&wg)) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + // reverse Bar#1 + mpb.BarOptOn(mpb.BarReverse(), func() bool { return i == 1 }), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + // decor.DSyncWidth bit enables column width synchronization + decor.Percentage(decor.WCSyncSpace), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + // simulating some work + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + // Waiting for passed &wg and for all bars to complete and flush + p.Wait() +} diff --git a/_examples/singleBar/go.mod b/_examples/singleBar/go.mod new file mode 100644 index 0000000..c2a7d8c --- /dev/null +++ b/_examples/singleBar/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/singleBar + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/singleBar/main.go b/_examples/singleBar/main.go new file mode 100644 index 0000000..5c6029f --- /dev/null +++ b/_examples/singleBar/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "math/rand" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + // initialize progress container, with custom width + p := mpb.New(mpb.WithWidth(64)) + + total := 100 + name := "Single Bar:" + // adding a single bar, which will inherit container's width + bar := p.AddBar(int64(total), + // override DefaultBarStyle, which is "[=>-]<+" + mpb.BarStyle("╢▌▌░╟"), + mpb.PrependDecorators( + // display our name with one space on the right + decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done", + ), + ), + mpb.AppendDecorators(decor.Percentage()), + ) + // simulating some work + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) + bar.Increment() + } + // wait for our bar to complete and flush + p.Wait() +} diff --git a/_examples/spinnerBar/go.mod b/_examples/spinnerBar/go.mod new file mode 100644 index 0000000..05b8407 --- /dev/null +++ b/_examples/spinnerBar/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/spinnerBar + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/spinnerBar/main.go b/_examples/spinnerBar/main.go new file mode 100644 index 0000000..fb4d910 --- /dev/null +++ b/_examples/spinnerBar/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + p := mpb.New( + mpb.WithWaitGroup(&wg), + mpb.WithWidth(13), + ) + total, numBars := 101, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + var bar *mpb.Bar + if i == 0 { + bar = p.AddBar(int64(total), + // override mpb.DefaultBarStyle, which is "[=>-]<+" + mpb.BarStyle("╢▌▌░╟"), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + } else { + bar = p.AddSpinner(int64(total), mpb.SpinnerOnMiddle, + // override mpb.DefaultSpinnerStyle + mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + } + + // simulating some work + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration + start := time.Now() + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) + } + }() + } + // wait for all bars to complete and flush + p.Wait() +} diff --git a/_examples/spinnerDecorator/go.mod b/_examples/spinnerDecorator/go.mod new file mode 100644 index 0000000..5fe9df5 --- /dev/null +++ b/_examples/spinnerDecorator/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/spinnerDecorator + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/spinnerDecorator/main.go b/_examples/spinnerDecorator/main.go new file mode 100644 index 0000000..d0499e0 --- /dev/null +++ b/_examples/spinnerDecorator/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + var wg sync.WaitGroup + // pass &wg (optional), so p will wait for it eventually + p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(64)) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(int64(total), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + decor.OnComplete( + // spinner decorator with default style + decor.Spinner(nil, decor.WCSyncSpace), "done", + ), + ), + mpb.AppendDecorators( + // decor.DSyncWidth bit enables column width synchronization + decor.Percentage(decor.WCSyncWidth), + ), + ) + // simulating some work + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + } + }() + } + // Waiting for passed &wg and for all bars to complete and flush + p.Wait() +} diff --git a/_examples/stress/go.mod b/_examples/stress/go.mod new file mode 100644 index 0000000..c391186 --- /dev/null +++ b/_examples/stress/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/stress + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/stress/main.go b/_examples/stress/main.go new file mode 100644 index 0000000..ec46fd3 --- /dev/null +++ b/_examples/stress/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +const ( + totalBars = 32 +) + +func main() { + var wg sync.WaitGroup + p := mpb.New( + mpb.WithWaitGroup(&wg), + mpb.WithRefreshRate(50*time.Millisecond), + ) + wg.Add(totalBars) + + for i := 0; i < totalBars; i++ { + name := fmt.Sprintf("Bar#%02d: ", i) + total := rand.Intn(320) + 10 + bar := p.AddBar(int64(total), + mpb.PrependDecorators( + decor.Name(name), + decor.Elapsed(decor.ET_STYLE_GO, decor.WCSyncSpace), + ), + mpb.AppendDecorators( + decor.OnComplete( + decor.Percentage(decor.WC{W: 5}), "done", + ), + ), + ) + + go func() { + defer wg.Done() + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for !bar.Completed() { + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + bar.Increment() + } + }() + } + + p.Wait() +} diff --git a/_examples/suppressBar/go.mod b/_examples/suppressBar/go.mod new file mode 100644 index 0000000..93845c4 --- /dev/null +++ b/_examples/suppressBar/go.mod @@ -0,0 +1,5 @@ +module github.com/vbauerster/mpb/_examples/suppressBar + +go 1.14 + +require github.com/vbauerster/mpb/v5 v5.0.3 diff --git a/_examples/suppressBar/main.go b/_examples/suppressBar/main.go new file mode 100644 index 0000000..906f2f9 --- /dev/null +++ b/_examples/suppressBar/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "errors" + "fmt" + "io" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +func main() { + p := mpb.New() + + total := 100 + msgCh := make(chan string) + resumeCh := make(chan struct{}) + filler, nextCh := newCustomFiller(msgCh, resumeCh) + bar := p.Add(int64(total), filler, + mpb.PrependDecorators( + decor.Name("my bar:"), + ), + mpb.AppendDecorators( + newCustomPercentage(nextCh), + ), + ) + ew := &errorWrapper{} + time.AfterFunc(2*time.Second, func() { + ew.reset(errors.New("timeout")) + }) + // simulating some work + go func() { + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) + if ew.isErr() { + msgCh <- fmt.Sprintf("%s at %d, retrying...", ew.Error(), i) + go ew.reset(nil) + i-- + bar.SetRefill(int64(i)) + time.Sleep(3 * time.Second) + resumeCh <- struct{}{} + continue + } + bar.Increment() + } + }() + + p.Wait() +} + +type errorWrapper struct { + sync.RWMutex + err error +} + +func (ew *errorWrapper) Error() string { + ew.RLock() + defer ew.RUnlock() + return ew.err.Error() +} + +func (ew *errorWrapper) isErr() bool { + ew.RLock() + defer ew.RUnlock() + return ew.err != nil +} + +func (ew *errorWrapper) reset(err error) { + ew.Lock() + ew.err = err + ew.Unlock() +} + +type myBarFiller struct { + mpb.BarFiller + base mpb.BarFiller +} + +func (cf *myBarFiller) Base() mpb.BarFiller { + return cf.base +} + +func newCustomFiller(ch <-chan string, resume <-chan struct{}) (mpb.BarFiller, <-chan struct{}) { + base := mpb.NewBarFiller(mpb.DefaultBarStyle, false) + nextCh := make(chan struct{}, 1) + var msg *string + filler := mpb.BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { + select { + case m := <-ch: + defer func() { + msg = &m + }() + nextCh <- struct{}{} + case <-resume: + msg = nil + default: + } + if msg != nil { + limitFmt := fmt.Sprintf("%%.%ds", width) + fmt.Fprintf(w, limitFmt, *msg) + nextCh <- struct{}{} + } else { + base.Fill(w, width, st) + } + }) + cf := &myBarFiller{ + BarFiller: filler, + base: base, + } + return cf, nextCh +} + +func newCustomPercentage(ch <-chan struct{}) decor.Decorator { + base := decor.Percentage() + f := func(s *decor.Statistics) string { + select { + case <-ch: + return "" + default: + return base.Decor(s) + } + } + return decor.Any(f) +} diff --git a/_svg/godEMrCZmJkHYH1X9dN4Nm0U7.svg b/_svg/godEMrCZmJkHYH1X9dN4Nm0U7.svg new file mode 100644 index 0000000..f794408 --- /dev/null +++ b/_svg/godEMrCZmJkHYH1X9dN4Nm0U7.svg @@ -0,0 +1 @@ +~/go/src/github.com/vbauerster/mpb/examples/dynTotal~/go/src/github.com/vbauerster/mpb/examples/dynTotalmaster*gorun-racemain.gogorun-racemain.gogorun-racemain.go55.7KiB/56.7KiB[============================================================>-]98%100.7KiB/100.7KiB[==============================================================]100%~/go/src/github.com/vbauerster/mpb/examples/dynTotalmaster*13s~/go/src/github.com/vbauerster/mpb/examples/dynTotalmaster~/go/src/github.com/vbauerster/mpb/examples/dynTotalmastergorun-racemain.gogorun-racemain.gogorun-racemain.gogorun-racemain.go519b/1.5KiB[====================>-----------------------------------------]34%2.2KiB/3.2KiB[==========================================>-------------------]69%2.5KiB/3.5KiB[===========================================>------------------]72%3.2KiB/4.2KiB[==============================================>---------------]76%3.6KiB/4.6KiB[================================================>-------------]78%6.5KiB/7.5KiB[=====================================================>--------]87%8.0KiB/9.0KiB[======================================================>-------]89%10.6KiB/11.6KiB[========================================================>-----]91%12.1KiB/13.1KiB[========================================================>-----]92%13.1KiB/14.1KiB[=========================================================>----]93%13.2KiB/14.2KiB[=========================================================>----]93%13.9KiB/14.9KiB[=========================================================>----]93%15.5KiB/16.5KiB[=========================================================>----]94%15.8KiB/16.8KiB[=========================================================>----]94%17.6KiB/18.6KiB[==========================================================>---]95%18.9KiB/19.9KiB[==========================================================>---]95%20.0KiB/21.0KiB[==========================================================>---]95%21.0KiB/22.0KiB[==========================================================>---]95%21.3KiB/22.3KiB[==========================================================>---]96%22.6KiB/23.6KiB[==========================================================>---]96%23.9KiB/24.9KiB[===========================================================>--]96%25.7KiB/26.7KiB[===========================================================>--]96%26.1KiB/27.1KiB[===========================================================>--]96%26.8KiB/27.8KiB[===========================================================>--]96%28.0KiB/29.0KiB[===========================================================>--]97%28.4KiB/29.4KiB[===========================================================>--]97%29.6KiB/30.6KiB[===========================================================>--]97%30.0KiB/31.0KiB[===========================================================>--]97%30.4KiB/31.4KiB[===========================================================>--]97%31.8KiB/32.8KiB[===========================================================>--]97%34.4KiB/35.4KiB[===========================================================>--]97%34.5KiB/35.5KiB[===========================================================>--]97%37.0KiB/38.0KiB[===========================================================>--]97%38.5KiB/39.5KiB[===========================================================>--]97%40.4KiB/41.4KiB[============================================================>-]98%41.1KiB/42.1KiB[============================================================>-]98%42.2KiB/43.2KiB[============================================================>-]98%43.9KiB/44.9KiB[============================================================>-]98%44.9KiB/45.9KiB[============================================================>-]98%46.2KiB/47.2KiB[============================================================>-]98%46.9KiB/47.9KiB[============================================================>-]98%48.4KiB/49.4KiB[============================================================>-]98%48.7KiB/49.7KiB[============================================================>-]98%49.3KiB/50.3KiB[============================================================>-]98%50.1KiB/51.1KiB[============================================================>-]98%50.5KiB/51.5KiB[============================================================>-]98%50.6KiB/51.6KiB[============================================================>-]98%50.8KiB/51.8KiB[============================================================>-]98%51.7KiB/52.7KiB[============================================================>-]98%52.7KiB/53.7KiB[============================================================>-]98%53.6KiB/54.6KiB[============================================================>-]98%57.5KiB/58.5KiB[============================================================>-]98%58.3KiB/59.3KiB[============================================================>-]98%58.7KiB/59.7KiB[============================================================>-]98%60.1KiB/61.1KiB[============================================================>-]98%62.0KiB/63.0KiB[============================================================>-]98%63.7KiB/64.7KiB[============================================================>-]98%64.7KiB/65.7KiB[============================================================>-]98%65.2KiB/66.2KiB[============================================================>-]98%65.8KiB/66.8KiB[============================================================>-]99%66.4KiB/67.4KiB[============================================================>-]99%67.6KiB/68.6KiB[============================================================>-]99%68.5KiB/69.5KiB[============================================================>-]99%70.0KiB/71.0KiB[============================================================>-]99%70.4KiB/71.4KiB[============================================================>-]99%70.8KiB/71.8KiB[============================================================>-]99%72.3KiB/73.3KiB[============================================================>-]99%73.1KiB/74.1KiB[============================================================>-]99%74.4KiB/75.4KiB[============================================================>-]99%75.7KiB/76.7KiB[============================================================>-]99%78.2KiB/79.2KiB[============================================================>-]99%79.3KiB/80.3KiB[============================================================>-]99%80.1KiB/81.1KiB[============================================================>-]99%81.3KiB/82.3KiB[============================================================>-]99%82.3KiB/83.3KiB[============================================================>-]99%82.6KiB/83.6KiB[============================================================>-]99%84.0KiB/85.0KiB[============================================================>-]99%84.7KiB/85.7KiB[============================================================>-]99%85.9KiB/86.9KiB[============================================================>-]99%87.7KiB/88.7KiB[============================================================>-]99%88.8KiB/89.8KiB[============================================================>-]99%90.3KiB/91.3KiB[============================================================>-]99%91.6KiB/92.6KiB[============================================================>-]99%92.8KiB/93.8KiB[============================================================>-]99%93.5KiB/94.5KiB[============================================================>-]99%93.6KiB/94.6KiB[============================================================>-]99%95.1KiB/96.1KiB[============================================================>-]99%96.4KiB/97.4KiB[============================================================>-]99%97.6KiB/98.6KiB[============================================================>-]99%98.8KiB/99.8KiB[============================================================>-]99%99.7KiB/100.7KiB[============================================================>-]99%100.6KiB/101.6KiB[============================================================>-]99%100.7KiB/101.7KiB[============================================================>-]99% \ No newline at end of file diff --git a/_svg/hIpTa3A5rQz65ssiVuRJu87X6.svg b/_svg/hIpTa3A5rQz65ssiVuRJu87X6.svg new file mode 100644 index 0000000..163921c --- /dev/null +++ b/_svg/hIpTa3A5rQz65ssiVuRJu87X6.svg @@ -0,0 +1 @@ +~/go/src/github.com/vbauerster/mpb/examples/io/single~/go/src/github.com/vbauerster/mpb/examples/io/singlemaster*gorun-racemain.gogorun-racemain.gogorun-racemain.go40.6MiB/40.6MiB[==========================================================|00:00]7.54MiB/s~/go/src/github.com/vbauerster/mpb/examples/io/singlemaster*46s~/go/src/github.com/vbauerster/mpb/examples/io/singlemastergitclean-fdxgorun-racemain.gogorun-racemain.gogorun-racemain.go16.6KiB/40.6MiB[----------------------------------------------------------|00:00]0b/s66.5KiB/40.6MiB[----------------------------------------------------------|00:00]0b/s134.5KiB/40.6MiB[----------------------------------------------------------|09:01]6.18MiB/s339.6KiB/40.6MiB[----------------------------------------------------------|08:18]6.41MiB/s577.6KiB/40.6MiB[>---------------------------------------------------------|07:32]7.05MiB/s866.6KiB/40.6MiB[>---------------------------------------------------------|06:43]7.36MiB/s1.5MiB/40.6MiB[=>--------------------------------------------------------|05:12]8.12MiB/s1.8MiB/40.6MiB[==>-------------------------------------------------------|04:28]8.48MiB/s2.4MiB/40.6MiB[==>-------------------------------------------------------|03:29]16.62MiB/s2.9MiB/40.6MiB[===>------------------------------------------------------|02:52]15.37MiB/s3.5MiB/40.6MiB[====>-----------------------------------------------------|02:17]14.22MiB/s3.9MiB/40.6MiB[=====>----------------------------------------------------|01:59]13.18MiB/s4.3MiB/40.6MiB[=====>----------------------------------------------------|01:40]12.42MiB/s4.9MiB/40.6MiB[======>---------------------------------------------------|01:19]18.55MiB/s5.2MiB/40.6MiB[======>---------------------------------------------------|01:11]17.39MiB/s5.5MiB/40.6MiB[=======>--------------------------------------------------|01:03]16.23MiB/s5.6MiB/40.6MiB[=======>--------------------------------------------------|01:02]16.04MiB/s6.2MiB/40.6MiB[========>-------------------------------------------------|00:50]17.91MiB/s6.5MiB/40.6MiB[========>-------------------------------------------------|00:47]16.80MiB/s6.7MiB/40.6MiB[=========>------------------------------------------------|00:45]15.85MiB/s7.0MiB/40.6MiB[=========>------------------------------------------------|00:41]14.66MiB/s7.3MiB/40.6MiB[=========>------------------------------------------------|00:38]17.03MiB/s7.5MiB/40.6MiB[==========>-----------------------------------------------|00:39]16.26MiB/s7.7MiB/40.6MiB[==========>-----------------------------------------------|00:38]15.16MiB/s8.0MiB/40.6MiB[==========>-----------------------------------------------|00:36]14.16MiB/s8.1MiB/40.6MiB[===========>----------------------------------------------|00:35]13.56MiB/s8.4MiB/40.6MiB[===========>----------------------------------------------|00:36]13.08MiB/s8.6MiB/40.6MiB[===========>----------------------------------------------|00:34]12.51MiB/s8.8MiB/40.6MiB[============>---------------------------------------------|00:34]12.05MiB/s9.0MiB/40.6MiB[============>---------------------------------------------|00:35]11.56MiB/s9.3MiB/40.6MiB[============>---------------------------------------------|00:32]11.12MiB/s9.5MiB/40.6MiB[=============>--------------------------------------------|00:31]10.75MiB/s9.7MiB/40.6MiB[=============>--------------------------------------------|00:30]10.23MiB/s10.0MiB/40.6MiB[=============>--------------------------------------------|00:29]9.89MiB/s10.2MiB/40.6MiB[==============>-------------------------------------------|00:30]9.54MiB/s10.4MiB/40.6MiB[==============>-------------------------------------------|00:28]9.34MiB/s10.7MiB/40.6MiB[==============>-------------------------------------------|00:27]9.04MiB/s10.9MiB/40.6MiB[===============>------------------------------------------|00:26]8.79MiB/s11.0MiB/40.6MiB[===============>------------------------------------------|00:26]8.75MiB/s11.3MiB/40.6MiB[===============>------------------------------------------|00:25]11.13MiB/s11.5MiB/40.6MiB[===============>------------------------------------------|00:24]10.81MiB/s11.7MiB/40.6MiB[================>-----------------------------------------|00:24]10.45MiB/s11.9MiB/40.6MiB[================>-----------------------------------------|00:25]10.00MiB/s12.1MiB/40.6MiB[================>-----------------------------------------|00:25]9.63MiB/s12.3MiB/40.6MiB[=================>----------------------------------------|00:24]9.26MiB/s12.4MiB/40.6MiB[=================>----------------------------------------|00:24]9.14MiB/s12.6MiB/40.6MiB[=================>----------------------------------------|00:23]8.79MiB/s12.8MiB/40.6MiB[=================>----------------------------------------|00:23]10.07MiB/s12.9MiB/40.6MiB[=================>----------------------------------------|00:23]9.96MiB/s13.1MiB/40.6MiB[==================>---------------------------------------|00:22]9.80MiB/s13.2MiB/40.6MiB[==================>---------------------------------------|00:22]9.71MiB/s13.4MiB/40.6MiB[==================>---------------------------------------|00:22]9.61MiB/s13.6MiB/40.6MiB[==================>---------------------------------------|00:25]9.36MiB/s13.7MiB/40.6MiB[===================>--------------------------------------|00:24]9.19MiB/s13.9MiB/40.6MiB[===================>--------------------------------------|00:24]8.93MiB/s14.0MiB/40.6MiB[===================>--------------------------------------|00:24]8.65MiB/s14.2MiB/40.6MiB[===================>--------------------------------------|00:24]8.48MiB/s14.4MiB/40.6MiB[====================>-------------------------------------|00:23]8.32MiB/s14.5MiB/40.6MiB[====================>-------------------------------------|00:23]8.20MiB/s14.7MiB/40.6MiB[====================>-------------------------------------|00:22]8.12MiB/s14.8MiB/40.6MiB[====================>-------------------------------------|00:22]7.97MiB/s15.0MiB/40.6MiB[====================>-------------------------------------|00:22]7.88MiB/s15.2MiB/40.6MiB[=====================>------------------------------------|00:21]7.74MiB/s15.3MiB/40.6MiB[=====================>------------------------------------|00:21]7.73MiB/s15.4MiB/40.6MiB[=====================>------------------------------------|00:20]9.07MiB/s15.6MiB/40.6MiB[=====================>------------------------------------|00:20]8.84MiB/s15.7MiB/40.6MiB[=====================>------------------------------------|00:20]8.72MiB/s15.9MiB/40.6MiB[======================>-----------------------------------|00:20]8.45MiB/s16.0MiB/40.6MiB[======================>-----------------------------------|00:20]8.27MiB/s16.1MiB/40.6MiB[======================>-----------------------------------|00:22]8.05MiB/s16.3MiB/40.6MiB[======================>-----------------------------------|00:21]7.88MiB/s16.4MiB/40.6MiB[======================>-----------------------------------|00:21]7.69MiB/s16.6MiB/40.6MiB[=======================>----------------------------------|00:21]7.60MiB/s16.7MiB/40.6MiB[=======================>----------------------------------|00:22]7.49MiB/s16.9MiB/40.6MiB[=======================>----------------------------------|00:22]7.41MiB/s17.0MiB/40.6MiB[=======================>----------------------------------|00:21]7.43MiB/s17.2MiB/40.6MiB[========================>---------------------------------|00:21]7.38MiB/s17.4MiB/40.6MiB[========================>---------------------------------|00:23]7.23MiB/s17.5MiB/40.6MiB[========================>---------------------------------|00:23]7.15MiB/s17.7MiB/40.6MiB[========================>---------------------------------|00:24]6.99MiB/s17.9MiB/40.6MiB[=========================>--------------------------------|00:25]6.90MiB/s18.0MiB/40.6MiB[=========================>--------------------------------|00:24]6.83MiB/s18.2MiB/40.6MiB[=========================>--------------------------------|00:23]8.15MiB/s18.3MiB/40.6MiB[=========================>--------------------------------|00:24]7.99MiB/s18.4MiB/40.6MiB[=========================>--------------------------------|00:24]7.88MiB/s18.6MiB/40.6MiB[==========================>-------------------------------|00:24]7.62MiB/s18.7MiB/40.6MiB[==========================>-------------------------------|00:23]7.58MiB/s18.8MiB/40.6MiB[==========================>-------------------------------|00:23]7.47MiB/s19.0MiB/40.6MiB[==========================>-------------------------------|00:22]7.39MiB/s19.1MiB/40.6MiB[==========================>-------------------------------|00:22]7.33MiB/s19.2MiB/40.6MiB[===========================>------------------------------|00:22]8.48MiB/s19.4MiB/40.6MiB[===========================>------------------------------|00:25]8.27MiB/s19.5MiB/40.6MiB[===========================>------------------------------|00:25]8.13MiB/s19.6MiB/40.6MiB[===========================>------------------------------|00:25]7.89MiB/s19.7MiB/40.6MiB[===========================>------------------------------|00:25]7.67MiB/s19.8MiB/40.6MiB[===========================>------------------------------|00:24]7.52MiB/s20.0MiB/40.6MiB[============================>-----------------------------|00:24]7.42MiB/s20.1MiB/40.6MiB[============================>-----------------------------|00:24]7.25MiB/s20.2MiB/40.6MiB[============================>-----------------------------|00:23]7.12MiB/s20.3MiB/40.6MiB[============================>-----------------------------|00:23]6.94MiB/s20.5MiB/40.6MiB[============================>-----------------------------|00:24]6.75MiB/s20.6MiB/40.6MiB[============================>-----------------------------|00:24]6.59MiB/s20.7MiB/40.6MiB[=============================>----------------------------|00:27]6.57MiB/s20.8MiB/40.6MiB[=============================>----------------------------|00:27]6.45MiB/s21.0MiB/40.6MiB[=============================>----------------------------|00:28]6.40MiB/s21.1MiB/40.6MiB[=============================>----------------------------|00:30]6.36MiB/s21.2MiB/40.6MiB[=============================>----------------------------|00:29]6.28MiB/s21.3MiB/40.6MiB[=============================>----------------------------|00:28]6.22MiB/s21.4MiB/40.6MiB[==============================>---------------------------|00:27]6.19MiB/s21.5MiB/40.6MiB[==============================>---------------------------|00:29]6.10MiB/s21.7MiB/40.6MiB[==============================>---------------------------|00:28]6.09MiB/s21.8MiB/40.6MiB[==============================>---------------------------|00:27]6.04MiB/s21.9MiB/40.6MiB[==============================>---------------------------|00:27]5.88MiB/s22.0MiB/40.6MiB[===============================>--------------------------|00:27]5.83MiB/s22.2MiB/40.6MiB[===============================>--------------------------|00:26]5.78MiB/s22.3MiB/40.6MiB[===============================>--------------------------|00:25]5.80MiB/s22.4MiB/40.6MiB[===============================>--------------------------|00:24]5.76MiB/s22.5MiB/40.6MiB[===============================>--------------------------|00:24]5.72MiB/s22.7MiB/40.6MiB[===============================>--------------------------|00:23]5.70MiB/s22.8MiB/40.6MiB[================================>-------------------------|00:24]5.60MiB/s22.8MiB/40.6MiB[================================>-------------------------|00:24]5.58MiB/s23.0MiB/40.6MiB[================================>-------------------------|00:25]6.35MiB/s23.1MiB/40.6MiB[================================>-------------------------|00:25]6.33MiB/s23.2MiB/40.6MiB[================================>-------------------------|00:24]6.33MiB/s23.4MiB/40.6MiB[================================>-------------------------|00:24]6.31MiB/s23.5MiB/40.6MiB[=================================>------------------------|00:23]6.28MiB/s23.6MiB/40.6MiB[=================================>------------------------|00:23]6.20MiB/s23.7MiB/40.6MiB[=================================>------------------------|00:22]6.26MiB/s23.8MiB/40.6MiB[=================================>------------------------|00:22]6.26MiB/s23.9MiB/40.6MiB[=================================>------------------------|00:21]6.31MiB/s24.0MiB/40.6MiB[=================================>------------------------|00:21]6.26MiB/s24.2MiB/40.6MiB[==================================>-----------------------|00:22]6.25MiB/s24.3MiB/40.6MiB[==================================>-----------------------|00:25]6.20MiB/s24.4MiB/40.6MiB[==================================>-----------------------|00:24]6.20MiB/s24.5MiB/40.6MiB[==================================>-----------------------|00:23]6.14MiB/s24.7MiB/40.6MiB[==================================>-----------------------|00:24]6.16MiB/s24.8MiB/40.6MiB[==================================>-----------------------|00:24]6.13MiB/s24.9MiB/40.6MiB[===================================>----------------------|00:23]6.14MiB/s25.0MiB/40.6MiB[===================================>----------------------|00:23]6.17MiB/s25.1MiB/40.6MiB[===================================>----------------------|00:23]6.16MiB/s25.3MiB/40.6MiB[===================================>----------------------|00:22]6.05MiB/s25.4MiB/40.6MiB[===================================>----------------------|00:22]6.14MiB/s25.5MiB/40.6MiB[===================================>----------------------|00:21]6.06MiB/s25.6MiB/40.6MiB[====================================>---------------------|00:22]6.04MiB/s25.8MiB/40.6MiB[====================================>---------------------|00:21]6.02MiB/s25.9MiB/40.6MiB[====================================>---------------------|00:22]5.96MiB/s26.0MiB/40.6MiB[====================================>---------------------|00:24]6.05MiB/s26.1MiB/40.6MiB[====================================>---------------------|00:23]6.03MiB/s26.2MiB/40.6MiB[=====================================>--------------------|00:22]7.18MiB/s26.3MiB/40.6MiB[=====================================>--------------------|00:22]7.10MiB/s26.4MiB/40.6MiB[=====================================>--------------------|00:21]7.09MiB/s26.5MiB/40.6MiB[=====================================>--------------------|00:21]7.09MiB/s26.7MiB/40.6MiB[=====================================>--------------------|00:21]7.07MiB/s26.8MiB/40.6MiB[=====================================>--------------------|00:20]7.02MiB/s26.8MiB/40.6MiB[=====================================>--------------------|00:20]6.92MiB/s27.0MiB/40.6MiB[======================================>-------------------|00:19]6.89MiB/s27.1MiB/40.6MiB[======================================>-------------------|00:19]6.99MiB/s27.2MiB/40.6MiB[======================================>-------------------|00:18]6.98MiB/s27.3MiB/40.6MiB[======================================>-------------------|00:18]6.99MiB/s27.5MiB/40.6MiB[======================================>-------------------|00:20]6.98MiB/s27.5MiB/40.6MiB[======================================>-------------------|00:20]6.94MiB/s27.7MiB/40.6MiB[=======================================>------------------|00:20]7.98MiB/s27.8MiB/40.6MiB[=======================================>------------------|00:19]7.88MiB/s27.8MiB/40.6MiB[=======================================>------------------|00:19]7.81MiB/s28.0MiB/40.6MiB[=======================================>------------------|00:18]7.71MiB/s28.0MiB/40.6MiB[=======================================>------------------|00:18]7.58MiB/s28.1MiB/40.6MiB[=======================================>------------------|00:18]7.44MiB/s28.2MiB/40.6MiB[=======================================>------------------|00:18]7.38MiB/s28.3MiB/40.6MiB[========================================>-----------------|00:17]7.33MiB/s28.4MiB/40.6MiB[========================================>-----------------|00:17]7.19MiB/s28.5MiB/40.6MiB[========================================>-----------------|00:18]7.17MiB/s28.6MiB/40.6MiB[========================================>-----------------|00:17]7.17MiB/s28.7MiB/40.6MiB[========================================>-----------------|00:20]7.11MiB/s28.8MiB/40.6MiB[========================================>-----------------|00:19]7.03MiB/s28.9MiB/40.6MiB[========================================>-----------------|00:20]6.89MiB/s29.0MiB/40.6MiB[=========================================>----------------|00:20]6.79MiB/s29.1MiB/40.6MiB[=========================================>----------------|00:19]6.72MiB/s29.2MiB/40.6MiB[=========================================>----------------|00:21]6.60MiB/s29.4MiB/40.6MiB[=========================================>----------------|00:20]6.50MiB/s29.4MiB/40.6MiB[=========================================>----------------|00:20]6.48MiB/s29.5MiB/40.6MiB[=========================================>----------------|00:20]6.44MiB/s29.7MiB/40.6MiB[=========================================>----------------|00:19]6.37MiB/s29.7MiB/40.6MiB[==========================================>---------------|00:19]6.31MiB/s29.8MiB/40.6MiB[==========================================>---------------|00:21]6.31MiB/s29.9MiB/40.6MiB[==========================================>---------------|00:20]6.34MiB/s30.0MiB/40.6MiB[==========================================>---------------|00:20]6.32MiB/s30.2MiB/40.6MiB[==========================================>---------------|00:19]6.22MiB/s30.2MiB/40.6MiB[==========================================>---------------|00:19]6.18MiB/s30.3MiB/40.6MiB[==========================================>---------------|00:19]6.16MiB/s30.5MiB/40.6MiB[===========================================>--------------|00:19]6.21MiB/s30.6MiB/40.6MiB[===========================================>--------------|00:18]6.19MiB/s30.7MiB/40.6MiB[===========================================>--------------|00:17]6.18MiB/s30.8MiB/40.6MiB[===========================================>--------------|00:17]6.19MiB/s30.9MiB/40.6MiB[===========================================>--------------|00:17]5.99MiB/s31.0MiB/40.6MiB[===========================================>--------------|00:17]5.89MiB/s31.1MiB/40.6MiB[============================================>-------------|00:16]5.92MiB/s31.3MiB/40.6MiB[============================================>-------------|00:15]5.80MiB/s31.4MiB/40.6MiB[============================================>-------------|00:15]5.85MiB/s31.5MiB/40.6MiB[============================================>-------------|00:14]5.87MiB/s31.7MiB/40.6MiB[============================================>-------------|00:13]5.93MiB/s31.9MiB/40.6MiB[=============================================>------------|00:13]5.95MiB/s32.0MiB/40.6MiB[=============================================>------------|00:12]5.98MiB/s32.2MiB/40.6MiB[=============================================>------------|00:11]5.99MiB/s32.4MiB/40.6MiB[=============================================>------------|00:11]6.04MiB/s32.5MiB/40.6MiB[=============================================>------------|00:10]6.08MiB/s32.7MiB/40.6MiB[==============================================>-----------|00:10]6.03MiB/s32.9MiB/40.6MiB[==============================================>-----------|00:09]6.05MiB/s33.1MiB/40.6MiB[==============================================>-----------|00:09]6.08MiB/s33.3MiB/40.6MiB[===============================================>----------|00:08]6.13MiB/s33.6MiB/40.6MiB[===============================================>----------|00:07]6.30MiB/s33.8MiB/40.6MiB[===============================================>----------|00:07]6.33MiB/s34.0MiB/40.6MiB[================================================>---------|00:07]6.44MiB/s34.3MiB/40.6MiB[================================================>---------|00:06]6.88MiB/s34.6MiB/40.6MiB[================================================>---------|00:05]6.88MiB/s34.8MiB/40.6MiB[=================================================>--------|00:05]6.88MiB/s35.2MiB/40.6MiB[=================================================>--------|00:04]10.50MiB/s35.4MiB/40.6MiB[==================================================>-------|00:04]10.17MiB/s35.6MiB/40.6MiB[==================================================>-------|00:03]9.88MiB/s35.9MiB/40.6MiB[==================================================>-------|00:03]9.36MiB/s36.2MiB/40.6MiB[===================================================>------|00:03]9.27MiB/s36.4MiB/40.6MiB[===================================================>------|00:02]9.08MiB/s36.7MiB/40.6MiB[===================================================>------|00:02]8.83MiB/s37.0MiB/40.6MiB[====================================================>-----|00:02]8.64MiB/s37.2MiB/40.6MiB[====================================================>-----|00:02]8.48MiB/s37.5MiB/40.6MiB[=====================================================>----|00:02]8.26MiB/s37.9MiB/40.6MiB[=====================================================>----|00:02]8.16MiB/s38.1MiB/40.6MiB[=====================================================>----|00:01]8.21MiB/s38.4MiB/40.6MiB[======================================================>---|00:01]8.38MiB/s38.7MiB/40.6MiB[======================================================>---|00:01]8.31MiB/s39.0MiB/40.6MiB[=======================================================>--|00:01]8.40MiB/s39.3MiB/40.6MiB[=======================================================>--|00:01]8.21MiB/s39.6MiB/40.6MiB[========================================================>-|00:00]8.14MiB/s40.0MiB/40.6MiB[========================================================>-|00:00]7.97MiB/s40.2MiB/40.6MiB[==========================================================|00:00]7.75MiB/s \ No newline at end of file diff --git a/_svg/wHzf1M7sd7B3zVa2scBMnjqRf.svg b/_svg/wHzf1M7sd7B3zVa2scBMnjqRf.svg new file mode 100644 index 0000000..9e3b871 --- /dev/null +++ b/_svg/wHzf1M7sd7B3zVa2scBMnjqRf.svg @@ -0,0 +1 @@ +~/go/src/github.com/vbauerster/mpb/examples/complex~/go/src/github.com/vbauerster/mpb/examples/complexmaster*gorun-racemain.gogorun-racemain.gogorun-racemain.goTask#03:installing00:08[======>-------------------------------------------------------]11%Task#03:installing00:07[==============>-----------------------------------------------]24%Task#02:done!Task#03:done!Task#01:done!Task#00:done!~/go/src/github.com/vbauerster/mpb/examples/complexmaster*19s~/go/src/github.com/vbauerster/mpb/examples/complexmastergorun-racemain.gogorun-racemain.gogorun-racemain.gogorun-racemain.goTask#00:downloading4/268[>-------------------------------------------------------------]1%Task#01:downloading2/274[--------------------------------------------------------------]1%Task#02:downloading3/114[=>------------------------------------------------------------]3%Task#03:downloading4/114[=>------------------------------------------------------------]4%Task#00:downloading6/268[>-------------------------------------------------------------]2%Task#01:downloading8/274[=>------------------------------------------------------------]3%Task#02:downloading9/114[====>---------------------------------------------------------]8%Task#03:downloading12/114[======>-------------------------------------------------------]11%Task#00:downloading9/268[=>------------------------------------------------------------]3%Task#01:downloading12/274[==>-----------------------------------------------------------]4%Task#02:downloading12/114[======>-------------------------------------------------------]11%Task#03:downloading24/114[============>-------------------------------------------------]21%Task#00:downloading11/268[==>-----------------------------------------------------------]4%Task#01:downloading16/274[===>----------------------------------------------------------]6%Task#02:downloading18/114[=========>----------------------------------------------------]16%Task#03:downloading36/114[===================>------------------------------------------]32%Task#00:downloading13/268[==>-----------------------------------------------------------]5%Task#01:downloading22/274[====>---------------------------------------------------------]8%Task#02:downloading27/114[==============>-----------------------------------------------]24%Task#03:downloading40/114[=====================>----------------------------------------]35%Task#00:downloading15/268[==>-----------------------------------------------------------]6%Task#01:downloading24/274[====>---------------------------------------------------------]9%Task#02:downloading30/114[===============>----------------------------------------------]26%Task#03:downloading48/114[=========================>------------------------------------]42%Task#00:downloading16/268[===>----------------------------------------------------------]6%Task#01:downloading30/274[======>-------------------------------------------------------]11%Task#02:downloading39/114[====================>-----------------------------------------]34%Task#03:downloading56/114[=============================>--------------------------------]49%Task#00:downloading18/268[===>----------------------------------------------------------]7%Task#01:downloading34/274[=======>------------------------------------------------------]12%Task#02:downloading42/114[======================>---------------------------------------]37%Task#03:downloading64/114[==================================>---------------------------]56%Task#00:downloading19/268[===>----------------------------------------------------------]7%Task#01:downloading40/274[========>-----------------------------------------------------]15%Task#02:downloading45/114[=======================>--------------------------------------]39%Task#03:downloading68/114[====================================>-------------------------]60%Task#00:downloading21/268[====>---------------------------------------------------------]8%Task#01:downloading44/274[=========>----------------------------------------------------]16%Task#02:downloading54/114[============================>---------------------------------]47%Task#03:downloading76/114[========================================>---------------------]67%Task#00:downloading25/268[=====>--------------------------------------------------------]9%Task#01:downloading52/274[===========>--------------------------------------------------]19%Task#02:downloading60/114[================================>-----------------------------]53%Task#03:downloading80/114[===========================================>------------------]70%Task#00:downloading27/268[=====>--------------------------------------------------------]10%Task#01:downloading54/274[===========>--------------------------------------------------]20%Task#02:downloading63/114[=================================>----------------------------]55%Task#03:downloading88/114[===============================================>--------------]77%Task#00:downloading29/268[======>-------------------------------------------------------]11%Task#01:downloading58/274[============>-------------------------------------------------]21%Task#02:downloading69/114[=====================================>------------------------]61%Task#03:downloading92/114[=================================================>------------]81%Task#00:downloading30/268[======>-------------------------------------------------------]11%Task#01:downloading60/274[=============>------------------------------------------------]22%Task#02:downloading75/114[========================================>---------------------]66%Task#03:downloading100/114[=====================================================>--------]88%Task#00:downloading32/268[======>-------------------------------------------------------]12%Task#01:downloading66/274[==============>-----------------------------------------------]24%Task#02:downloading78/114[=========================================>--------------------]68%Task#03:downloading108/114[==========================================================>---]95%Task#00:downloading34/268[=======>------------------------------------------------------]13%Task#01:downloading70/274[===============>----------------------------------------------]26%Task#02:downloading84/114[=============================================>----------------]74%Task#03:downloading114/114[==============================================================]100%Task#00:downloading35/268[=======>------------------------------------------------------]13%Task#01:downloading74/274[================>---------------------------------------------]27%Task#02:downloading90/114[================================================>-------------]79%Task#03:installing00:00[--------------------------------------------------------------]1%Task#00:downloading37/268[========>-----------------------------------------------------]14%Task#01:downloading76/274[================>---------------------------------------------]28%Task#02:downloading96/114[===================================================>----------]84%Task#03:installing00:00[=>------------------------------------------------------------]2%Task#00:downloading40/268[========>-----------------------------------------------------]15%Task#01:downloading78/274[=================>--------------------------------------------]28%Task#02:downloading102/114[======================================================>-------]89%Task#03:installing00:00[=>------------------------------------------------------------]4%Task#00:downloading42/268[=========>----------------------------------------------------]16%Task#01:downloading82/274[==================>-------------------------------------------]30%Task#02:downloading108/114[==========================================================>---]95%Task#03:installing00:00[==>-----------------------------------------------------------]4%Task#00:downloading44/268[=========>----------------------------------------------------]16%Task#01:downloading86/274[==================>-------------------------------------------]31%Task#02:downloading114/114[==============================================================]100%Task#03:installing00:00[==>-----------------------------------------------------------]6%Task#00:downloading47/268[==========>---------------------------------------------------]18%Task#01:downloading88/274[===================>------------------------------------------]32%Task#02:installing00:00[=>------------------------------------------------------------]3%Task#03:installing00:09[====>---------------------------------------------------------]7%Task#00:downloading50/268[===========>--------------------------------------------------]19%Task#01:downloading92/274[====================>-----------------------------------------]34%Task#02:installing00:00[===>----------------------------------------------------------]6%Task#03:installing00:09[====>---------------------------------------------------------]9%Task#00:downloading52/268[===========>--------------------------------------------------]19%Task#01:downloading96/274[=====================>----------------------------------------]35%Task#02:installing00:00[=====>--------------------------------------------------------]9%Task#00:downloading54/268[===========>--------------------------------------------------]20%Task#01:downloading98/274[=====================>----------------------------------------]36%Task#02:installing00:03[========>-----------------------------------------------------]14%Task#00:downloading56/268[============>-------------------------------------------------]21%Task#01:downloading102/274[======================>---------------------------------------]37%Task#02:installing00:03[=========>----------------------------------------------------]17%Task#03:installing00:08[=======>------------------------------------------------------]12%Task#00:downloading57/268[============>-------------------------------------------------]21%Task#01:downloading106/274[=======================>--------------------------------------]39%Task#02:installing00:03[===========>--------------------------------------------------]19%Task#03:installing00:08[=======>------------------------------------------------------]14%Task#00:downloading59/268[=============>------------------------------------------------]22%Task#01:downloading114/274[=========================>------------------------------------]42%Task#02:installing00:03[=============>------------------------------------------------]23%Task#03:installing00:08[========>-----------------------------------------------------]14%Task#00:downloading61/268[=============>------------------------------------------------]23%Task#01:downloading120/274[==========================>-----------------------------------]44%Task#02:installing00:03[===============>----------------------------------------------]25%Task#03:installing00:08[=========>----------------------------------------------------]16%Task#00:downloading63/268[==============>-----------------------------------------------]24%Task#01:downloading126/274[============================>---------------------------------]46%Task#02:installing00:02[=================>--------------------------------------------]29%Task#03:installing00:08[=========>----------------------------------------------------]17%Task#00:downloading67/268[===============>----------------------------------------------]25%Task#01:downloading130/274[============================>---------------------------------]47%Task#02:installing00:02[==================>-------------------------------------------]31%Task#03:installing00:08[==========>---------------------------------------------------]17%Task#00:downloading68/268[===============>----------------------------------------------]25%Task#01:downloading132/274[=============================>--------------------------------]48%Task#02:installing00:02[=====================>----------------------------------------]36%Task#03:installing00:08[===========>--------------------------------------------------]19%Task#00:downloading69/268[===============>----------------------------------------------]26%Task#01:downloading136/274[==============================>-------------------------------]50%Task#02:installing00:02[======================>---------------------------------------]37%Task#03:installing00:08[============>-------------------------------------------------]20%Task#00:downloading71/268[===============>----------------------------------------------]26%Task#01:downloading140/274[===============================>------------------------------]51%Task#02:installing00:02[========================>-------------------------------------]41%Task#03:installing00:07[============>-------------------------------------------------]22%Task#00:downloading73/268[================>---------------------------------------------]27%Task#01:downloading144/274[================================>-----------------------------]53%Task#02:installing00:02[=========================>------------------------------------]42%Task#00:downloading75/268[================>---------------------------------------------]28%Task#01:downloading150/274[=================================>----------------------------]55%Task#02:installing00:02[===========================>----------------------------------]45%Task#00:downloading78/268[=================>--------------------------------------------]29%Task#01:downloading154/274[==================================>---------------------------]56%Task#02:installing00:02[============================>---------------------------------]47%Task#03:installing00:07[===============>----------------------------------------------]26%Task#00:downloading80/268[==================>-------------------------------------------]30%Task#01:downloading158/274[===================================>--------------------------]58%Task#02:installing00:02[==============================>-------------------------------]50%Task#03:installing00:07[================>---------------------------------------------]27%Task#00:downloading82/268[==================>-------------------------------------------]31%Task#01:downloading162/274[====================================>-------------------------]59%Task#02:installing00:02[===============================>------------------------------]51%Task#03:installing00:06[=================>--------------------------------------------]29%Task#00:downloading87/268[===================>------------------------------------------]32%Task#01:downloading164/274[====================================>-------------------------]60%Task#02:installing00:01[==================================>---------------------------]56%Task#03:installing00:06[==================>-------------------------------------------]31%Task#00:downloading90/268[====================>-----------------------------------------]34%Task#01:downloading166/274[=====================================>------------------------]61%Task#02:installing00:01[====================================>-------------------------]60%Task#03:installing00:06[===================>------------------------------------------]32%Task#00:downloading92/268[====================>-----------------------------------------]34%Task#01:downloading172/274[======================================>-----------------------]63%Task#02:installing00:01[======================================>-----------------------]62%Task#03:installing00:05[====================>-----------------------------------------]34%Task#00:downloading93/268[=====================>----------------------------------------]35%Task#01:downloading176/274[=======================================>----------------------]64%Task#02:installing00:01[=======================================>----------------------]65%Task#03:installing00:05[=====================>----------------------------------------]35%Task#00:downloading96/268[=====================>----------------------------------------]36%Task#01:downloading178/274[=======================================>----------------------]65%Task#02:installing00:01[==========================================>-------------------]69%Task#03:installing00:05[=====================>----------------------------------------]36%Task#00:downloading99/268[======================>---------------------------------------]37%Task#01:downloading182/274[========================================>---------------------]66%Task#02:installing00:01[===========================================>------------------]71%Task#03:installing00:05[======================>---------------------------------------]37%Task#00:downloading102/268[=======================>--------------------------------------]38%Task#01:downloading186/274[=========================================>--------------------]68%Task#02:installing00:00[==============================================>---------------]75%Task#03:installing00:06[======================>---------------------------------------]37%Task#00:downloading105/268[=======================>--------------------------------------]39%Task#01:downloading188/274[==========================================>-------------------]69%Task#02:installing00:00[===============================================>--------------]78%Task#03:installing00:06[=======================>--------------------------------------]39%Task#00:downloading107/268[========================>-------------------------------------]40%Task#01:downloading192/274[==========================================>-------------------]70%Task#02:installing00:00[=================================================>------------]80%Task#03:installing00:05[========================>-------------------------------------]40%Task#00:downloading109/268[========================>-------------------------------------]41%Task#01:downloading194/274[===========================================>------------------]71%Task#02:installing00:00[==================================================>-----------]82%Task#03:installing00:05[========================>-------------------------------------]41%Task#00:downloading112/268[=========================>------------------------------------]42%Task#01:downloading198/274[============================================>-----------------]72%Task#02:installing00:00[===================================================>----------]84%Task#03:installing00:05[=========================>------------------------------------]42%Task#00:downloading114/268[=========================>------------------------------------]43%Task#01:downloading202/274[=============================================>----------------]74%Task#02:installing00:00[=====================================================>--------]88%Task#03:installing00:05[==========================>-----------------------------------]44%Task#00:downloading116/268[==========================>-----------------------------------]43%Task#01:downloading206/274[==============================================>---------------]75%Task#02:installing00:00[========================================================>-----]92%Task#03:installing00:04[===========================>----------------------------------]46%Task#00:downloading119/268[===========================>----------------------------------]44%Task#01:downloading210/274[===============================================>--------------]77%Task#02:installing00:00[=========================================================>----]94%Task#03:installing00:04[=============================>--------------------------------]48%Task#00:downloading122/268[===========================>----------------------------------]46%Task#01:downloading212/274[===============================================>--------------]77%Task#02:installing00:00[===========================================================>--]97%Task#03:installing00:04[=============================>--------------------------------]49%Task#00:downloading124/268[============================>---------------------------------]46%Task#01:downloading214/274[===============================================>--------------]78%Task#02:installing00:00[==============================================================]99%Task#03:installing00:04[==============================>-------------------------------]50%Task#00:downloading126/268[============================>---------------------------------]47%Task#01:downloading218/274[================================================>-------------]80%Task#02:installing00:00[==============================================================]100%Task#03:installing00:04[===============================>------------------------------]52%Task#00:downloading127/268[============================>---------------------------------]47%Task#01:downloading220/274[=================================================>------------]80%Task#03:installing00:04[================================>-----------------------------]53%Task#00:downloading130/268[=============================>--------------------------------]49%Task#01:downloading224/274[==================================================>-----------]82%Task#03:installing00:03[=================================>----------------------------]55%Task#00:downloading132/268[==============================>-------------------------------]49%Task#01:downloading230/274[===================================================>----------]84%Task#03:installing00:03[==================================>---------------------------]57%Task#00:downloading134/268[==============================>-------------------------------]50%Task#01:downloading234/274[====================================================>---------]85%Task#03:installing00:03[====================================>-------------------------]59%Task#00:downloading136/268[==============================>-------------------------------]51%Task#01:downloading238/274[=====================================================>--------]87%Task#03:installing00:03[====================================>-------------------------]60%Task#00:downloading139/268[===============================>------------------------------]52%Task#01:downloading242/274[======================================================>-------]88%Task#03:installing00:03[=====================================>------------------------]61%Task#00:downloading141/268[================================>-----------------------------]53%Task#01:downloading246/274[=======================================================>------]90%Task#03:installing00:03[======================================>-----------------------]63%Task#00:downloading143/268[================================>-----------------------------]53%Task#01:downloading254/274[========================================================>-----]93%Task#03:installing00:02[=======================================>----------------------]65%Task#00:downloading147/268[=================================>----------------------------]55%Task#01:downloading258/274[=========================================================>----]94%Task#03:installing00:02[========================================>---------------------]66%Task#00:downloading149/268[=================================>----------------------------]56%Task#01:downloading262/274[==========================================================>---]96%Task#03:installing00:02[=========================================>--------------------]68%Task#00:downloading150/268[==================================>---------------------------]56%Task#01:downloading268/274[============================================================>-]98%Task#03:installing00:02[==========================================>-------------------]69%Task#00:downloading152/268[==================================>---------------------------]57%Task#01:downloading272/274[==============================================================]99%Task#03:installing00:02[===========================================>------------------]70%Task#00:downloading156/268[===================================>--------------------------]58%Task#01:downloading274/274[==============================================================]100%Task#03:installing00:02[===========================================>------------------]71%Task#00:downloading160/268[====================================>-------------------------]60%Task#01:installing00:00[==>-----------------------------------------------------------]5%Task#03:installing00:02[============================================>-----------------]73%Task#00:downloading162/268[====================================>-------------------------]60%Task#01:installing00:00[====>---------------------------------------------------------]8%Task#03:installing00:02[=============================================>----------------]74%Task#00:downloading163/268[=====================================>------------------------]61%Task#01:installing00:00[=======>------------------------------------------------------]14%Task#03:installing00:02[=============================================>----------------]75%Task#00:downloading168/268[======================================>-----------------------]63%Task#01:installing00:00[==========>---------------------------------------------------]17%Task#03:installing00:02[==============================================>---------------]76%Task#00:downloading170/268[======================================>-----------------------]63%Task#01:installing00:02[============>-------------------------------------------------]20%Task#03:installing00:02[===============================================>--------------]77%Task#00:downloading173/268[=======================================>----------------------]65%Task#01:installing00:02[===============>----------------------------------------------]25%Task#03:installing00:01[================================================>-------------]78%Task#00:downloading176/268[========================================>---------------------]66%Task#01:installing00:02[==================>-------------------------------------------]31%Task#03:installing00:01[================================================>-------------]79%Task#00:downloading178/268[========================================>---------------------]66%Task#01:installing00:01[=====================>----------------------------------------]36%Task#03:installing00:01[=================================================>------------]81%Task#00:downloading180/268[=========================================>--------------------]67%Task#01:installing00:01[======================>---------------------------------------]37%Task#03:installing00:01[==================================================>-----------]83%Task#00:downloading182/268[=========================================>--------------------]68%Task#01:installing00:01[========================>-------------------------------------]41%Task#03:installing00:01[===================================================>----------]83%Task#00:downloading185/268[==========================================>-------------------]69%Task#01:installing00:01[=========================>------------------------------------]42%Task#03:installing00:01[===================================================>----------]84%Task#00:downloading188/268[==========================================>-------------------]70%Task#01:installing00:01[===========================>----------------------------------]46%Task#03:installing00:01[====================================================>---------]85%Task#00:downloading190/268[===========================================>------------------]71%Task#01:installing00:01[=============================>--------------------------------]49%Task#03:installing00:01[=====================================================>--------]87%Task#00:downloading192/268[===========================================>------------------]72%Task#01:installing00:01[================================>-----------------------------]53%Task#03:installing00:01[======================================================>-------]88%Task#00:downloading194/268[============================================>-----------------]72%Task#01:installing00:01[=================================>----------------------------]54%Task#03:installing00:00[======================================================>-------]89%Task#00:downloading197/268[=============================================>----------------]74%Task#01:installing00:01[===================================>--------------------------]58%Task#03:installing00:00[=======================================================>------]91%Task#00:downloading198/268[=============================================>----------------]74%Task#01:installing00:01[=====================================>------------------------]61%Task#03:installing00:00[========================================================>-----]93%Task#00:downloading202/268[==============================================>---------------]75%Task#01:installing00:01[======================================>-----------------------]63%Task#03:installing00:00[=========================================================>----]93%Task#00:downloading204/268[==============================================>---------------]76%Task#01:installing00:01[========================================>---------------------]66%Task#03:installing00:00[==========================================================>---]94%Task#00:downloading206/268[===============================================>--------------]77%Task#01:installing00:01[==========================================>-------------------]69%Task#03:installing00:00[===========================================================>--]97%Task#00:downloading209/268[===============================================>--------------]78%Task#01:installing00:00[============================================>-----------------]73%Task#03:installing00:00[===========================================================>--]98%Task#00:downloading212/268[================================================>-------------]79%Task#01:installing00:00[==============================================>---------------]76%Task#03:installing00:00[============================================================>-]99%Task#00:downloading213/268[================================================>-------------]79%Task#01:installing00:00[================================================>-------------]80%Task#03:installing00:00[==============================================================]100%Task#00:downloading215/268[=================================================>------------]80%Task#01:installing00:00[=================================================>------------]81%Task#00:downloading217/268[=================================================>------------]81%Task#01:installing00:00[=====================================================>--------]86%Task#00:downloading219/268[==================================================>-----------]82%Task#01:installing00:00[======================================================>-------]88%Task#00:downloading220/268[==================================================>-----------]82%Task#01:installing00:00[========================================================>-----]92%Task#00:downloading221/268[==================================================>-----------]82%Task#01:installing00:00[=========================================================>----]93%Task#00:downloading225/268[===================================================>----------]84%Task#01:installing00:00[===========================================================>--]97%Task#00:downloading227/268[====================================================>---------]85%Task#01:installing00:00[============================================================>-]98%Task#00:downloading229/268[====================================================>---------]85%Task#01:installing00:00[==============================================================]100%Task#00:downloading230/268[====================================================>---------]86%Task#00:downloading232/268[=====================================================>--------]87%Task#00:downloading233/268[=====================================================>--------]87%Task#00:downloading235/268[=====================================================>--------]88%Task#00:downloading236/268[======================================================>-------]88%Task#00:downloading237/268[======================================================>-------]88%Task#00:downloading240/268[=======================================================>------]90%Task#00:downloading241/268[=======================================================>------]90%Task#00:downloading244/268[=======================================================>------]91%Task#00:downloading246/268[========================================================>-----]92%Task#00:downloading249/268[=========================================================>----]93%Task#00:downloading251/268[=========================================================>----]94%Task#00:downloading253/268[==========================================================>---]94%Task#00:downloading256/268[==========================================================>---]96%Task#00:downloading257/268[==========================================================>---]96%Task#00:downloading259/268[===========================================================>--]97%Task#00:downloading261/268[===========================================================>--]97%Task#00:downloading262/268[============================================================>-]98%Task#00:downloading263/268[============================================================>-]98%Task#00:downloading266/268[==============================================================]99%Task#00:downloading268/268[==============================================================]100%Task#00:installing00:00[=>------------------------------------------------------------]3%Task#00:installing00:00[======>-------------------------------------------------------]12%Task#00:installing00:00[========>-----------------------------------------------------]14%Task#00:installing00:00[============>-------------------------------------------------]20%Task#00:installing00:00[=================>--------------------------------------------]29%Task#00:installing00:01[===================>------------------------------------------]32%Task#00:installing00:01[==========================>-----------------------------------]43%Task#00:installing00:01[==============================>-------------------------------]49%Task#00:installing00:00[=================================>----------------------------]55%Task#00:installing00:00[=====================================>------------------------]61%Task#00:installing00:00[========================================>---------------------]67%Task#00:installing00:00[============================================>-----------------]72%Task#00:installing00:00[=================================================>------------]81%Task#00:installing00:00[=====================================================>--------]87%Task#00:installing00:00[=========================================================>----]93%Task#00:installing00:00[============================================================>-]99%Task#00:installing00:00[==============================================================]100% \ No newline at end of file diff --git a/bar.go b/bar.go index a304a87..1a4c66f 100644 --- a/bar.go +++ b/bar.go @@ -5,171 +5,126 @@ "context" "fmt" "io" - "io/ioutil" + "log" "strings" - "sync" "time" "unicode/utf8" - "github.com/vbauerster/mpb/decor" + "github.com/vbauerster/mpb/v5/decor" ) -// Bar represents a progress Bar +// BarFiller interface. +// Bar renders itself by calling BarFiller's Fill method. You can +// literally have any bar kind, by implementing this interface and +// passing it to the *Progress.Add(...) *Bar method. +type BarFiller interface { + Fill(w io.Writer, width int, stat *decor.Statistics) +} + +// BarFillerFunc is function type adapter to convert function into Filler. +type BarFillerFunc func(w io.Writer, width int, stat *decor.Statistics) + +func (f BarFillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) { + f(w, width, stat) +} + +// Bar represents a progress Bar. type Bar struct { + priority int // used by heap + index int // used by heap + + extendedLines int + toShutdown bool + toDrop bool + noPop bool + hasEwmaDecorators bool + operateState chan func(*bState) + frameCh chan io.Reader + syncTableCh chan [][]chan int + completed chan bool + + // cancel is called either by user or on complete event + cancel func() + // done is closed after cacheState is assigned + done chan struct{} + // cacheState is populated, right after close(shutdown) + cacheState *bState + + container *Progress + dlogger *log.Logger + recoveredPanic interface{} +} + +type extFunc func(in io.Reader, tw int, st *decor.Statistics) (out io.Reader, lines int) + +type bState struct { + baseF BarFiller + filler BarFiller + id int + width int + total int64 + current int64 + lastN int64 + iterated bool + trimSpace bool + toComplete bool + completeFlushed bool + noPop bool + aDecorators []decor.Decorator + pDecorators []decor.Decorator + averageDecorators []decor.AverageDecorator + ewmaDecorators []decor.EwmaDecorator + shutdownListeners []decor.ShutdownListener + bufP, bufB, bufA *bytes.Buffer + extender extFunc + + // priority overrides *Bar's priority, if set priority int - index int - - runningBar *Bar - cacheState *bState - operateState chan func(*bState) - int64Ch chan int64 - boolCh chan bool - frameReaderCh chan *frameReader - syncTableCh chan [][]chan int - - // done is closed by Bar's goroutine, after cacheState is written - done chan struct{} - // shutdown is closed from master Progress goroutine only - shutdown chan struct{} -} - -// Filler interface. -// Bar renders by calling Filler's Fill method. You can literally have -// any bar kind, by implementing this interface and passing it to the -// Add method. -type Filler interface { - Fill(w io.Writer, width int, s *decor.Statistics) -} - -// FillerFunc is function type adapter to convert function into Filler. -type FillerFunc func(w io.Writer, width int, stat *decor.Statistics) - -func (f FillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) { - f(w, width, stat) -} - -type ( - bState struct { - filler Filler - id int - width int - alignment int - total int64 - current int64 - trimSpace bool - toComplete bool - removeOnComplete bool - barClearOnComplete bool - completeFlushed bool - aDecorators []decor.Decorator - pDecorators []decor.Decorator - amountReceivers []decor.AmountReceiver - shutdownListeners []decor.ShutdownListener - refill *refill - bufP, bufB, bufA *bytes.Buffer - bufNL *bytes.Buffer - panicMsg string - newLineExtendFn func(io.Writer, *decor.Statistics) - - // following options are assigned to the *Bar - priority int - runningBar *Bar - } - refill struct { - r rune - limit int64 - } - frameReader struct { - io.Reader - extendedLines int - toShutdown bool - removeOnComplete bool - } -) - -func newBar( - ctx context.Context, - wg *sync.WaitGroup, - filler Filler, - id, width int, - total int64, - options ...BarOption, -) *Bar { - - s := &bState{ - filler: filler, - id: id, - priority: id, - width: width, - total: total, - } - - for _, opt := range options { - if opt != nil { - opt(s) - } - } - - s.bufP = bytes.NewBuffer(make([]byte, 0, s.width)) - s.bufB = bytes.NewBuffer(make([]byte, 0, s.width)) - s.bufA = bytes.NewBuffer(make([]byte, 0, s.width)) - if s.newLineExtendFn != nil { - s.bufNL = bytes.NewBuffer(make([]byte, 0, s.width)) - } - - b := &Bar{ - priority: s.priority, - runningBar: s.runningBar, - operateState: make(chan func(*bState)), - int64Ch: make(chan int64), - boolCh: make(chan bool), - frameReaderCh: make(chan *frameReader, 1), - syncTableCh: make(chan [][]chan int), - done: make(chan struct{}), - shutdown: make(chan struct{}), - } - - if b.runningBar != nil { - b.priority = b.runningBar.priority - } - - go b.serve(ctx, wg, s) - return b -} - -// RemoveAllPrependers removes all prepend functions. -func (b *Bar) RemoveAllPrependers() { - select { - case b.operateState <- func(s *bState) { s.pDecorators = nil }: - case <-b.done: - } -} - -// RemoveAllAppenders removes all append functions. -func (b *Bar) RemoveAllAppenders() { - select { - case b.operateState <- func(s *bState) { s.aDecorators = nil }: - case <-b.done: - } + // dropOnComplete propagates to *Bar + dropOnComplete bool + // runningBar is a key for *pState.parkedBars + runningBar *Bar + + debugOut io.Writer +} + +func newBar(container *Progress, bs *bState) *Bar { + logPrefix := fmt.Sprintf("%sbar#%02d ", container.dlogger.Prefix(), bs.id) + ctx, cancel := context.WithCancel(container.ctx) + + bar := &Bar{ + container: container, + priority: bs.priority, + toDrop: bs.dropOnComplete, + noPop: bs.noPop, + operateState: make(chan func(*bState)), + frameCh: make(chan io.Reader, 1), + syncTableCh: make(chan [][]chan int), + completed: make(chan bool, 1), + done: make(chan struct{}), + cancel: cancel, + dlogger: log.New(bs.debugOut, logPrefix, log.Lshortfile), + } + + go bar.serve(ctx, bs) + return bar } // ProxyReader wraps r with metrics required for progress tracking. +// Panics if r is nil. func (b *Bar) ProxyReader(r io.Reader) io.ReadCloser { if r == nil { - panic("expect io.Reader, got nil") - } - rc, ok := r.(io.ReadCloser) - if !ok { - rc = ioutil.NopCloser(r) - } - return &proxyReader{rc, b, time.Now()} + panic("expected non nil io.Reader") + } + return newProxyReader(r, b) } // ID returs id of the bar. func (b *Bar) ID() int { - select { - case b.operateState <- func(s *bState) { b.int64Ch <- int64(s.id) }: - return int(<-b.int64Ch) + result := make(chan int) + select { + case b.operateState <- func(s *bState) { result <- s.id }: + return <-result case <-b.done: return b.cacheState.id } @@ -177,94 +132,181 @@ // Current returns bar's current number, in other words sum of all increments. func (b *Bar) Current() int64 { - select { - case b.operateState <- func(s *bState) { b.int64Ch <- s.current }: - return <-b.int64Ch + result := make(chan int64) + select { + case b.operateState <- func(s *bState) { result <- s.current }: + return <-result case <-b.done: return b.cacheState.current } } +// SetRefill fills bar with refill rune up to amount argument. +// Given default bar style is "[=>-]<+", refill rune is '+'. +// To set bar style use mpb.BarStyle(string) BarOption. +func (b *Bar) SetRefill(amount int64) { + type refiller interface { + SetRefill(int64) + } + b.operateState <- func(s *bState) { + if f, ok := s.baseF.(refiller); ok { + f.SetRefill(amount) + } + } +} + +// TraverseDecorators traverses all available decorators and calls cb func on each. +func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) { + b.operateState <- func(s *bState) { + for _, decorators := range [...][]decor.Decorator{ + s.pDecorators, + s.aDecorators, + } { + for _, d := range decorators { + cb(extractBaseDecorator(d)) + } + } + } +} + // SetTotal sets total dynamically. -// Set complete to true, to trigger bar complete event now. +// If total is less or equal to zero it takes progress' current value. +// If complete is true, complete event will be triggered. func (b *Bar) SetTotal(total int64, complete bool) { select { case b.operateState <- func(s *bState) { - s.total = total + if total <= 0 { + s.total = s.current + } else { + s.total = total + } if complete && !s.toComplete { s.current = s.total s.toComplete = true - } - }: - case <-b.done: - } -} - -// SetRefill sets refill, if supported by underlying Filler. -func (b *Bar) SetRefill(amount int64) { - b.operateState <- func(s *bState) { - if f, ok := s.filler.(interface{ SetRefill(int64) }); ok { - f.SetRefill(amount) - } - } -} - -// Increment is a shorthand for b.IncrBy(1). -func (b *Bar) Increment() { - b.IncrBy(1) -} - -// IncrBy increments progress bar by amount of n. -// wdd is optional work duration i.e. time.Since(start), which expected -// to be provided, if any ewma based decorator is used. -func (b *Bar) IncrBy(n int, wdd ...time.Duration) { - select { - case b.operateState <- func(s *bState) { - s.current += int64(n) + go b.refreshTillShutdown() + } + }: + case <-b.done: + } +} + +// SetCurrent sets progress' current to an arbitrary value. +func (b *Bar) SetCurrent(current int64) { + select { + case b.operateState <- func(s *bState) { + s.iterated = true + s.lastN = current - s.current + s.current = current if s.total > 0 && s.current >= s.total { s.current = s.total s.toComplete = true - } - for _, ar := range s.amountReceivers { - ar.NextAmount(n, wdd...) - } - }: - case <-b.done: + go b.refreshTillShutdown() + } + }: + case <-b.done: + } +} + +// Increment is a shorthand for b.IncrInt64(1). +func (b *Bar) Increment() { + b.IncrInt64(1) +} + +// IncrBy is a shorthand for b.IncrInt64(int64(n)). +func (b *Bar) IncrBy(n int) { + b.IncrInt64(int64(n)) +} + +// IncrInt64 increments progress by amount of n. +func (b *Bar) IncrInt64(n int64) { + select { + case b.operateState <- func(s *bState) { + s.iterated = true + s.lastN = n + s.current += n + if s.total > 0 && s.current >= s.total { + s.current = s.total + s.toComplete = true + go b.refreshTillShutdown() + } + }: + case <-b.done: + } +} + +// DecoratorEwmaUpdate updates all EWMA based decorators. Should be +// called on each iteration, because EWMA's unit of measure is an +// iteration's duration. Panics if called before *Bar.Incr... family +// methods. +func (b *Bar) DecoratorEwmaUpdate(dur time.Duration) { + select { + case b.operateState <- func(s *bState) { + ewmaIterationUpdate(false, s, dur) + }: + case <-b.done: + ewmaIterationUpdate(true, b.cacheState, dur) + } +} + +// DecoratorAverageAdjust adjusts all average based decorators. Call +// if you need to adjust start time of all average based decorators +// or after progress resume. +func (b *Bar) DecoratorAverageAdjust(start time.Time) { + select { + case b.operateState <- func(s *bState) { + for _, d := range s.averageDecorators { + d.AverageAdjust(start) + } + }: + case <-b.done: + } +} + +// SetPriority changes bar's order among multiple bars. Zero is highest +// priority, i.e. bar will be on top. If you don't need to set priority +// dynamically, better use BarPriority option. +func (b *Bar) SetPriority(priority int) { + select { + case <-b.done: + default: + b.container.setBarPriority(b, priority) + } +} + +// Abort interrupts bar's running goroutine. Call this, if you'd like +// to stop/remove bar before completion event. It has no effect after +// completion event. If drop is true bar will be removed as well. +func (b *Bar) Abort(drop bool) { + select { + case <-b.done: + default: + if drop { + b.container.dropBar(b) + } + b.cancel() } } // Completed reports whether the bar is in completed state. func (b *Bar) Completed() bool { - // omit select here, because primary usage of the method is for loop - // condition, like for !bar.Completed() {...} so when toComplete=true - // it is called once (at which time, the bar is still alive), then - // quits the loop and never suppose to be called afterwards. - return <-b.boolCh -} - -func (b *Bar) wSyncTable() [][]chan int { - select { - case b.operateState <- func(s *bState) { b.syncTableCh <- s.wSyncTable() }: - return <-b.syncTableCh - case <-b.done: - return b.cacheState.wSyncTable() - } -} - -func (b *Bar) serve(ctx context.Context, wg *sync.WaitGroup, s *bState) { - defer wg.Done() - cancel := ctx.Done() + select { + case b.operateState <- func(s *bState) { b.completed <- s.toComplete }: + return <-b.completed + case <-b.done: + return true + } +} + +func (b *Bar) serve(ctx context.Context, s *bState) { + defer b.container.bwg.Done() for { select { case op := <-b.operateState: op(s) - case b.boolCh <- s.toComplete: - case <-cancel: - s.toComplete = true - cancel = nil - case <-b.shutdown: + case <-ctx.Done(): b.cacheState = s close(b.done) + // Notifying decorators about shutdown event for _, sl := range s.shutdownListeners { sl.Shutdown() } @@ -273,60 +315,88 @@ } } -func (b *Bar) render(debugOut io.Writer, tw int) { +func (b *Bar) render(tw int) { + if b.recoveredPanic != nil { + b.toShutdown = false + b.frameCh <- b.panicToFrame(tw) + return + } select { case b.operateState <- func(s *bState) { defer func() { // recovering if user defined decorator panics for example if p := recover(); p != nil { - s.panicMsg = fmt.Sprintf("panic: %v", p) - fmt.Fprintf(debugOut, "%s %s bar id %02d %v\n", "[mpb]", time.Now(), s.id, s.panicMsg) - b.frameReaderCh <- &frameReader{ - Reader: strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", tw), s.panicMsg)), - toShutdown: true, - } + b.dlogger.Println(p) + b.recoveredPanic = p + b.toShutdown = !s.completeFlushed + b.frameCh <- b.panicToFrame(tw) } }() - r := s.draw(tw) - var extendedLines int - if s.newLineExtendFn != nil { - s.bufNL.Reset() - s.newLineExtendFn(s.bufNL, newStatistics(s)) - extendedLines = countLines(s.bufNL.Bytes()) - r = io.MultiReader(r, s.bufNL) - } - b.frameReaderCh <- &frameReader{ - Reader: r, - extendedLines: extendedLines, - toShutdown: s.toComplete && !s.completeFlushed, - removeOnComplete: s.removeOnComplete, - } + + st := newStatistics(s) + frame := s.draw(tw, st) + frame, b.extendedLines = s.extender(frame, tw, st) + + b.toShutdown = s.toComplete && !s.completeFlushed s.completeFlushed = s.toComplete + b.frameCh <- frame }: case <-b.done: s := b.cacheState - r := s.draw(tw) - var extendedLines int - if s.newLineExtendFn != nil { - s.bufNL.Reset() - s.newLineExtendFn(s.bufNL, newStatistics(s)) - extendedLines = countLines(s.bufNL.Bytes()) - r = io.MultiReader(r, s.bufNL) - } - b.frameReaderCh <- &frameReader{ - Reader: r, - extendedLines: extendedLines, - } - } -} - -func (s *bState) draw(termWidth int) io.Reader { - if s.panicMsg != "" { - return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", termWidth), s.panicMsg)) - } - - stat := newStatistics(s) - + st := newStatistics(s) + frame := s.draw(tw, st) + frame, b.extendedLines = s.extender(frame, tw, st) + b.frameCh <- frame + } +} + +func (b *Bar) panicToFrame(termWidth int) io.Reader { + return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%dv\n", termWidth), b.recoveredPanic)) +} + +func (b *Bar) subscribeDecorators() { + var averageDecorators []decor.AverageDecorator + var ewmaDecorators []decor.EwmaDecorator + var shutdownListeners []decor.ShutdownListener + b.TraverseDecorators(func(d decor.Decorator) { + if d, ok := d.(decor.AverageDecorator); ok { + averageDecorators = append(averageDecorators, d) + } + if d, ok := d.(decor.EwmaDecorator); ok { + ewmaDecorators = append(ewmaDecorators, d) + } + if d, ok := d.(decor.ShutdownListener); ok { + shutdownListeners = append(shutdownListeners, d) + } + }) + b.operateState <- func(s *bState) { + s.averageDecorators = averageDecorators + s.ewmaDecorators = ewmaDecorators + s.shutdownListeners = shutdownListeners + } + b.hasEwmaDecorators = len(ewmaDecorators) != 0 +} + +func (b *Bar) refreshTillShutdown() { + for { + select { + case b.container.refreshCh <- time.Now(): + case <-b.done: + return + } + } +} + +func (b *Bar) wSyncTable() [][]chan int { + select { + case b.operateState <- func(s *bState) { b.syncTableCh <- s.wSyncTable() }: + return <-b.syncTableCh + case <-b.done: + return b.cacheState.wSyncTable() + } +} + +func (s *bState) draw(termWidth int, stat *decor.Statistics) io.Reader { for _, d := range s.pDecorators { s.bufP.WriteString(d.Decor(stat)) } @@ -335,31 +405,24 @@ s.bufA.WriteString(d.Decor(stat)) } - if s.barClearOnComplete && s.completeFlushed { - s.bufA.WriteByte('\n') - return io.MultiReader(s.bufP, s.bufA) - } + s.bufA.WriteByte('\n') prependCount := utf8.RuneCount(s.bufP.Bytes()) - appendCount := utf8.RuneCount(s.bufA.Bytes()) - - if !s.trimSpace { - // reserve space for edge spaces - termWidth -= 2 - s.bufB.WriteByte(' ') - } - - if prependCount+s.width+appendCount > termWidth { - s.filler.Fill(s.bufB, termWidth-prependCount-appendCount, stat) - } else { - s.filler.Fill(s.bufB, s.width, stat) - } - - if !s.trimSpace { - s.bufB.WriteByte(' ') - } - - s.bufA.WriteByte('\n') + appendCount := utf8.RuneCount(s.bufA.Bytes()) - 1 + + if fitWidth := s.width; termWidth > 1 { + if !s.trimSpace { + // reserve space for edge spaces + termWidth -= 2 + s.bufB.WriteByte(' ') + defer s.bufB.WriteByte(' ') + } + if prependCount+s.width+appendCount > termWidth { + fitWidth = termWidth - prependCount - appendCount + } + s.filler.Fill(s.bufB, fitWidth, stat) + } + return io.MultiReader(s.bufP, s.bufB, s.bufA) } @@ -367,14 +430,14 @@ columns := make([]chan int, 0, len(s.pDecorators)+len(s.aDecorators)) var pCount int for _, d := range s.pDecorators { - if ok, ch := d.Syncable(); ok { + if ch, ok := d.Sync(); ok { columns = append(columns, ch) pCount++ } } var aCount int for _, d := range s.aDecorators { - if ok, ch := d.Syncable(); ok { + if ch, ok := d.Sync(); ok { columns = append(columns, ch) aCount++ } @@ -394,6 +457,20 @@ } } -func countLines(b []byte) int { - return bytes.Count(b, []byte("\n")) -} +func extractBaseDecorator(d decor.Decorator) decor.Decorator { + if d, ok := d.(decor.Wrapper); ok { + return extractBaseDecorator(d.Base()) + } + return d +} + +func ewmaIterationUpdate(done bool, s *bState, dur time.Duration) { + if !done && !s.iterated { + panic("increment required before ewma iteration update") + } else { + s.iterated = false + } + for _, d := range s.ewmaDecorators { + d.EwmaUpdate(s.lastN, dur) + } +} diff --git a/bar_filler.go b/bar_filler.go index 4e9285c..00bf0a4 100644 --- a/bar_filler.go +++ b/bar_filler.go @@ -4,8 +4,8 @@ "io" "unicode/utf8" - "github.com/vbauerster/mpb/decor" - "github.com/vbauerster/mpb/internal" + "github.com/vbauerster/mpb/v5/decor" + "github.com/vbauerster/mpb/v5/internal" ) const ( @@ -18,23 +18,47 @@ rRefill ) -var defaultBarStyle = "[=>-]<+" +// DefaultBarStyle is a string containing 7 runes. +// Each rune is a building block of a progress bar. +// +// '1st rune' stands for left boundary rune +// +// '2nd rune' stands for fill rune +// +// '3rd rune' stands for tip rune +// +// '4th rune' stands for empty rune +// +// '5th rune' stands for right boundary rune +// +// '6th rune' stands for reverse tip rune +// +// '7th rune' stands for refill rune +// +const DefaultBarStyle string = "[=>-]<+" type barFiller struct { - format [][]byte - refillAmount int64 - reverse bool + format [][]byte + tip []byte + refill int64 + reverse bool + flush func(w io.Writer, bb [][]byte) } -func newDefaultBarFiller() Filler { +// NewBarFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. +func NewBarFiller(style string, reverse bool) BarFiller { + if style == "" { + style = DefaultBarStyle + } bf := &barFiller{ - format: make([][]byte, utf8.RuneCountInString(defaultBarStyle)), + format: make([][]byte, utf8.RuneCountInString(style)), + reverse: reverse, } - bf.setStyle(defaultBarStyle) + bf.SetStyle(style) return bf } -func (s *barFiller) setStyle(style string) { +func (s *barFiller) SetStyle(style string) { if !utf8.ValidString(style) { return } @@ -43,44 +67,47 @@ src = append(src, []byte(string(r))) } copy(s.format, src) + s.SetReverse(s.reverse) } -func (s *barFiller) setReverse() { - s.reverse = true +func (s *barFiller) SetReverse(reverse bool) { + if reverse { + s.tip = s.format[rRevTip] + s.flush = reverseFlush + } else { + s.tip = s.format[rTip] + s.flush = normalFlush + } + s.reverse = reverse } func (s *barFiller) SetRefill(amount int64) { - s.refillAmount = amount + s.refill = amount } func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { - - // don't count rLeft and rRight [brackets] + // don't count rLeft and rRight as progress width -= 2 if width < 2 { return } - w.Write(s.format[rLeft]) - if width == 2 { - w.Write(s.format[rRight]) - return - } + defer w.Write(s.format[rRight]) bb := make([][]byte, width) - cwidth := int(internal.Percentage(stat.Total, stat.Current, int64(width))) + cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width)) for i := 0; i < cwidth; i++ { bb[i] = s.format[rFill] } - if s.refillAmount > 0 { + if s.refill > 0 { var rwidth int - if s.refillAmount > stat.Current { + if s.refill > stat.Current { rwidth = cwidth } else { - rwidth = int(internal.Percentage(stat.Total, int64(s.refillAmount), int64(width))) + rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width)) } for i := 0; i < rwidth; i++ { bb[i] = s.format[rRefill] @@ -88,24 +115,24 @@ } if cwidth > 0 && cwidth < width { - bb[cwidth-1] = s.format[rTip] + bb[cwidth-1] = s.tip } for i := cwidth; i < width; i++ { bb[i] = s.format[rEmpty] } - if s.reverse { - if cwidth > 0 && cwidth < width { - bb[cwidth-1] = s.format[rRevTip] - } - for i := len(bb) - 1; i >= 0; i-- { - w.Write(bb[i]) - } - } else { - for i := 0; i < len(bb); i++ { - w.Write(bb[i]) - } + s.flush(w, bb) +} + +func normalFlush(w io.Writer, bb [][]byte) { + for i := 0; i < len(bb); i++ { + w.Write(bb[i]) } - w.Write(s.format[rRight]) } + +func reverseFlush(w io.Writer, bb [][]byte) { + for i := len(bb) - 1; i >= 0; i-- { + w.Write(bb[i]) + } +} diff --git a/bar_option.go b/bar_option.go index e9a4bd2..76f2050 100644 --- a/bar_option.go +++ b/bar_option.go @@ -1,41 +1,38 @@ package mpb import ( + "bytes" "io" - "github.com/vbauerster/mpb/decor" + "github.com/vbauerster/mpb/v5/decor" ) // BarOption is a function option which changes the default behavior of a bar. type BarOption func(*bState) +func (s *bState) addDecorators(dest *[]decor.Decorator, decorators ...decor.Decorator) { + type mergeWrapper interface { + MergeUnwrap() []decor.Decorator + } + for _, decorator := range decorators { + if mw, ok := decorator.(mergeWrapper); ok { + *dest = append(*dest, mw.MergeUnwrap()...) + } + *dest = append(*dest, decorator) + } +} + // AppendDecorators let you inject decorators to the bar's right side. -func AppendDecorators(appenders ...decor.Decorator) BarOption { - return func(s *bState) { - for _, decorator := range appenders { - if ar, ok := decorator.(decor.AmountReceiver); ok { - s.amountReceivers = append(s.amountReceivers, ar) - } - if sl, ok := decorator.(decor.ShutdownListener); ok { - s.shutdownListeners = append(s.shutdownListeners, sl) - } - s.aDecorators = append(s.aDecorators, decorator) - } +func AppendDecorators(decorators ...decor.Decorator) BarOption { + return func(s *bState) { + s.addDecorators(&s.aDecorators, decorators...) } } // PrependDecorators let you inject decorators to the bar's left side. -func PrependDecorators(prependers ...decor.Decorator) BarOption { - return func(s *bState) { - for _, decorator := range prependers { - if ar, ok := decorator.(decor.AmountReceiver); ok { - s.amountReceivers = append(s.amountReceivers, ar) - } - if sl, ok := decorator.(decor.ShutdownListener); ok { - s.shutdownListeners = append(s.shutdownListeners, sl) - } - s.pDecorators = append(s.pDecorators, decorator) - } +func PrependDecorators(decorators ...decor.Decorator) BarOption { + return func(s *bState) { + s.addDecorators(&s.pDecorators, decorators...) } } @@ -53,37 +50,46 @@ } } -// BarRemoveOnComplete is a flag, if set whole bar line will be removed -// on complete event. If both BarRemoveOnComplete and BarClearOnComplete -// are set, first bar section gets cleared and then whole bar line -// gets removed completely. +// BarQueueAfter queues this (being constructed) bar to relplace +// runningBar after it has been completed. +func BarQueueAfter(runningBar *Bar) BarOption { + if runningBar == nil { + return nil + } + return func(s *bState) { + s.runningBar = runningBar + } +} + +// BarRemoveOnComplete removes both bar's filler and its decorators +// on complete event. func BarRemoveOnComplete() BarOption { return func(s *bState) { - s.removeOnComplete = true - } -} - -// BarReplaceOnComplete is indicator for delayed bar start, after the -// `runningBar` is complete. To achieve bar replacement effect, -// `runningBar` should has its `BarRemoveOnComplete` option set. -func BarReplaceOnComplete(runningBar *Bar) BarOption { - return BarParkTo(runningBar) -} - -// BarParkTo same as BarReplaceOnComplete -func BarParkTo(runningBar *Bar) BarOption { - return func(s *bState) { - s.runningBar = runningBar - } -} - -// BarClearOnComplete is a flag, if set will clear bar section on -// complete event. If you need to remove a whole bar line, refer to -// BarRemoveOnComplete. -func BarClearOnComplete() BarOption { - return func(s *bState) { - s.barClearOnComplete = true - } + s.dropOnComplete = true + } +} + +// BarFillerClearOnComplete clears bar's filler on complete event. +// It's shortcut for BarFillerOnComplete(""). +func BarFillerClearOnComplete() BarOption { + return BarFillerOnComplete("") +} + +// BarFillerOnComplete replaces bar's filler with message, on complete event. +func BarFillerOnComplete(message string) BarOption { + return func(s *bState) { + s.filler = makeBarFillerOnComplete(s.baseF, message) + } +} + +func makeBarFillerOnComplete(filler BarFiller, message string) BarFiller { + return BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { + if st.Completed { + io.WriteString(w, message) + } else { + filler.Fill(w, width, st) + } + }) } // BarPriority sets bar's priority. Zero is highest priority, i.e. bar @@ -95,12 +101,23 @@ } } -// BarNewLineExtend takes user defined efn, which gets called each -// render cycle. Any write to provided writer of efn, will appear on -// new line of respective bar. -func BarNewLineExtend(efn func(io.Writer, *decor.Statistics)) BarOption { - return func(s *bState) { - s.newLineExtendFn = efn +// BarExtender is an option to extend bar to the next new line, with +// arbitrary output. +func BarExtender(extender BarFiller) BarOption { + if extender == nil { + return nil + } + return func(s *bState) { + s.extender = makeExtFunc(extender) + } +} + +func makeExtFunc(extender BarFiller) extFunc { + buf := new(bytes.Buffer) + nl := []byte("\n") + return func(r io.Reader, tw int, st *decor.Statistics) (io.Reader, int) { + extender.Fill(buf, tw, st) + return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), nl) } } @@ -111,56 +128,50 @@ } } -// BarStyle sets custom bar style, default one is "[=>-]<+". -// -// '[' left bracket rune -// -// '=' fill rune -// -// '>' tip rune -// -// '-' empty rune -// -// ']' right bracket rune -// -// '<' reverse tip rune, used when BarReverse option is set -// -// '+' refill rune, used when *Bar.SetRefill(int64) is called -// -// It's ok to provide first five runes only, for example mpb.BarStyle("╢▌▌░╟") +// BarStyle overrides mpb.DefaultBarStyle which is "[=>-]<+". +// It's ok to pass string containing just 5 runes, for example "╢▌▌░╟", +// if you don't need to override '<' (reverse tip) and '+' (refill rune). func BarStyle(style string) BarOption { - chk := func(filler Filler) (interface{}, bool) { - if style == "" { - return nil, false - } - t, ok := filler.(*barFiller) - return t, ok - } - cb := func(t interface{}) { - t.(*barFiller).setStyle(style) - } - return MakeFillerTypeSpecificBarOption(chk, cb) + if style == "" { + return nil + } + type styleSetter interface { + SetStyle(string) + } + return func(s *bState) { + if t, ok := s.baseF.(styleSetter); ok { + t.SetStyle(style) + } + } +} + +// BarNoPop disables bar pop out of container. Effective when +// PopCompletedMode of container is enabled. +func BarNoPop() BarOption { + return func(s *bState) { + s.noPop = true + } } // BarReverse reverse mode, bar will progress from right to left. func BarReverse() BarOption { - chk := func(filler Filler) (interface{}, bool) { - t, ok := filler.(*barFiller) - return t, ok - } - cb := func(t interface{}) { - t.(*barFiller).setReverse() - } - return MakeFillerTypeSpecificBarOption(chk, cb) + type revSetter interface { + SetReverse(bool) + } + return func(s *bState) { + if t, ok := s.baseF.(revSetter); ok { + t.SetReverse(true) + } + } } // SpinnerStyle sets custom spinner style. // Effective when Filler type is spinner. func SpinnerStyle(frames []string) BarOption { - chk := func(filler Filler) (interface{}, bool) { - if len(frames) == 0 { - return nil, false - } + if len(frames) == 0 { + return nil + } + chk := func(filler BarFiller) (interface{}, bool) { t, ok := filler.(*spinnerFiller) return t, ok } @@ -174,18 +185,18 @@ // actual type. If you implement your own Filler, so most probably // you'll need this. See BarStyle or SpinnerStyle for example. func MakeFillerTypeSpecificBarOption( - typeChecker func(Filler) (interface{}, bool), + typeChecker func(BarFiller) (interface{}, bool), cb func(interface{}), ) BarOption { return func(s *bState) { - if t, ok := typeChecker(s.filler); ok { + if t, ok := typeChecker(s.baseF); ok { cb(t) } } } -// OptionOnCondition returns option when condition evaluates to true. -func OptionOnCondition(option BarOption, condition func() bool) BarOption { +// BarOptOn returns option when condition evaluates to true. +func BarOptOn(option BarOption, condition func() bool) BarOption { if condition() { return option } diff --git a/bar_test.go b/bar_test.go index 51a8924..b442195 100644 --- a/bar_test.go +++ b/bar_test.go @@ -5,12 +5,13 @@ "fmt" "io/ioutil" "strings" + "sync/atomic" "testing" "time" "unicode/utf8" - . "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" + . "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" ) func TestBarCompleted(t *testing.T) { @@ -33,23 +34,23 @@ func TestBarID(t *testing.T) { p := New(WithOutput(ioutil.Discard)) - total := 80 + total := 100 wantID := 11 bar := p.AddBar(int64(total), BarID(wantID)) - go func() { + go func(total int) { for i := 0; i < total; i++ { time.Sleep(50 * time.Millisecond) bar.Increment() } - }() + }(total) gotID := bar.ID() if gotID != wantID { t.Errorf("Expected bar id: %d, got %d\n", wantID, gotID) } - p.Abort(bar, true) + bar.Abort(true) p.Wait() } @@ -162,39 +163,87 @@ ) got := string(getLastLine(buf.Bytes())) - if got != wantBar { + if !strings.Contains(got, wantBar) { t.Errorf("Want bar: %q:%d, got bar: %q:%d\n", wantBar, utf8.RuneCountInString(wantBar), got, utf8.RuneCountInString(got)) } } -func TestBarPanics(t *testing.T) { +func TestBarPanicBeforeComplete(t *testing.T) { var buf bytes.Buffer p := New(WithDebugOutput(&buf), WithOutput(ioutil.Discard)) - wantPanic := "Upps!!!" - total := 100 - - bar := p.AddBar(int64(total), PrependDecorators(panicDecorator(wantPanic))) - - go func() { - for i := 0; i < 100; i++ { - time.Sleep(10 * time.Millisecond) - bar.Increment() - } - }() - - p.Wait() - - wantPanic = fmt.Sprintf("panic: %s", wantPanic) - debugStr := buf.String() - if !strings.Contains(debugStr, wantPanic) { - t.Errorf("%q doesn't contain %q\n", debugStr, wantPanic) - } -} - -func panicDecorator(panicMsg string) decor.Decorator { + total := 100 + panicMsg := "Upps!!!" + var pCount uint32 + bar := p.AddBar(int64(total), + PrependDecorators(panicDecorator(panicMsg, + func(st *decor.Statistics) bool { + if st.Current >= 42 { + atomic.AddUint32(&pCount, 1) + return true + } + return false + }, + )), + ) + + for i := 0; i < total; i++ { + time.Sleep(10 * time.Millisecond) + bar.Increment() + } + + p.Wait() + + if pCount != 1 { + t.Errorf("Decor called after panic %d times\n", pCount-1) + } + + barStr := buf.String() + if !strings.Contains(barStr, panicMsg) { + t.Errorf("%q doesn't contain %q\n", barStr, panicMsg) + } +} + +func TestBarPanicAfterComplete(t *testing.T) { + var buf bytes.Buffer + p := New(WithDebugOutput(&buf), WithOutput(ioutil.Discard)) + + total := 100 + panicMsg := "Upps!!!" + var pCount uint32 + bar := p.AddBar(int64(total), + PrependDecorators(panicDecorator(panicMsg, + func(st *decor.Statistics) bool { + if st.Completed { + atomic.AddUint32(&pCount, 1) + return true + } + return false + }, + )), + ) + + for i := 0; i < total; i++ { + time.Sleep(10 * time.Millisecond) + bar.Increment() + } + + p.Wait() + + if pCount != 1 { + t.Errorf("Decor called after panic %d times\n", pCount-1) + } + + barStr := buf.String() + if !strings.Contains(barStr, panicMsg) { + t.Errorf("%q doesn't contain %q\n", barStr, panicMsg) + } +} + +func panicDecorator(panicMsg string, cond func(*decor.Statistics) bool) decor.Decorator { d := &decorator{ panicMsg: panicMsg, + cond: cond, } d.Init() return d @@ -203,10 +252,11 @@ type decorator struct { decor.WC panicMsg string + cond func(*decor.Statistics) bool } func (d *decorator) Decor(st *decor.Statistics) string { - if st.Current >= 42 { + if d.cond(st) { panic(d.panicMsg) } return d.FormatMsg("") diff --git a/barbench_test.go b/barbench_test.go index d5d904c..53a8141 100644 --- a/barbench_test.go +++ b/barbench_test.go @@ -4,7 +4,7 @@ "io/ioutil" "testing" - "github.com/vbauerster/mpb/decor" + "github.com/vbauerster/mpb/v5/decor" ) func BenchmarkIncrSingleBar(b *testing.B) { diff --git a/cwriter/export_test.go b/cwriter/export_test.go deleted file mode 100644 index 1219508..0000000 --- a/cwriter/export_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package cwriter - -var ClearCursorAndLine = clearCursorAndLine diff --git a/cwriter/writer.go b/cwriter/writer.go index 638237c..9ec1ec6 100644 --- a/cwriter/writer.go +++ b/cwriter/writer.go @@ -7,59 +7,50 @@ "io" "os" - isatty "github.com/mattn/go-isatty" "golang.org/x/crypto/ssh/terminal" ) -// ESC is the ASCII code for escape character -const ESC = 27 - +// NotATTY not a TeleTYpewriter error. var NotATTY = errors.New("not a terminal") -var ( - cursorUp = fmt.Sprintf("%c[%dA", ESC, 1) - clearLine = fmt.Sprintf("%c[2K\r", ESC) - clearCursorAndLine = cursorUp + clearLine -) +var cuuAndEd = fmt.Sprintf("%c[%%dA%[1]c[J", 27) -// Writer is a buffered the writer that updates the terminal. The +// Writer is a buffered the writer that updates the terminal. The // contents of writer will be flushed when Flush is called. type Writer struct { out io.Writer buf bytes.Buffer + lineCount int + fd uintptr isTerminal bool - fd int - lineCount int } -// New returns a new Writer with defaults +// New returns a new Writer with defaults. func New(out io.Writer) *Writer { w := &Writer{out: out} if f, ok := out.(*os.File); ok { - fd := f.Fd() - w.isTerminal = isatty.IsTerminal(fd) - w.fd = int(fd) + w.fd = f.Fd() + w.isTerminal = terminal.IsTerminal(int(w.fd)) } return w } -// Flush flushes the underlying buffer -func (w *Writer) Flush(lineCount int) error { - err := w.clearLines() +// Flush flushes the underlying buffer. +func (w *Writer) Flush(lineCount int) (err error) { + if w.lineCount > 0 { + w.clearLines() + } w.lineCount = lineCount - // WriteTo takes care of w.buf.Reset - if _, e := w.buf.WriteTo(w.out); err == nil { - err = e - } - return err + _, err = w.buf.WriteTo(w.out) + return } -// Write appends the contents of p to the underlying buffer +// Write appends the contents of p to the underlying buffer. func (w *Writer) Write(p []byte) (n int, err error) { return w.buf.Write(p) } -// WriteString writes string to the underlying buffer +// WriteString writes string to the underlying buffer. func (w *Writer) WriteString(s string) (n int, err error) { return w.buf.WriteString(s) } @@ -73,7 +64,7 @@ // GetWidth returns width of underlying terminal. func (w *Writer) GetWidth() (int, error) { if w.isTerminal { - tw, _, err := terminal.GetSize(w.fd) + tw, _, err := terminal.GetSize(int(w.fd)) return tw, err } return -1, NotATTY diff --git a/cwriter/writer_posix.go b/cwriter/writer_posix.go index 05e31c4..3fb8b7d 100644 --- a/cwriter/writer_posix.go +++ b/cwriter/writer_posix.go @@ -2,12 +2,8 @@ package cwriter -import ( - "io" - "strings" -) +import "fmt" -func (w *Writer) clearLines() error { - _, err := io.WriteString(w.out, strings.Repeat(clearCursorAndLine, w.lineCount)) - return err +func (w *Writer) clearLines() { + fmt.Fprintf(w.out, cuuAndEd, w.lineCount) } diff --git a/cwriter/writer_posix_test.go b/cwriter/writer_posix_test.go deleted file mode 100644 index e2a48a4..0000000 --- a/cwriter/writer_posix_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// +build !windows - -package cwriter_test - -import ( - "bytes" - "testing" - - . "github.com/vbauerster/mpb/cwriter" -) - -// TestWriterPosix by writing and flushing many times. The output buffer -// must contain the clearCursor and clearLine sequences. -func TestWriterPosix(t *testing.T) { - out := new(bytes.Buffer) - w := New(out) - - for _, tcase := range []struct { - input, expectedOutput string - }{ - {input: "foo\n", expectedOutput: "foo\n"}, - {input: "bar\n", expectedOutput: "foo\n" + ClearCursorAndLine + "bar\n"}, - {input: "fizz\n", expectedOutput: "foo\n" + ClearCursorAndLine + "bar\n" + ClearCursorAndLine + "fizz\n"}, - } { - t.Run(tcase.input, func(t *testing.T) { - s := []byte(tcase.input) - lc := bytes.Count(s, []byte("\n")) - w.Write(s) - w.Flush(lc) - output := out.String() - if output != tcase.expectedOutput { - t.Fatalf("want %q, got %q", tcase.expectedOutput, output) - } - }) - } -} diff --git a/cwriter/writer_windows.go b/cwriter/writer_windows.go index 747a634..7125289 100644 --- a/cwriter/writer_windows.go +++ b/cwriter/writer_windows.go @@ -3,12 +3,9 @@ package cwriter import ( - "io" - "strings" + "fmt" "syscall" "unsafe" - - isatty "github.com/mattn/go-isatty" ) var kernel32 = syscall.NewLazyDLL("kernel32.dll") @@ -20,58 +17,44 @@ procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") ) -type ( - short int16 - word uint16 - dword uint32 - - coord struct { - x short - y short - } - smallRect struct { - left short - top short - right short - bottom short - } - consoleScreenBufferInfo struct { - size coord - cursorPosition coord - attributes word - window smallRect - maximumWindowSize coord - } -) - -// FdWriter is a writer with a file descriptor. -type FdWriter interface { - io.Writer - Fd() uintptr +type coord struct { + x int16 + y int16 } -func (w *Writer) clearLines() error { - f, ok := w.out.(FdWriter) - if ok && !isatty.IsTerminal(f.Fd()) { - _, err := io.WriteString(w.out, strings.Repeat(clearCursorAndLine, w.lineCount)) - return err +type smallRect struct { + left int16 + top int16 + right int16 + bottom int16 +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes uint16 + window smallRect + maximumWindowSize coord +} + +func (w *Writer) clearLines() { + if !w.isTerminal { + fmt.Fprintf(w.out, cuuAndEd, w.lineCount) } - fd := f.Fd() var info consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(fd, uintptr(unsafe.Pointer(&info))) + procGetConsoleScreenBufferInfo.Call(w.fd, uintptr(unsafe.Pointer(&info))) - for i := 0; i < w.lineCount; i++ { - // move the cursor up - info.cursorPosition.y-- - procSetConsoleCursorPosition.Call(fd, uintptr(*(*int32)(unsafe.Pointer(&info.cursorPosition)))) - // clear the line - cursor := coord{ - x: info.window.left, - y: info.window.top + info.cursorPosition.y, - } - var count, w dword - count = dword(info.size.x) - procFillConsoleOutputCharacter.Call(fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w))) + info.cursorPosition.y -= int16(w.lineCount) + if info.cursorPosition.y < 0 { + info.cursorPosition.y = 0 } - return nil + procSetConsoleCursorPosition.Call(w.fd, uintptr(uint32(uint16(info.cursorPosition.y))<<16|uint32(uint16(info.cursorPosition.x)))) + + // clear the lines + cursor := coord{ + x: info.window.left, + y: info.cursorPosition.y, + } + count := uint32(info.size.x) * uint32(w.lineCount) + procFillConsoleOutputCharacter.Call(w.fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(new(uint32)))) } diff --git a/decor/any.go b/decor/any.go new file mode 100644 index 0000000..bf9cf51 --- /dev/null +++ b/decor/any.go @@ -0,0 +1,21 @@ +package decor + +// Any decorator displays text, that can be changed during decorator's +// lifetime via provided func call back. +// +// `f` call back which provides string to display +// +// `wcc` optional WC config +// +func Any(f func(*Statistics) string, wcc ...WC) Decorator { + return &any{initWC(wcc...), f} +} + +type any struct { + WC + f func(*Statistics) string +} + +func (d *any) Decor(s *Statistics) string { + return d.FormatMsg(d.f(s)) +} diff --git a/decor/counters.go b/decor/counters.go index 7d581ee..297bf93 100644 --- a/decor/counters.go +++ b/decor/counters.go @@ -2,24 +2,6 @@ import ( "fmt" - "io" - "strconv" - "strings" -) - -const ( - _ = iota - KiB = 1 << (iota * 10) - MiB - GiB - TiB -) - -const ( - KB = 1000 - MB = KB * 1000 - GB = MB * 1000 - TB = GB * 1000 ) const ( @@ -28,181 +10,58 @@ UnitKB ) -type CounterKiB int64 - -func (c CounterKiB) Format(st fmt.State, verb rune) { - prec, ok := st.Precision() - - if verb == 'd' || !ok { - prec = 0 - } - if verb == 'f' && !ok { - prec = 6 - } - // retain old beahavior if s verb used - if verb == 's' { - prec = 1 - } - - var res, unit string - switch { - case c >= TiB: - unit = "TiB" - res = strconv.FormatFloat(float64(c)/TiB, 'f', prec, 64) - case c >= GiB: - unit = "GiB" - res = strconv.FormatFloat(float64(c)/GiB, 'f', prec, 64) - case c >= MiB: - unit = "MiB" - res = strconv.FormatFloat(float64(c)/MiB, 'f', prec, 64) - case c >= KiB: - unit = "KiB" - res = strconv.FormatFloat(float64(c)/KiB, 'f', prec, 64) - default: - unit = "b" - res = strconv.FormatInt(int64(c), 10) - } - - if st.Flag(' ') { - res += " " - } - res += unit - - if w, ok := st.Width(); ok { - if len(res) < w { - pad := strings.Repeat(" ", w-len(res)) - if st.Flag(int('-')) { - res += pad - } else { - res = pad + res - } - } - } - - io.WriteString(st, res) -} - -type CounterKB int64 - -func (c CounterKB) Format(st fmt.State, verb rune) { - prec, ok := st.Precision() - - if verb == 'd' || !ok { - prec = 0 - } - if verb == 'f' && !ok { - prec = 6 - } - // retain old beahavior if s verb used - if verb == 's' { - prec = 1 - } - - var res, unit string - switch { - case c >= TB: - unit = "TB" - res = strconv.FormatFloat(float64(c)/TB, 'f', prec, 64) - case c >= GB: - unit = "GB" - res = strconv.FormatFloat(float64(c)/GB, 'f', prec, 64) - case c >= MB: - unit = "MB" - res = strconv.FormatFloat(float64(c)/MB, 'f', prec, 64) - case c >= KB: - unit = "kB" - res = strconv.FormatFloat(float64(c)/KB, 'f', prec, 64) - default: - unit = "b" - res = strconv.FormatInt(int64(c), 10) - } - - if st.Flag(' ') { - res += " " - } - res += unit - - if w, ok := st.Width(); ok { - if len(res) < w { - pad := strings.Repeat(" ", w-len(res)) - if st.Flag(int('-')) { - res += pad - } else { - res = pad + res - } - } - } - - io.WriteString(st, res) -} - // CountersNoUnit is a wrapper around Counters with no unit param. -func CountersNoUnit(pairFormat string, wcc ...WC) Decorator { - return Counters(0, pairFormat, wcc...) +func CountersNoUnit(pairFmt string, wcc ...WC) Decorator { + return Counters(0, pairFmt, wcc...) } // CountersKibiByte is a wrapper around Counters with predefined unit // UnitKiB (bytes/1024). -func CountersKibiByte(pairFormat string, wcc ...WC) Decorator { - return Counters(UnitKiB, pairFormat, wcc...) +func CountersKibiByte(pairFmt string, wcc ...WC) Decorator { + return Counters(UnitKiB, pairFmt, wcc...) } // CountersKiloByte is a wrapper around Counters with predefined unit // UnitKB (bytes/1000). -func CountersKiloByte(pairFormat string, wcc ...WC) Decorator { - return Counters(UnitKB, pairFormat, wcc...) +func CountersKiloByte(pairFmt string, wcc ...WC) Decorator { + return Counters(UnitKB, pairFmt, wcc...) } // Counters decorator with dynamic unit measure adjustment. // // `unit` one of [0|UnitKiB|UnitKB] zero for no unit // -// `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" +// `pairFmt` printf compatible verbs for current and total, like "%f" or "%d" // // `wcc` optional WC config // -// pairFormat example if UnitKB is chosen: +// pairFmt example if unit=UnitKB: // -// "%.1f / %.1f" = "1.0MB / 12.0MB" or "% .1f / % .1f" = "1.0 MB / 12.0 MB" -func Counters(unit int, pairFormat string, wcc ...WC) Decorator { - var wc WC - for _, widthConf := range wcc { - wc = widthConf - } - wc.Init() - d := &countersDecorator{ - WC: wc, - unit: unit, - pairFormat: pairFormat, - } - return d +// pairFmt="%.1f / %.1f" output: "1.0MB / 12.0MB" +// pairFmt="% .1f / % .1f" output: "1.0 MB / 12.0 MB" +// pairFmt="%d / %d" output: "1MB / 12MB" +// pairFmt="% d / % d" output: "1 MB / 12 MB" +// +func Counters(unit int, pairFmt string, wcc ...WC) Decorator { + return Any(chooseSizeProducer(unit, pairFmt), wcc...) } -type countersDecorator struct { - WC - unit int - pairFormat string - completeMsg *string +func chooseSizeProducer(unit int, format string) func(*Statistics) string { + if format == "" { + format = "%d / %d" + } + switch unit { + case UnitKiB: + return func(s *Statistics) string { + return fmt.Sprintf(format, SizeB1024(s.Current), SizeB1024(s.Total)) + } + case UnitKB: + return func(s *Statistics) string { + return fmt.Sprintf(format, SizeB1000(s.Current), SizeB1000(s.Total)) + } + default: + return func(s *Statistics) string { + return fmt.Sprintf(format, s.Current, s.Total) + } + } } - -func (d *countersDecorator) Decor(st *Statistics) string { - if st.Completed && d.completeMsg != nil { - return d.FormatMsg(*d.completeMsg) - } - - var str string - switch d.unit { - case UnitKiB: - str = fmt.Sprintf(d.pairFormat, CounterKiB(st.Current), CounterKiB(st.Total)) - case UnitKB: - str = fmt.Sprintf(d.pairFormat, CounterKB(st.Current), CounterKB(st.Total)) - default: - str = fmt.Sprintf(d.pairFormat, st.Current, st.Total) - } - - return d.FormatMsg(str) -} - -func (d *countersDecorator) OnCompleteMessage(msg string) { - d.completeMsg = &msg -} diff --git a/decor/counters_test.go b/decor/counters_test.go deleted file mode 100644 index 9988d50..0000000 --- a/decor/counters_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package decor - -import ( - "fmt" - "testing" -) - -func TestCounterKiB(t *testing.T) { - cases := map[string]struct { - value int64 - verb string - expected string - }{ - "verb %f": {12345678, "%f", "11.773756MiB"}, - "verb %.0f": {12345678, "%.0f", "12MiB"}, - "verb %.1f": {12345678, "%.1f", "11.8MiB"}, - "verb %.2f": {12345678, "%.2f", "11.77MiB"}, - "verb %.3f": {12345678, "%.3f", "11.774MiB"}, - - "verb % f": {12345678, "% f", "11.773756 MiB"}, - "verb % .0f": {12345678, "% .0f", "12 MiB"}, - "verb % .1f": {12345678, "% .1f", "11.8 MiB"}, - "verb % .2f": {12345678, "% .2f", "11.77 MiB"}, - "verb % .3f": {12345678, "% .3f", "11.774 MiB"}, - - "verb %8.f": {12345678, "%8.f", " 12MiB"}, - "verb %8.0f": {12345678, "%8.0f", " 12MiB"}, - "verb %8.1f": {12345678, "%8.1f", " 11.8MiB"}, - "verb %8.2f": {12345678, "%8.2f", "11.77MiB"}, - "verb %8.3f": {12345678, "%8.3f", "11.774MiB"}, - - "verb % 8.f": {12345678, "% 8.f", " 12 MiB"}, - "verb % 8.0f": {12345678, "% 8.0f", " 12 MiB"}, - "verb % 8.1f": {12345678, "% 8.1f", "11.8 MiB"}, - - "verb %-8.f": {12345678, "%-8.f", "12MiB "}, - "verb %-8.0f": {12345678, "%-8.0f", "12MiB "}, - "verb %-8.1f": {12345678, "%-8.1f", "11.8MiB "}, - "verb %-8.2f": {12345678, "%8.2f", "11.77MiB"}, - "verb %-8.3f": {12345678, "%8.3f", "11.774MiB"}, - - "verb % -8.f": {12345678, "% -8.f", "12 MiB "}, - "verb % -8.0f": {12345678, "% -8.0f", "12 MiB "}, - "verb % -8.1f": {12345678, "% -8.1f", "11.8 MiB"}, - - "1000 %f": {1000, "%f", "1000b"}, - "1000 %d": {1000, "%d", "1000b"}, - "1000 %s": {1000, "%s", "1000b"}, - "1024 %f": {1024, "%f", "1.000000KiB"}, - "1024 %d": {1024, "%d", "1KiB"}, - "1024 %.1f": {1024, "%.1f", "1.0KiB"}, - "1024 %s": {1024, "%s", "1.0KiB"}, - "3*MiB+140KiB %f": {3*MiB + 140*KiB, "%f", "3.136719MiB"}, - "3*MiB+140KiB %d": {3*MiB + 140*KiB, "%d", "3MiB"}, - "3*MiB+140KiB %.1f": {3*MiB + 140*KiB, "%.1f", "3.1MiB"}, - "3*MiB+140KiB %s": {3*MiB + 140*KiB, "%s", "3.1MiB"}, - "2*GiB %f": {2 * GiB, "%f", "2.000000GiB"}, - "2*GiB %d": {2 * GiB, "%d", "2GiB"}, - "2*GiB %.1f": {2 * GiB, "%.1f", "2.0GiB"}, - "2*GiB %s": {2 * GiB, "%s", "2.0GiB"}, - "4*TiB %f": {4 * TiB, "%f", "4.000000TiB"}, - "4*TiB %d": {4 * TiB, "%d", "4TiB"}, - "4*TiB %.1f": {4 * TiB, "%.1f", "4.0TiB"}, - "4*TiB %s": {4 * TiB, "%s", "4.0TiB"}, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - got := fmt.Sprintf(tc.verb, CounterKiB(tc.value)) - if got != tc.expected { - t.Fatalf("expected: %q, got: %q\n", tc.expected, got) - } - }) - } -} - -func TestCounterKB(t *testing.T) { - cases := map[string]struct { - value int64 - verb string - expected string - }{ - "verb %f": {12345678, "%f", "12.345678MB"}, - "verb %.0f": {12345678, "%.0f", "12MB"}, - "verb %.1f": {12345678, "%.1f", "12.3MB"}, - "verb %.2f": {12345678, "%.2f", "12.35MB"}, - "verb %.3f": {12345678, "%.3f", "12.346MB"}, - - "verb % f": {12345678, "% f", "12.345678 MB"}, - "verb % .0f": {12345678, "% .0f", "12 MB"}, - "verb % .1f": {12345678, "% .1f", "12.3 MB"}, - "verb % .2f": {12345678, "% .2f", "12.35 MB"}, - "verb % .3f": {12345678, "% .3f", "12.346 MB"}, - - "verb %8.f": {12345678, "%8.f", " 12MB"}, - "verb %8.0f": {12345678, "%8.0f", " 12MB"}, - "verb %8.1f": {12345678, "%8.1f", " 12.3MB"}, - "verb %8.2f": {12345678, "%8.2f", " 12.35MB"}, - "verb %8.3f": {12345678, "%8.3f", "12.346MB"}, - - "verb % 8.f": {12345678, "% 8.f", " 12 MB"}, - "verb % 8.0f": {12345678, "% 8.0f", " 12 MB"}, - "verb % 8.1f": {12345678, "% 8.1f", " 12.3 MB"}, - - "verb %-8.f": {12345678, "%-8.f", "12MB "}, - "verb %-8.0f": {12345678, "%-8.0f", "12MB "}, - "verb %-8.1f": {12345678, "%-8.1f", "12.3MB "}, - "verb %-8.2f": {12345678, "%8.2f", " 12.35MB"}, - "verb %-8.3f": {12345678, "%8.3f", "12.346MB"}, - - "verb % -8.f": {12345678, "% -8.f", "12 MB "}, - "verb % -8.0f": {12345678, "% -8.0f", "12 MB "}, - "verb % -8.1f": {12345678, "% -8.1f", "12.3 MB "}, - - "1000 %f": {1000, "%f", "1.000000kB"}, - "1000 %d": {1000, "%d", "1kB"}, - "1000 %s": {1000, "%s", "1.0kB"}, - "1024 %f": {1024, "%f", "1.024000kB"}, - "1024 %d": {1024, "%d", "1kB"}, - "1024 %.1f": {1024, "%.1f", "1.0kB"}, - "1024 %s": {1024, "%s", "1.0kB"}, - "3*MB+140*KB %f": {3*MB + 140*KB, "%f", "3.140000MB"}, - "3*MB+140*KB %d": {3*MB + 140*KB, "%d", "3MB"}, - "3*MB+140*KB %.1f": {3*MB + 140*KB, "%.1f", "3.1MB"}, - "3*MB+140*KB %s": {3*MB + 140*KB, "%s", "3.1MB"}, - "2*GB %f": {2 * GB, "%f", "2.000000GB"}, - "2*GB %d": {2 * GB, "%d", "2GB"}, - "2*GB %.1f": {2 * GB, "%.1f", "2.0GB"}, - "2*GB %s": {2 * GB, "%s", "2.0GB"}, - "4*TB %f": {4 * TB, "%f", "4.000000TB"}, - "4*TB %d": {4 * TB, "%d", "4TB"}, - "4*TB %.1f": {4 * TB, "%.1f", "4.0TB"}, - "4*TB %s": {4 * TB, "%s", "4.0TB"}, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - got := fmt.Sprintf(tc.verb, CounterKB(tc.value)) - if got != tc.expected { - t.Fatalf("expected: %q, got: %q\n", tc.expected, got) - } - }) - } -} diff --git a/decor/decorator.go b/decor/decorator.go index 2fe40ae..5bca63d 100644 --- a/decor/decorator.go +++ b/decor/decorator.go @@ -4,6 +4,8 @@ "fmt" "time" "unicode/utf8" + + "github.com/acarl005/stripansi" ) const ( @@ -42,7 +44,8 @@ ET_STYLE_MMSS ) -// Statistics is a struct, which gets passed to a Decorator. +// Statistics consists of progress related statistics, that Decorator +// may need. type Statistics struct { ID int Completed bool @@ -51,32 +54,47 @@ } // Decorator interface. -// A decorator must implement this interface, in order to be used with -// mpb library. +// Implementors should embed WC type, that way only single method +// Decor(*Statistics) needs to be implemented, the rest will be handled +// by WC type. type Decorator interface { + Configurator + Synchronizer Decor(*Statistics) string - Syncable } -// Syncable interface. -// All decorators implement this interface implicitly. Its Syncable -// method exposes width sync channel, if sync is enabled. -type Syncable interface { - Syncable() (bool, chan int) +// Synchronizer interface. +// All decorators implement this interface implicitly. Its Sync +// method exposes width sync channel, if DSyncWidth bit is set. +type Synchronizer interface { + Sync() (chan int, bool) } -// OnCompleteMessenger interface. -// Decorators implementing this interface suppose to return provided -// string on complete event. -type OnCompleteMessenger interface { - OnCompleteMessage(string) +// Configurator interface. +type Configurator interface { + GetConf() WC + SetConf(WC) } -// AmountReceiver interface. -// If decorator needs to receive increment amount, so this is the right -// interface to implement. -type AmountReceiver interface { - NextAmount(int, ...time.Duration) +// Wrapper interface. +// If you're implementing custom Decorator by wrapping a built-in one, +// it is necessary to implement this interface to retain functionality +// of built-in Decorator. +type Wrapper interface { + Base() Decorator +} + +// EwmaDecorator interface. +// EWMA based decorators should implement this one. +type EwmaDecorator interface { + EwmaUpdate(int64, time.Duration) +} + +// AverageDecorator interface. +// Average decorators should implement this interface to provide start +// time adjustment facility, for resume-able tasks. +type AverageDecorator interface { + AverageAdjust(time.Time) } // ShutdownListener interface. @@ -86,7 +104,8 @@ Shutdown() } -// Global convenience shortcuts +// Global convenience instances of WC with sync width bit set. +// To be used with multiple bars only, i.e. not effective for single bar usage. var ( WCSyncWidth = WC{C: DSyncWidth} WCSyncWidthR = WC{C: DSyncWidthR} @@ -96,57 +115,70 @@ // WC is a struct with two public fields W and C, both of int type. // W represents width and C represents bit set of width related config. -// A decorator should embed WC, in order to become Syncable. +// A decorator should embed WC, to enable width synchronization. type WC struct { - W int - C int - format string - wsync chan int + W int + C int + dynFormat string + wsync chan int } // FormatMsg formats final message according to WC.W and WC.C. // Should be called by any Decorator implementation. -func (wc WC) FormatMsg(msg string) string { +func (wc *WC) FormatMsg(msg string) string { + var format string + runeCount := utf8.RuneCountInString(stripansi.Strip(msg)) + ansiCount := utf8.RuneCountInString(msg) - runeCount if (wc.C & DSyncWidth) != 0 { - wc.wsync <- utf8.RuneCountInString(msg) + if (wc.C & DextraSpace) != 0 { + runeCount++ + } + wc.wsync <- runeCount max := <-wc.wsync - if max == 0 { - max = wc.W - } - if (wc.C & DextraSpace) != 0 { - max++ - } - return fmt.Sprintf(fmt.Sprintf(wc.format, max), msg) + format = fmt.Sprintf(wc.dynFormat, ansiCount+max) + } else { + format = fmt.Sprintf(wc.dynFormat, ansiCount+wc.W) } - return fmt.Sprintf(fmt.Sprintf(wc.format, wc.W), msg) + return fmt.Sprintf(format, msg) } // Init initializes width related config. -func (wc *WC) Init() { - wc.format = "%%" +func (wc *WC) Init() WC { + wc.dynFormat = "%%" if (wc.C & DidentRight) != 0 { - wc.format += "-" + wc.dynFormat += "-" } - wc.format += "%ds" + wc.dynFormat += "%ds" if (wc.C & DSyncWidth) != 0 { + // it's deliberate choice to override wsync on each Init() call, + // this way globals like WCSyncSpace can be reused wc.wsync = make(chan int) } + return *wc } -// Syncable is implementation of Syncable interface. -func (wc *WC) Syncable() (bool, chan int) { - return (wc.C & DSyncWidth) != 0, wc.wsync +// Sync is implementation of Synchronizer interface. +func (wc *WC) Sync() (chan int, bool) { + if (wc.C&DSyncWidth) != 0 && wc.wsync == nil { + panic(fmt.Sprintf("%T is not initialized", wc)) + } + return wc.wsync, (wc.C & DSyncWidth) != 0 } -// OnComplete returns decorator, which wraps provided decorator, with -// sole purpose to display provided message on complete event. -// -// `decorator` Decorator to wrap -// -// `message` message to display on complete event -func OnComplete(decorator Decorator, message string) Decorator { - if d, ok := decorator.(OnCompleteMessenger); ok { - d.OnCompleteMessage(message) +// GetConf is implementation of Configurator interface. +func (wc *WC) GetConf() WC { + return *wc +} + +// SetConf is implementation of Configurator interface. +func (wc *WC) SetConf(conf WC) { + *wc = conf.Init() +} + +func initWC(wcc ...WC) WC { + var wc WC + for _, nwc := range wcc { + wc = nwc } - return decorator + return wc.Init() } diff --git a/decor/doc.go b/decor/doc.go index 561a867..6d26144 100644 --- a/decor/doc.go +++ b/decor/doc.go @@ -1,9 +1,5 @@ -// Copyright (C) 2016-2018 Vladimir Bauer -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - /* - Package decor contains common decorators used by "github.com/vbauerster/mpb" package. + Package decor provides common decorators for "github.com/vbauerster/mpb/v5" module. Some decorators returned by this package might have a closure state. It is ok to use decorators concurrently, unless you share the same decorator among multiple diff --git a/decor/elapsed.go b/decor/elapsed.go index b2e7585..c9999a3 100644 --- a/decor/elapsed.go +++ b/decor/elapsed.go @@ -1,68 +1,35 @@ package decor import ( - "fmt" "time" ) -// Elapsed returns elapsed time decorator. +// Elapsed decorator. It's wrapper of NewElapsed. // // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] // // `wcc` optional WC config +// func Elapsed(style TimeStyle, wcc ...WC) Decorator { - var wc WC - for _, widthConf := range wcc { - wc = widthConf - } - wc.Init() - d := &elapsedDecorator{ - WC: wc, - style: style, - startTime: time.Now(), - } - return d + return NewElapsed(style, time.Now(), wcc...) } -type elapsedDecorator struct { - WC - style TimeStyle - startTime time.Time - msg string - completeMsg *string +// NewElapsed returns elapsed time decorator. +// +// `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] +// +// `startTime` start time +// +// `wcc` optional WC config +// +func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator { + var msg string + producer := chooseTimeProducer(style) + f := func(s *Statistics) string { + if !s.Completed { + msg = producer(time.Since(startTime)) + } + return msg + } + return Any(f, wcc...) } - -func (d *elapsedDecorator) Decor(st *Statistics) string { - if st.Completed { - if d.completeMsg != nil { - return d.FormatMsg(*d.completeMsg) - } - return d.FormatMsg(d.msg) - } - - timeElapsed := time.Since(d.startTime) - hours := int64((timeElapsed / time.Hour) % 60) - minutes := int64((timeElapsed / time.Minute) % 60) - seconds := int64((timeElapsed / time.Second) % 60) - - switch d.style { - case ET_STYLE_GO: - d.msg = fmt.Sprint(time.Duration(timeElapsed.Seconds()) * time.Second) - case ET_STYLE_HHMMSS: - d.msg = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - case ET_STYLE_HHMM: - d.msg = fmt.Sprintf("%02d:%02d", hours, minutes) - case ET_STYLE_MMSS: - if hours > 0 { - d.msg = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - } else { - d.msg = fmt.Sprintf("%02d:%02d", minutes, seconds) - } - } - - return d.FormatMsg(d.msg) -} - -func (d *elapsedDecorator) OnCompleteMessage(msg string) { - d.completeMsg = &msg -} diff --git a/decor/eta.go b/decor/eta.go index e8dc979..6cb27a2 100644 --- a/decor/eta.go +++ b/decor/eta.go @@ -8,166 +8,138 @@ "github.com/VividCortex/ewma" ) -type TimeNormalizer func(time.Duration) time.Duration +// TimeNormalizer interface. Implementors could be passed into +// MovingAverageETA, in order to affect i.e. normalize its output. +type TimeNormalizer interface { + Normalize(time.Duration) time.Duration +} + +// TimeNormalizerFunc is function type adapter to convert function +// into TimeNormalizer. +type TimeNormalizerFunc func(time.Duration) time.Duration + +func (f TimeNormalizerFunc) Normalize(src time.Duration) time.Duration { + return f(src) +} // EwmaETA exponential-weighted-moving-average based ETA decorator. +// For this decorator to work correctly you have to measure each +// iteration's duration and pass it to the +// *Bar.DecoratorEwmaUpdate(time.Duration) method after each increment. +func EwmaETA(style TimeStyle, age float64, wcc ...WC) Decorator { + var average ewma.MovingAverage + if age == 0 { + average = ewma.NewMovingAverage() + } else { + average = ewma.NewMovingAverage(age) + } + return MovingAverageETA(style, NewThreadSafeMovingAverage(average), nil, wcc...) +} + +// MovingAverageETA decorator relies on MovingAverage implementation to calculate its average. // // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] // -// `age` is the previous N samples to average over. +// `average` implementation of MovingAverage interface +// +// `normalizer` available implementations are [FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer] // // `wcc` optional WC config -func EwmaETA(style TimeStyle, age float64, wcc ...WC) Decorator { - return MovingAverageETA(style, ewma.NewMovingAverage(age), NopNormalizer(), wcc...) -} - -// MovingAverageETA decorator relies on MovingAverage implementation to calculate its average. -// -// `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] -// -// `average` available implementations of MovingAverage [ewma.MovingAverage|NewMedian|NewMedianEwma] -// -// `normalizer` available implementations are [NopNormalizer|FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer] -// -// `wcc` optional WC config -func MovingAverageETA(style TimeStyle, average MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator { - var wc WC - for _, widthConf := range wcc { - wc = widthConf - } - wc.Init() +// +func MovingAverageETA(style TimeStyle, average ewma.MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator { d := &movingAverageETA{ - WC: wc, - style: style, + WC: initWC(wcc...), average: average, normalizer: normalizer, + producer: chooseTimeProducer(style), } return d } type movingAverageETA struct { WC - style TimeStyle - average ewma.MovingAverage - completeMsg *string - normalizer TimeNormalizer -} - -func (d *movingAverageETA) Decor(st *Statistics) string { - if st.Completed && d.completeMsg != nil { - return d.FormatMsg(*d.completeMsg) - } - + average ewma.MovingAverage + normalizer TimeNormalizer + producer func(time.Duration) string +} + +func (d *movingAverageETA) Decor(s *Statistics) string { v := math.Round(d.average.Value()) - remaining := d.normalizer(time.Duration((st.Total - st.Current) * int64(v))) - hours := int64((remaining / time.Hour) % 60) - minutes := int64((remaining / time.Minute) % 60) - seconds := int64((remaining / time.Second) % 60) - - var str string - switch d.style { - case ET_STYLE_GO: - str = fmt.Sprint(time.Duration(remaining.Seconds()) * time.Second) - case ET_STYLE_HHMMSS: - str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - case ET_STYLE_HHMM: - str = fmt.Sprintf("%02d:%02d", hours, minutes) - case ET_STYLE_MMSS: - if hours > 0 { - str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - } else { - str = fmt.Sprintf("%02d:%02d", minutes, seconds) - } - } - - return d.FormatMsg(str) -} - -func (d *movingAverageETA) NextAmount(n int, wdd ...time.Duration) { - var workDuration time.Duration - for _, wd := range wdd { - workDuration = wd - } - lastItemEstimate := float64(workDuration) / float64(n) - if math.IsInf(lastItemEstimate, 0) || math.IsNaN(lastItemEstimate) { + remaining := time.Duration((s.Total - s.Current) * int64(v)) + if d.normalizer != nil { + remaining = d.normalizer.Normalize(remaining) + } + return d.FormatMsg(d.producer(remaining)) +} + +func (d *movingAverageETA) EwmaUpdate(n int64, dur time.Duration) { + durPerItem := float64(dur) / float64(n) + if math.IsInf(durPerItem, 0) || math.IsNaN(durPerItem) { return } - d.average.Add(lastItemEstimate) -} - -func (d *movingAverageETA) OnCompleteMessage(msg string) { - d.completeMsg = &msg -} - -// AverageETA decorator. + d.average.Add(durPerItem) +} + +// AverageETA decorator. It's wrapper of NewAverageETA. // // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] // // `wcc` optional WC config +// func AverageETA(style TimeStyle, wcc ...WC) Decorator { - var wc WC - for _, widthConf := range wcc { - wc = widthConf - } - wc.Init() + return NewAverageETA(style, time.Now(), nil, wcc...) +} + +// NewAverageETA decorator with user provided start time. +// +// `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] +// +// `startTime` start time +// +// `normalizer` available implementations are [FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer] +// +// `wcc` optional WC config +// +func NewAverageETA(style TimeStyle, startTime time.Time, normalizer TimeNormalizer, wcc ...WC) Decorator { d := &averageETA{ - WC: wc, - style: style, - startTime: time.Now(), + WC: initWC(wcc...), + startTime: startTime, + normalizer: normalizer, + producer: chooseTimeProducer(style), } return d } type averageETA struct { WC - style TimeStyle - startTime time.Time - completeMsg *string -} - -func (d *averageETA) Decor(st *Statistics) string { - if st.Completed && d.completeMsg != nil { - return d.FormatMsg(*d.completeMsg) - } - - var str string - timeElapsed := time.Since(d.startTime) - v := math.Round(float64(timeElapsed) / float64(st.Current)) - if math.IsInf(v, 0) || math.IsNaN(v) { - v = 0 - } - remaining := time.Duration((st.Total - st.Current) * int64(v)) - hours := int64((remaining / time.Hour) % 60) - minutes := int64((remaining / time.Minute) % 60) - seconds := int64((remaining / time.Second) % 60) - - switch d.style { - case ET_STYLE_GO: - str = fmt.Sprint(time.Duration(remaining.Seconds()) * time.Second) - case ET_STYLE_HHMMSS: - str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - case ET_STYLE_HHMM: - str = fmt.Sprintf("%02d:%02d", hours, minutes) - case ET_STYLE_MMSS: - if hours > 0 { - str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - } else { - str = fmt.Sprintf("%02d:%02d", minutes, seconds) - } - } - - return d.FormatMsg(str) -} - -func (d *averageETA) OnCompleteMessage(msg string) { - d.completeMsg = &msg -} - + startTime time.Time + normalizer TimeNormalizer + producer func(time.Duration) string +} + +func (d *averageETA) Decor(s *Statistics) string { + var remaining time.Duration + if s.Current != 0 { + durPerItem := float64(time.Since(d.startTime)) / float64(s.Current) + durPerItem = math.Round(durPerItem) + remaining = time.Duration((s.Total - s.Current) * int64(durPerItem)) + if d.normalizer != nil { + remaining = d.normalizer.Normalize(remaining) + } + } + return d.FormatMsg(d.producer(remaining)) +} + +func (d *averageETA) AverageAdjust(startTime time.Time) { + d.startTime = startTime +} + +// MaxTolerateTimeNormalizer returns implementation of TimeNormalizer. func MaxTolerateTimeNormalizer(maxTolerate time.Duration) TimeNormalizer { var normalized time.Duration var lastCall time.Time - return func(remaining time.Duration) time.Duration { - if diff := normalized - remaining; diff <= 0 || diff > maxTolerate || remaining < maxTolerate/2 { + return TimeNormalizerFunc(func(remaining time.Duration) time.Duration { + if diff := normalized - remaining; diff <= 0 || diff > maxTolerate || remaining < time.Minute { normalized = remaining lastCall = time.Now() return remaining @@ -175,15 +147,16 @@ normalized -= time.Since(lastCall) lastCall = time.Now() return normalized - } -} - + }) +} + +// FixedIntervalTimeNormalizer returns implementation of TimeNormalizer. func FixedIntervalTimeNormalizer(updInterval int) TimeNormalizer { var normalized time.Duration var lastCall time.Time var count int - return func(remaining time.Duration) time.Duration { - if count == 0 || remaining <= time.Duration(15*time.Second) { + return TimeNormalizerFunc(func(remaining time.Duration) time.Duration { + if count == 0 || remaining < time.Minute { count = updInterval normalized = remaining lastCall = time.Now() @@ -192,15 +165,39 @@ count-- normalized -= time.Since(lastCall) lastCall = time.Now() - if normalized > 0 { - return normalized - } - return remaining - } -} - -func NopNormalizer() TimeNormalizer { - return func(remaining time.Duration) time.Duration { - return remaining - } -} + return normalized + }) +} + +func chooseTimeProducer(style TimeStyle) func(time.Duration) string { + switch style { + case ET_STYLE_HHMMSS: + return func(remaining time.Duration) string { + hours := int64(remaining/time.Hour) % 60 + minutes := int64(remaining/time.Minute) % 60 + seconds := int64(remaining/time.Second) % 60 + return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) + } + case ET_STYLE_HHMM: + return func(remaining time.Duration) string { + hours := int64(remaining/time.Hour) % 60 + minutes := int64(remaining/time.Minute) % 60 + return fmt.Sprintf("%02d:%02d", hours, minutes) + } + case ET_STYLE_MMSS: + return func(remaining time.Duration) string { + hours := int64(remaining/time.Hour) % 60 + minutes := int64(remaining/time.Minute) % 60 + seconds := int64(remaining/time.Second) % 60 + if hours > 0 { + return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) + } + return fmt.Sprintf("%02d:%02d", minutes, seconds) + } + default: + return func(remaining time.Duration) string { + // strip off nanoseconds + return ((remaining / time.Second) * time.Second).String() + } + } +} diff --git a/decor/merge.go b/decor/merge.go new file mode 100644 index 0000000..520f13a --- /dev/null +++ b/decor/merge.go @@ -0,0 +1,106 @@ +package decor + +import ( + "fmt" + "strings" + "unicode/utf8" +) + +// Merge wraps its decorator argument with intention to sync width +// with several decorators of another bar. Visual example: +// +// +----+--------+---------+--------+ +// | B1 | MERGE(D, P1, Pn) | +// +----+--------+---------+--------+ +// | B2 | D0 | D1 | Dn | +// +----+--------+---------+--------+ +// +func Merge(decorator Decorator, placeholders ...WC) Decorator { + if _, ok := decorator.Sync(); !ok || len(placeholders) == 0 { + return decorator + } + md := &mergeDecorator{ + Decorator: decorator, + wc: decorator.GetConf(), + placeHolders: make([]*placeHolderDecorator, len(placeholders)), + } + decorator.SetConf(WC{}) + for i, wc := range placeholders { + if (wc.C & DSyncWidth) == 0 { + return decorator + } + md.placeHolders[i] = &placeHolderDecorator{wc.Init()} + } + return md +} + +type mergeDecorator struct { + Decorator + wc WC + placeHolders []*placeHolderDecorator +} + +func (d *mergeDecorator) GetConf() WC { + return d.wc +} + +func (d *mergeDecorator) SetConf(conf WC) { + d.wc = conf.Init() +} + +func (d *mergeDecorator) MergeUnwrap() []Decorator { + decorators := make([]Decorator, len(d.placeHolders)) + for i, ph := range d.placeHolders { + decorators[i] = ph + } + return decorators +} + +func (d *mergeDecorator) Sync() (chan int, bool) { + return d.wc.Sync() +} + +func (d *mergeDecorator) Base() Decorator { + return d.Decorator +} + +func (d *mergeDecorator) Decor(s *Statistics) string { + msg := d.Decorator.Decor(s) + msgLen := utf8.RuneCountInString(msg) + if (d.wc.C & DextraSpace) != 0 { + msgLen++ + } + + var total int + max := utf8.RuneCountInString(d.placeHolders[0].FormatMsg("")) + total += max + pw := (msgLen - max) / len(d.placeHolders) + rem := (msgLen - max) % len(d.placeHolders) + + var diff int + for i := 1; i < len(d.placeHolders); i++ { + ph := d.placeHolders[i] + width := pw - diff + if (ph.WC.C & DextraSpace) != 0 { + width-- + if width < 0 { + width = 0 + } + } + max = utf8.RuneCountInString(ph.FormatMsg(strings.Repeat(" ", width))) + total += max + diff = max - pw + } + + d.wc.wsync <- pw + rem + max = <-d.wc.wsync + return fmt.Sprintf(fmt.Sprintf(d.wc.dynFormat, max+total), msg) +} + +type placeHolderDecorator struct { + WC +} + +func (d *placeHolderDecorator) Decor(*Statistics) string { + return "" +} diff --git a/decor/moving-average.go b/decor/moving-average.go deleted file mode 100644 index fcd2689..0000000 --- a/decor/moving-average.go +++ /dev/null @@ -1,67 +0,0 @@ -package decor - -import ( - "sort" - - "github.com/VividCortex/ewma" -) - -// MovingAverage is the interface that computes a moving average over -// a time-series stream of numbers. The average may be over a window -// or exponentially decaying. -type MovingAverage interface { - Add(float64) - Value() float64 - Set(float64) -} - -type medianWindow [3]float64 - -func (s *medianWindow) Len() int { return len(s) } -func (s *medianWindow) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s *medianWindow) Less(i, j int) bool { return s[i] < s[j] } - -func (s *medianWindow) Add(value float64) { - s[0], s[1] = s[1], s[2] - s[2] = value -} - -func (s *medianWindow) Value() float64 { - tmp := *s - sort.Sort(&tmp) - return tmp[1] -} - -func (s *medianWindow) Set(value float64) { - for i := 0; i < len(s); i++ { - s[i] = value - } -} - -// NewMedian is fixed last 3 samples median MovingAverage. -func NewMedian() MovingAverage { - return new(medianWindow) -} - -type medianEwma struct { - count uint - median MovingAverage - MovingAverage -} - -func (s *medianEwma) Add(v float64) { - s.median.Add(v) - if s.count >= 2 { - s.MovingAverage.Add(s.median.Value()) - } - s.count++ -} - -// NewMedianEwma is ewma based MovingAverage, which gets its values -// from median MovingAverage. -func NewMedianEwma(age ...float64) MovingAverage { - return &medianEwma{ - MovingAverage: ewma.NewMovingAverage(age...), - median: NewMedian(), - } -} diff --git a/decor/moving_average.go b/decor/moving_average.go new file mode 100644 index 0000000..50ac9c3 --- /dev/null +++ b/decor/moving_average.go @@ -0,0 +1,68 @@ +package decor + +import ( + "sort" + "sync" + + "github.com/VividCortex/ewma" +) + +type threadSafeMovingAverage struct { + ewma.MovingAverage + mu sync.Mutex +} + +func (s *threadSafeMovingAverage) Add(value float64) { + s.mu.Lock() + s.MovingAverage.Add(value) + s.mu.Unlock() +} + +func (s *threadSafeMovingAverage) Value() float64 { + s.mu.Lock() + defer s.mu.Unlock() + return s.MovingAverage.Value() +} + +func (s *threadSafeMovingAverage) Set(value float64) { + s.mu.Lock() + s.MovingAverage.Set(value) + s.mu.Unlock() +} + +// NewThreadSafeMovingAverage converts provided ewma.MovingAverage +// into thread safe ewma.MovingAverage. +func NewThreadSafeMovingAverage(average ewma.MovingAverage) ewma.MovingAverage { + if tsma, ok := average.(*threadSafeMovingAverage); ok { + return tsma + } + return &threadSafeMovingAverage{MovingAverage: average} +} + +type medianWindow [3]float64 + +func (s *medianWindow) Len() int { return len(s) } +func (s *medianWindow) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s *medianWindow) Less(i, j int) bool { return s[i] < s[j] } + +func (s *medianWindow) Add(value float64) { + s[0], s[1] = s[1], s[2] + s[2] = value +} + +func (s *medianWindow) Value() float64 { + tmp := *s + sort.Sort(&tmp) + return tmp[1] +} + +func (s *medianWindow) Set(value float64) { + for i := 0; i < len(s); i++ { + s[i] = value + } +} + +// NewMedian is fixed last 3 samples median MovingAverage. +func NewMedian() ewma.MovingAverage { + return NewThreadSafeMovingAverage(new(medianWindow)) +} diff --git a/decor/name.go b/decor/name.go index a5a5d14..a7d477e 100644 --- a/decor/name.go +++ b/decor/name.go @@ -1,45 +1,12 @@ package decor -// StaticName returns name decorator. +// Name decorator displays text that is set once and can't be changed +// during decorator's lifetime. // -// `name` string to display +// `str` string to display // // `wcc` optional WC config -func StaticName(name string, wcc ...WC) Decorator { - return Name(name, wcc...) +// +func Name(str string, wcc ...WC) Decorator { + return Any(func(*Statistics) string { return str }, wcc...) } - -// Name returns name decorator. -// -// `name` string to display -// -// `wcc` optional WC config -func Name(name string, wcc ...WC) Decorator { - var wc WC - for _, widthConf := range wcc { - wc = widthConf - } - wc.Init() - d := &nameDecorator{ - WC: wc, - msg: name, - } - return d -} - -type nameDecorator struct { - WC - msg string - complete *string -} - -func (d *nameDecorator) Decor(st *Statistics) string { - if st.Completed && d.complete != nil { - return d.FormatMsg(*d.complete) - } - return d.FormatMsg(d.msg) -} - -func (d *nameDecorator) OnCompleteMessage(msg string) { - d.complete = &msg -} diff --git a/decor/on_complete.go b/decor/on_complete.go new file mode 100644 index 0000000..0a1526b --- /dev/null +++ b/decor/on_complete.go @@ -0,0 +1,37 @@ +package decor + +// OnComplete returns decorator, which wraps provided decorator, with +// sole purpose to display provided message on complete event. +// +// `decorator` Decorator to wrap +// +// `message` message to display on complete event +// +func OnComplete(decorator Decorator, message string) Decorator { + d := &onCompleteWrapper{ + Decorator: decorator, + msg: message, + } + if md, ok := decorator.(*mergeDecorator); ok { + d.Decorator, md.Decorator = md.Decorator, d + return md + } + return d +} + +type onCompleteWrapper struct { + Decorator + msg string +} + +func (d *onCompleteWrapper) Decor(s *Statistics) string { + if s.Completed { + wc := d.GetConf() + return wc.FormatMsg(d.msg) + } + return d.Decorator.Decor(s) +} + +func (d *onCompleteWrapper) Base() Decorator { + return d.Decorator +} diff --git a/decor/percentage.go b/decor/percentage.go index 078fbcf..65ca7d3 100644 --- a/decor/percentage.go +++ b/decor/percentage.go @@ -2,38 +2,57 @@ import ( "fmt" + "io" + "strconv" - "github.com/vbauerster/mpb/internal" + "github.com/vbauerster/mpb/v5/internal" ) -// Percentage returns percentage decorator. -// -// `wcc` optional WC config -func Percentage(wcc ...WC) Decorator { - var wc WC - for _, widthConf := range wcc { - wc = widthConf +type percentageType float64 + +func (s percentageType) Format(st fmt.State, verb rune) { + var prec int + switch verb { + case 'd': + case 's': + prec = -1 + default: + if p, ok := st.Precision(); ok { + prec = p + } else { + prec = 6 + } } - wc.Init() - d := &percentageDecorator{ - WC: wc, + + io.WriteString(st, strconv.FormatFloat(float64(s), 'f', prec, 64)) + + if st.Flag(' ') { + io.WriteString(st, " ") } - return d + io.WriteString(st, "%") } -type percentageDecorator struct { - WC - completeMsg *string +// Percentage returns percentage decorator. It's a wrapper of NewPercentage. +func Percentage(wcc ...WC) Decorator { + return NewPercentage("% d", wcc...) } -func (d *percentageDecorator) Decor(st *Statistics) string { - if st.Completed && d.completeMsg != nil { - return d.FormatMsg(*d.completeMsg) +// NewPercentage percentage decorator with custom format string. +// +// format examples: +// +// format="%.1f" output: "1.0%" +// format="% .1f" output: "1.0 %" +// format="%d" output: "1%" +// format="% d" output: "1 %" +// +func NewPercentage(format string, wcc ...WC) Decorator { + if format == "" { + format = "% d" } - str := fmt.Sprintf("%d %%", internal.Percentage(st.Total, st.Current, 100)) - return d.FormatMsg(str) + f := func(s *Statistics) string { + p := internal.Percentage(s.Total, s.Current, 100) + return fmt.Sprintf(format, percentageType(p)) + } + return Any(f, wcc...) } - -func (d *percentageDecorator) OnCompleteMessage(msg string) { - d.completeMsg = &msg -} diff --git a/decor/size_type.go b/decor/size_type.go new file mode 100644 index 0000000..e4b9740 --- /dev/null +++ b/decor/size_type.go @@ -0,0 +1,109 @@ +package decor + +import ( + "fmt" + "io" + "math" + "strconv" +) + +//go:generate stringer -type=SizeB1024 -trimprefix=_i +//go:generate stringer -type=SizeB1000 -trimprefix=_ + +const ( + _ib SizeB1024 = iota + 1 + _iKiB SizeB1024 = 1 << (iota * 10) + _iMiB + _iGiB + _iTiB +) + +// SizeB1024 named type, which implements fmt.Formatter interface. It +// adjusts its value according to byte size multiple by 1024 and appends +// appropriate size marker (KiB, MiB, GiB, TiB). +type SizeB1024 int64 + +func (self SizeB1024) Format(st fmt.State, verb rune) { + var prec int + switch verb { + case 'd': + case 's': + prec = -1 + default: + if p, ok := st.Precision(); ok { + prec = p + } else { + prec = 6 + } + } + + var unit SizeB1024 + switch { + case self < _iKiB: + unit = _ib + case self < _iMiB: + unit = _iKiB + case self < _iGiB: + unit = _iMiB + case self < _iTiB: + unit = _iGiB + case self <= math.MaxInt64: + unit = _iTiB + } + + io.WriteString(st, strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) + + if st.Flag(' ') { + io.WriteString(st, " ") + } + io.WriteString(st, unit.String()) +} + +const ( + _b SizeB1000 = 1 + _KB SizeB1000 = _b * 1000 + _MB SizeB1000 = _KB * 1000 + _GB SizeB1000 = _MB * 1000 + _TB SizeB1000 = _GB * 1000 +) + +// SizeB1000 named type, which implements fmt.Formatter interface. It +// adjusts its value according to byte size multiple by 1000 and appends +// appropriate size marker (KB, MB, GB, TB). +type SizeB1000 int64 + +func (self SizeB1000) Format(st fmt.State, verb rune) { + var prec int + switch verb { + case 'd': + case 's': + prec = -1 + default: + if p, ok := st.Precision(); ok { + prec = p + } else { + prec = 6 + } + } + + var unit SizeB1000 + switch { + case self < _KB: + unit = _b + case self < _MB: + unit = _KB + case self < _GB: + unit = _MB + case self < _TB: + unit = _GB + case self <= math.MaxInt64: + unit = _TB + } + + io.WriteString(st, strconv.FormatFloat(float64(self)/float64(unit), 'f', prec, 64)) + + if st.Flag(' ') { + io.WriteString(st, " ") + } + io.WriteString(st, unit.String()) +} diff --git a/decor/size_type_test.go b/decor/size_type_test.go new file mode 100644 index 0000000..8601e25 --- /dev/null +++ b/decor/size_type_test.go @@ -0,0 +1,102 @@ +package decor + +import ( + "fmt" + "testing" +) + +func TestB1024(t *testing.T) { + cases := map[string]struct { + value int64 + verb string + expected string + }{ + "verb %f": {12345678, "%f", "11.773756MiB"}, + "verb %.0f": {12345678, "%.0f", "12MiB"}, + "verb %.1f": {12345678, "%.1f", "11.8MiB"}, + "verb %.2f": {12345678, "%.2f", "11.77MiB"}, + "verb %.3f": {12345678, "%.3f", "11.774MiB"}, + + "verb % f": {12345678, "% f", "11.773756 MiB"}, + "verb % .0f": {12345678, "% .0f", "12 MiB"}, + "verb % .1f": {12345678, "% .1f", "11.8 MiB"}, + "verb % .2f": {12345678, "% .2f", "11.77 MiB"}, + "verb % .3f": {12345678, "% .3f", "11.774 MiB"}, + + "1000 %f": {1000, "%f", "1000.000000b"}, + "1000 %d": {1000, "%d", "1000b"}, + "1000 %s": {1000, "%s", "1000b"}, + "1024 %f": {1024, "%f", "1.000000KiB"}, + "1024 %d": {1024, "%d", "1KiB"}, + "1024 %.1f": {1024, "%.1f", "1.0KiB"}, + "1024 %s": {1024, "%s", "1KiB"}, + "3*MiB+140KiB %f": {3*int64(_iMiB) + 140*int64(_iKiB), "%f", "3.136719MiB"}, + "3*MiB+140KiB %d": {3*int64(_iMiB) + 140*int64(_iKiB), "%d", "3MiB"}, + "3*MiB+140KiB %.1f": {3*int64(_iMiB) + 140*int64(_iKiB), "%.1f", "3.1MiB"}, + "3*MiB+140KiB %s": {3*int64(_iMiB) + 140*int64(_iKiB), "%s", "3.13671875MiB"}, + "2*GiB %f": {2 * int64(_iGiB), "%f", "2.000000GiB"}, + "2*GiB %d": {2 * int64(_iGiB), "%d", "2GiB"}, + "2*GiB %.1f": {2 * int64(_iGiB), "%.1f", "2.0GiB"}, + "2*GiB %s": {2 * int64(_iGiB), "%s", "2GiB"}, + "4*TiB %f": {4 * int64(_iTiB), "%f", "4.000000TiB"}, + "4*TiB %d": {4 * int64(_iTiB), "%d", "4TiB"}, + "4*TiB %.1f": {4 * int64(_iTiB), "%.1f", "4.0TiB"}, + "4*TiB %s": {4 * int64(_iTiB), "%s", "4TiB"}, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := fmt.Sprintf(tc.verb, SizeB1024(tc.value)) + if got != tc.expected { + t.Fatalf("expected: %q, got: %q\n", tc.expected, got) + } + }) + } +} + +func TestB1000(t *testing.T) { + cases := map[string]struct { + value int64 + verb string + expected string + }{ + "verb %f": {12345678, "%f", "12.345678MB"}, + "verb %.0f": {12345678, "%.0f", "12MB"}, + "verb %.1f": {12345678, "%.1f", "12.3MB"}, + "verb %.2f": {12345678, "%.2f", "12.35MB"}, + "verb %.3f": {12345678, "%.3f", "12.346MB"}, + + "verb % f": {12345678, "% f", "12.345678 MB"}, + "verb % .0f": {12345678, "% .0f", "12 MB"}, + "verb % .1f": {12345678, "% .1f", "12.3 MB"}, + "verb % .2f": {12345678, "% .2f", "12.35 MB"}, + "verb % .3f": {12345678, "% .3f", "12.346 MB"}, + + "1000 %f": {1000, "%f", "1.000000KB"}, + "1000 %d": {1000, "%d", "1KB"}, + "1000 %s": {1000, "%s", "1KB"}, + "1024 %f": {1024, "%f", "1.024000KB"}, + "1024 %d": {1024, "%d", "1KB"}, + "1024 %.1f": {1024, "%.1f", "1.0KB"}, + "1024 %s": {1024, "%s", "1.024KB"}, + "3*MB+140*KB %f": {3*int64(_MB) + 140*int64(_KB), "%f", "3.140000MB"}, + "3*MB+140*KB %d": {3*int64(_MB) + 140*int64(_KB), "%d", "3MB"}, + "3*MB+140*KB %.1f": {3*int64(_MB) + 140*int64(_KB), "%.1f", "3.1MB"}, + "3*MB+140*KB %s": {3*int64(_MB) + 140*int64(_KB), "%s", "3.14MB"}, + "2*GB %f": {2 * int64(_GB), "%f", "2.000000GB"}, + "2*GB %d": {2 * int64(_GB), "%d", "2GB"}, + "2*GB %.1f": {2 * int64(_GB), "%.1f", "2.0GB"}, + "2*GB %s": {2 * int64(_GB), "%s", "2GB"}, + "4*TB %f": {4 * int64(_TB), "%f", "4.000000TB"}, + "4*TB %d": {4 * int64(_TB), "%d", "4TB"}, + "4*TB %.1f": {4 * int64(_TB), "%.1f", "4.0TB"}, + "4*TB %s": {4 * int64(_TB), "%s", "4TB"}, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := fmt.Sprintf(tc.verb, SizeB1000(tc.value)) + if got != tc.expected { + t.Fatalf("expected: %q, got: %q\n", tc.expected, got) + } + }) + } +} diff --git a/decor/sizeb1000_string.go b/decor/sizeb1000_string.go new file mode 100644 index 0000000..3f32ef7 --- /dev/null +++ b/decor/sizeb1000_string.go @@ -0,0 +1,41 @@ +// Code generated by "stringer -type=SizeB1000 -trimprefix=_"; DO NOT EDIT. + +package decor + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[_b-1] + _ = x[_KB-1000] + _ = x[_MB-1000000] + _ = x[_GB-1000000000] + _ = x[_TB-1000000000000] +} + +const ( + _SizeB1000_name_0 = "b" + _SizeB1000_name_1 = "KB" + _SizeB1000_name_2 = "MB" + _SizeB1000_name_3 = "GB" + _SizeB1000_name_4 = "TB" +) + +func (i SizeB1000) String() string { + switch { + case i == 1: + return _SizeB1000_name_0 + case i == 1000: + return _SizeB1000_name_1 + case i == 1000000: + return _SizeB1000_name_2 + case i == 1000000000: + return _SizeB1000_name_3 + case i == 1000000000000: + return _SizeB1000_name_4 + default: + return "SizeB1000(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/decor/sizeb1024_string.go b/decor/sizeb1024_string.go new file mode 100644 index 0000000..9fca66c --- /dev/null +++ b/decor/sizeb1024_string.go @@ -0,0 +1,41 @@ +// Code generated by "stringer -type=SizeB1024 -trimprefix=_i"; DO NOT EDIT. + +package decor + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[_ib-1] + _ = x[_iKiB-1024] + _ = x[_iMiB-1048576] + _ = x[_iGiB-1073741824] + _ = x[_iTiB-1099511627776] +} + +const ( + _SizeB1024_name_0 = "b" + _SizeB1024_name_1 = "KiB" + _SizeB1024_name_2 = "MiB" + _SizeB1024_name_3 = "GiB" + _SizeB1024_name_4 = "TiB" +) + +func (i SizeB1024) String() string { + switch { + case i == 1: + return _SizeB1024_name_0 + case i == 1024: + return _SizeB1024_name_1 + case i == 1048576: + return _SizeB1024_name_2 + case i == 1073741824: + return _SizeB1024_name_3 + case i == 1099511627776: + return _SizeB1024_name_4 + default: + return "SizeB1024(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/decor/speed.go b/decor/speed.go index 74658ce..8a48e3f 100644 --- a/decor/speed.go +++ b/decor/speed.go @@ -4,137 +4,41 @@ "fmt" "io" "math" - "strconv" - "strings" "time" "github.com/VividCortex/ewma" ) -type SpeedKiB float64 - -func (s SpeedKiB) Format(st fmt.State, verb rune) { - prec, ok := st.Precision() - - if verb == 'd' || !ok { - prec = 0 - } - if verb == 'f' && !ok { - prec = 6 - } - // retain old beahavior if s verb used - if verb == 's' { - prec = 1 - } - - var res, unit string - switch { - case s >= TiB: - unit = "TiB/s" - res = strconv.FormatFloat(float64(s)/TiB, 'f', prec, 64) - case s >= GiB: - unit = "GiB/s" - res = strconv.FormatFloat(float64(s)/GiB, 'f', prec, 64) - case s >= MiB: - unit = "MiB/s" - res = strconv.FormatFloat(float64(s)/MiB, 'f', prec, 64) - case s >= KiB: - unit = "KiB/s" - res = strconv.FormatFloat(float64(s)/KiB, 'f', prec, 64) - default: - unit = "b/s" - res = strconv.FormatInt(int64(s), 10) - } - - if st.Flag(' ') { - res += " " - } - res += unit - - if w, ok := st.Width(); ok { - if len(res) < w { - pad := strings.Repeat(" ", w-len(res)) - if st.Flag(int('-')) { - res += pad - } else { - res = pad + res - } - } - } - - io.WriteString(st, res) +// FmtAsSpeed adds "/s" to the end of the input formatter. To be +// used with SizeB1000 or SizeB1024 types, for example: +// +// fmt.Printf("%.1f", FmtAsSpeed(SizeB1024(2048))) +// +func FmtAsSpeed(input fmt.Formatter) fmt.Formatter { + return &speedFormatter{input} } -type SpeedKB float64 - -func (s SpeedKB) Format(st fmt.State, verb rune) { - prec, ok := st.Precision() - - if verb == 'd' || !ok { - prec = 0 - } - if verb == 'f' && !ok { - prec = 6 - } - // retain old beahavior if s verb used - if verb == 's' { - prec = 1 - } - - var res, unit string - switch { - case s >= TB: - unit = "TB/s" - res = strconv.FormatFloat(float64(s)/TB, 'f', prec, 64) - case s >= GB: - unit = "GB/s" - res = strconv.FormatFloat(float64(s)/GB, 'f', prec, 64) - case s >= MB: - unit = "MB/s" - res = strconv.FormatFloat(float64(s)/MB, 'f', prec, 64) - case s >= KB: - unit = "kB/s" - res = strconv.FormatFloat(float64(s)/KB, 'f', prec, 64) - default: - unit = "b/s" - res = strconv.FormatInt(int64(s), 10) - } - - if st.Flag(' ') { - res += " " - } - res += unit - - if w, ok := st.Width(); ok { - if len(res) < w { - pad := strings.Repeat(" ", w-len(res)) - if st.Flag(int('-')) { - res += pad - } else { - res = pad + res - } - } - } - - io.WriteString(st, res) +type speedFormatter struct { + fmt.Formatter } -// EwmaSpeed exponential-weighted-moving-average based speed decorator, -// with dynamic unit measure adjustment. -// -// `unit` one of [0|UnitKiB|UnitKB] zero for no unit -// -// `unitFormat` printf compatible verb for value, like "%f" or "%d" -// -// `average` MovingAverage implementation -// -// `wcc` optional WC config -// -// unitFormat example if UnitKiB is chosen: -// -// "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s" -func EwmaSpeed(unit int, unitFormat string, age float64, wcc ...WC) Decorator { - return MovingAverageSpeed(unit, unitFormat, ewma.NewMovingAverage(age), wcc...) +func (self *speedFormatter) Format(st fmt.State, verb rune) { + self.Formatter.Format(st, verb) + io.WriteString(st, "/s") +} + +// EwmaSpeed exponential-weighted-moving-average based speed decorator. +// For this decorator to work correctly you have to measure each +// iteration's duration and pass it to the +// *Bar.DecoratorEwmaUpdate(time.Duration) method after each increment. +func EwmaSpeed(unit int, format string, age float64, wcc ...WC) Decorator { + var average ewma.MovingAverage + if age == 0 { + average = ewma.NewMovingAverage() + } else { + average = ewma.NewMovingAverage(age) + } + return MovingAverageSpeed(unit, format, NewThreadSafeMovingAverage(average), wcc...) } // MovingAverageSpeed decorator relies on MovingAverage implementation @@ -142,130 +46,126 @@ // // `unit` one of [0|UnitKiB|UnitKB] zero for no unit // -// `unitFormat` printf compatible verb for value, like "%f" or "%d" +// `format` printf compatible verb for value, like "%f" or "%d" // // `average` MovingAverage implementation // // `wcc` optional WC config -func MovingAverageSpeed(unit int, unitFormat string, average MovingAverage, wcc ...WC) Decorator { - var wc WC - for _, widthConf := range wcc { - wc = widthConf +// +// format examples: +// +// unit=UnitKiB, format="%.1f" output: "1.0MiB/s" +// unit=UnitKiB, format="% .1f" output: "1.0 MiB/s" +// unit=UnitKB, format="%.1f" output: "1.0MB/s" +// unit=UnitKB, format="% .1f" output: "1.0 MB/s" +// +func MovingAverageSpeed(unit int, format string, average ewma.MovingAverage, wcc ...WC) Decorator { + if format == "" { + format = "%.0f" } - wc.Init() d := &movingAverageSpeed{ - WC: wc, - unit: unit, - unitFormat: unitFormat, - average: average, + WC: initWC(wcc...), + average: average, + producer: chooseSpeedProducer(unit, format), } return d } type movingAverageSpeed struct { WC - unit int - unitFormat string - average ewma.MovingAverage - msg string - completeMsg *string + producer func(float64) string + average ewma.MovingAverage + msg string } -func (d *movingAverageSpeed) Decor(st *Statistics) string { - if st.Completed { - if d.completeMsg != nil { - return d.FormatMsg(*d.completeMsg) +func (d *movingAverageSpeed) Decor(s *Statistics) string { + if !s.Completed { + var speed float64 + if v := d.average.Value(); v > 0 { + speed = 1 / v } - return d.FormatMsg(d.msg) + d.msg = d.producer(speed * 1e9) } - - speed := d.average.Value() - switch d.unit { - case UnitKiB: - d.msg = fmt.Sprintf(d.unitFormat, SpeedKiB(speed)) - case UnitKB: - d.msg = fmt.Sprintf(d.unitFormat, SpeedKB(speed)) - default: - d.msg = fmt.Sprintf(d.unitFormat, speed) - } - return d.FormatMsg(d.msg) } -func (s *movingAverageSpeed) NextAmount(n int, wdd ...time.Duration) { - var workDuration time.Duration - for _, wd := range wdd { - workDuration = wd - } - speed := float64(n) / workDuration.Seconds() / 1000 - if math.IsInf(speed, 0) || math.IsNaN(speed) { +func (d *movingAverageSpeed) EwmaUpdate(n int64, dur time.Duration) { + durPerByte := float64(dur) / float64(n) + if math.IsInf(durPerByte, 0) || math.IsNaN(durPerByte) { return } - s.average.Add(speed) + d.average.Add(durPerByte) } -func (d *movingAverageSpeed) OnCompleteMessage(msg string) { - d.completeMsg = &msg +// AverageSpeed decorator with dynamic unit measure adjustment. It's +// a wrapper of NewAverageSpeed. +func AverageSpeed(unit int, format string, wcc ...WC) Decorator { + return NewAverageSpeed(unit, format, time.Now(), wcc...) } -// AverageSpeed decorator with dynamic unit measure adjustment. +// NewAverageSpeed decorator with dynamic unit measure adjustment and +// user provided start time. // // `unit` one of [0|UnitKiB|UnitKB] zero for no unit // -// `unitFormat` printf compatible verb for value, like "%f" or "%d" +// `format` printf compatible verb for value, like "%f" or "%d" +// +// `startTime` start time // // `wcc` optional WC config // -// unitFormat example if UnitKiB is chosen: +// format examples: // -// "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s" -func AverageSpeed(unit int, unitFormat string, wcc ...WC) Decorator { - var wc WC - for _, widthConf := range wcc { - wc = widthConf +// unit=UnitKiB, format="%.1f" output: "1.0MiB/s" +// unit=UnitKiB, format="% .1f" output: "1.0 MiB/s" +// unit=UnitKB, format="%.1f" output: "1.0MB/s" +// unit=UnitKB, format="% .1f" output: "1.0 MB/s" +// +func NewAverageSpeed(unit int, format string, startTime time.Time, wcc ...WC) Decorator { + if format == "" { + format = "%.0f" } - wc.Init() d := &averageSpeed{ - WC: wc, - unit: unit, - unitFormat: unitFormat, - startTime: time.Now(), + WC: initWC(wcc...), + startTime: startTime, + producer: chooseSpeedProducer(unit, format), } return d } type averageSpeed struct { WC - unit int - unitFormat string - startTime time.Time - msg string - completeMsg *string + startTime time.Time + producer func(float64) string + msg string } -func (d *averageSpeed) Decor(st *Statistics) string { - if st.Completed { - if d.completeMsg != nil { - return d.FormatMsg(*d.completeMsg) - } - return d.FormatMsg(d.msg) - } - - timeElapsed := time.Since(d.startTime) - speed := float64(st.Current) / timeElapsed.Seconds() - - switch d.unit { - case UnitKiB: - d.msg = fmt.Sprintf(d.unitFormat, SpeedKiB(speed)) - case UnitKB: - d.msg = fmt.Sprintf(d.unitFormat, SpeedKB(speed)) - default: - d.msg = fmt.Sprintf(d.unitFormat, speed) +func (d *averageSpeed) Decor(s *Statistics) string { + if !s.Completed { + speed := float64(s.Current) / float64(time.Since(d.startTime)) + d.msg = d.producer(speed * 1e9) } return d.FormatMsg(d.msg) } -func (d *averageSpeed) OnCompleteMessage(msg string) { - d.completeMsg = &msg +func (d *averageSpeed) AverageAdjust(startTime time.Time) { + d.startTime = startTime } + +func chooseSpeedProducer(unit int, format string) func(float64) string { + switch unit { + case UnitKiB: + return func(speed float64) string { + return fmt.Sprintf(format, FmtAsSpeed(SizeB1024(math.Round(speed)))) + } + case UnitKB: + return func(speed float64) string { + return fmt.Sprintf(format, FmtAsSpeed(SizeB1000(math.Round(speed)))) + } + default: + return func(speed float64) string { + return fmt.Sprintf(format, speed) + } + } +} diff --git a/decor/speed_test.go b/decor/speed_test.go index a95252d..4f16aea 100644 --- a/decor/speed_test.go +++ b/decor/speed_test.go @@ -1,141 +1,261 @@ package decor import ( - "fmt" "testing" + "time" ) -func TestSpeedKiB(t *testing.T) { - cases := map[string]struct { - value int64 - verb string +func TestSpeedKiBDecor(t *testing.T) { + cases := []struct { + name string + fmt string + unit int + current int64 + elapsed time.Duration expected string }{ - "verb %f": {12345678, "%f", "11.773756MiB/s"}, - "verb %.0f": {12345678, "%.0f", "12MiB/s"}, - "verb %.1f": {12345678, "%.1f", "11.8MiB/s"}, - "verb %.2f": {12345678, "%.2f", "11.77MiB/s"}, - "verb %.3f": {12345678, "%.3f", "11.774MiB/s"}, - - "verb % f": {12345678, "% f", "11.773756 MiB/s"}, - "verb % .0f": {12345678, "% .0f", "12 MiB/s"}, - "verb % .1f": {12345678, "% .1f", "11.8 MiB/s"}, - "verb % .2f": {12345678, "% .2f", "11.77 MiB/s"}, - "verb % .3f": {12345678, "% .3f", "11.774 MiB/s"}, - - "verb %10.f": {12345678, "%10.f", " 12MiB/s"}, - "verb %10.0f": {12345678, "%10.0f", " 12MiB/s"}, - "verb %10.1f": {12345678, "%10.1f", " 11.8MiB/s"}, - "verb %10.2f": {12345678, "%10.2f", "11.77MiB/s"}, - "verb %10.3f": {12345678, "%10.3f", "11.774MiB/s"}, - - "verb % 10.f": {12345678, "% 10.f", " 12 MiB/s"}, - "verb % 10.0f": {12345678, "% 10.0f", " 12 MiB/s"}, - "verb % 10.1f": {12345678, "% 10.1f", "11.8 MiB/s"}, - - "verb %-10.f": {12345678, "%-10.f", "12MiB/s "}, - "verb %-10.0f": {12345678, "%-10.0f", "12MiB/s "}, - "verb %-10.1f": {12345678, "%-10.1f", "11.8MiB/s "}, - "verb %-10.2f": {12345678, "%10.2f", "11.77MiB/s"}, - "verb %-10.3f": {12345678, "%10.3f", "11.774MiB/s"}, - - "verb % -10.f": {12345678, "% -10.f", "12 MiB/s "}, - "verb % -10.0f": {12345678, "% -10.0f", "12 MiB/s "}, - "verb % -10.1f": {12345678, "% -10.1f", "11.8 MiB/s"}, - - "1000 %f": {1000, "%f", "1000b/s"}, - "1000 %d": {1000, "%d", "1000b/s"}, - "1000 %s": {1000, "%s", "1000b/s"}, - "1024 %f": {1024, "%f", "1.000000KiB/s"}, - "1024 %d": {1024, "%d", "1KiB/s"}, - "1024 %.1f": {1024, "%.1f", "1.0KiB/s"}, - "1024 %s": {1024, "%s", "1.0KiB/s"}, - "3*MiB/s+140KiB/s %f": {3*MiB + 140*KiB, "%f", "3.136719MiB/s"}, - "3*MiB/s+140KiB/s %d": {3*MiB + 140*KiB, "%d", "3MiB/s"}, - "3*MiB/s+140KiB/s %.1f": {3*MiB + 140*KiB, "%.1f", "3.1MiB/s"}, - "3*MiB/s+140KiB/s %s": {3*MiB + 140*KiB, "%s", "3.1MiB/s"}, - "2*GiB/s %f": {2 * GiB, "%f", "2.000000GiB/s"}, - "2*GiB/s %d": {2 * GiB, "%d", "2GiB/s"}, - "2*GiB/s %.1f": {2 * GiB, "%.1f", "2.0GiB/s"}, - "2*GiB/s %s": {2 * GiB, "%s", "2.0GiB/s"}, - "4*TiB/s %f": {4 * TiB, "%f", "4.000000TiB/s"}, - "4*TiB/s %d": {4 * TiB, "%d", "4TiB/s"}, - "4*TiB/s %.1f": {4 * TiB, "%.1f", "4.0TiB/s"}, - "4*TiB/s %s": {4 * TiB, "%s", "4.0TiB/s"}, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - got := fmt.Sprintf(tc.verb, SpeedKiB(tc.value)) - if got != tc.expected { - t.Fatalf("expected: %q, got: %q\n", tc.expected, got) + { + name: "empty fmt", + unit: UnitKiB, + fmt: "", + current: 0, + elapsed: time.Second, + expected: "0b/s", + }, + { + name: "UnitKiB:%d:0b", + unit: UnitKiB, + fmt: "%d", + current: 0, + elapsed: time.Second, + expected: "0b/s", + }, + { + name: "UnitKiB:% .2f:0b", + unit: UnitKiB, + fmt: "% .2f", + current: 0, + elapsed: time.Second, + expected: "0.00 b/s", + }, + { + name: "UnitKiB:%d:1b", + unit: UnitKiB, + fmt: "%d", + current: 1, + elapsed: time.Second, + expected: "1b/s", + }, + { + name: "UnitKiB:% .2f:1b", + unit: UnitKiB, + fmt: "% .2f", + current: 1, + elapsed: time.Second, + expected: "1.00 b/s", + }, + { + name: "UnitKiB:%d:KiB", + unit: UnitKiB, + fmt: "%d", + current: 2 * int64(_iKiB), + elapsed: 1 * time.Second, + expected: "2KiB/s", + }, + { + name: "UnitKiB:% .f:KiB", + unit: UnitKiB, + fmt: "% .2f", + current: 2 * int64(_iKiB), + elapsed: 1 * time.Second, + expected: "2.00 KiB/s", + }, + { + name: "UnitKiB:%d:MiB", + unit: UnitKiB, + fmt: "%d", + current: 2 * int64(_iMiB), + elapsed: 1 * time.Second, + expected: "2MiB/s", + }, + { + name: "UnitKiB:% .2f:MiB", + unit: UnitKiB, + fmt: "% .2f", + current: 2 * int64(_iMiB), + elapsed: 1 * time.Second, + expected: "2.00 MiB/s", + }, + { + name: "UnitKiB:%d:GiB", + unit: UnitKiB, + fmt: "%d", + current: 2 * int64(_iGiB), + elapsed: 1 * time.Second, + expected: "2GiB/s", + }, + { + name: "UnitKiB:% .2f:GiB", + unit: UnitKiB, + fmt: "% .2f", + current: 2 * int64(_iGiB), + elapsed: 1 * time.Second, + expected: "2.00 GiB/s", + }, + { + name: "UnitKiB:%d:TiB", + unit: UnitKiB, + fmt: "%d", + current: 2 * int64(_iTiB), + elapsed: 1 * time.Second, + expected: "2TiB/s", + }, + { + name: "UnitKiB:% .2f:TiB", + unit: UnitKiB, + fmt: "% .2f", + current: 2 * int64(_iTiB), + elapsed: 1 * time.Second, + expected: "2.00 TiB/s", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) + stat := &Statistics{ + Current: tc.current, + } + res := decor.Decor(stat) + if res != tc.expected { + t.Fatalf("expected: %q, got: %q\n", tc.expected, res) } }) } } -func TestSpeedKB(t *testing.T) { - cases := map[string]struct { - value int64 - verb string +func TestSpeedKBDecor(t *testing.T) { + cases := []struct { + name string + fmt string + unit int + current int64 + elapsed time.Duration expected string }{ - "verb %f": {12345678, "%f", "12.345678MB/s"}, - "verb %.0f": {12345678, "%.0f", "12MB/s"}, - "verb %.1f": {12345678, "%.1f", "12.3MB/s"}, - "verb %.2f": {12345678, "%.2f", "12.35MB/s"}, - "verb %.3f": {12345678, "%.3f", "12.346MB/s"}, - - "verb % f": {12345678, "% f", "12.345678 MB/s"}, - "verb % .0f": {12345678, "% .0f", "12 MB/s"}, - "verb % .1f": {12345678, "% .1f", "12.3 MB/s"}, - "verb % .2f": {12345678, "% .2f", "12.35 MB/s"}, - "verb % .3f": {12345678, "% .3f", "12.346 MB/s"}, - - "verb %10.f": {12345678, "%10.f", " 12MB/s"}, - "verb %10.0f": {12345678, "%10.0f", " 12MB/s"}, - "verb %10.1f": {12345678, "%10.1f", " 12.3MB/s"}, - "verb %10.2f": {12345678, "%10.2f", " 12.35MB/s"}, - "verb %10.3f": {12345678, "%10.3f", "12.346MB/s"}, - - "verb % 10.f": {12345678, "% 10.f", " 12 MB/s"}, - "verb % 10.0f": {12345678, "% 10.0f", " 12 MB/s"}, - "verb % 10.1f": {12345678, "% 10.1f", " 12.3 MB/s"}, - - "verb %-10.f": {12345678, "%-10.f", "12MB/s "}, - "verb %-10.0f": {12345678, "%-10.0f", "12MB/s "}, - "verb %-10.1f": {12345678, "%-10.1f", "12.3MB/s "}, - "verb %-10.2f": {12345678, "%10.2f", " 12.35MB/s"}, - "verb %-10.3f": {12345678, "%10.3f", "12.346MB/s"}, - - "verb % -10.f": {12345678, "% -10.f", "12 MB/s "}, - "verb % -10.0f": {12345678, "% -10.0f", "12 MB/s "}, - "verb % -10.1f": {12345678, "% -10.1f", "12.3 MB/s "}, - - "1000 %f": {1000, "%f", "1.000000kB/s"}, - "1000 %d": {1000, "%d", "1kB/s"}, - "1000 %s": {1000, "%s", "1.0kB/s"}, - "1024 %f": {1024, "%f", "1.024000kB/s"}, - "1024 %d": {1024, "%d", "1kB/s"}, - "1024 %.1f": {1024, "%.1f", "1.0kB/s"}, - "1024 %s": {1024, "%s", "1.0kB/s"}, - "3*MB/s+140*KB/s %f": {3*MB + 140*KB, "%f", "3.140000MB/s"}, - "3*MB/s+140*KB/s %d": {3*MB + 140*KB, "%d", "3MB/s"}, - "3*MB/s+140*KB/s %.1f": {3*MB + 140*KB, "%.1f", "3.1MB/s"}, - "3*MB/s+140*KB/s %s": {3*MB + 140*KB, "%s", "3.1MB/s"}, - "2*GB/s %f": {2 * GB, "%f", "2.000000GB/s"}, - "2*GB/s %d": {2 * GB, "%d", "2GB/s"}, - "2*GB/s %.1f": {2 * GB, "%.1f", "2.0GB/s"}, - "2*GB/s %s": {2 * GB, "%s", "2.0GB/s"}, - "4*TB/s %f": {4 * TB, "%f", "4.000000TB/s"}, - "4*TB/s %d": {4 * TB, "%d", "4TB/s"}, - "4*TB/s %.1f": {4 * TB, "%.1f", "4.0TB/s"}, - "4*TB/s %s": {4 * TB, "%s", "4.0TB/s"}, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - got := fmt.Sprintf(tc.verb, SpeedKB(tc.value)) - if got != tc.expected { - t.Fatalf("expected: %q, got: %q\n", tc.expected, got) + { + name: "empty fmt", + unit: UnitKB, + fmt: "", + current: 0, + elapsed: time.Second, + expected: "0b/s", + }, + { + name: "UnitKB:%d:0b", + unit: UnitKB, + fmt: "%d", + current: 0, + elapsed: time.Second, + expected: "0b/s", + }, + { + name: "UnitKB:% .2f:0b", + unit: UnitKB, + fmt: "% .2f", + current: 0, + elapsed: time.Second, + expected: "0.00 b/s", + }, + { + name: "UnitKB:%d:1b", + unit: UnitKB, + fmt: "%d", + current: 1, + elapsed: time.Second, + expected: "1b/s", + }, + { + name: "UnitKB:% .2f:1b", + unit: UnitKB, + fmt: "% .2f", + current: 1, + elapsed: time.Second, + expected: "1.00 b/s", + }, + { + name: "UnitKB:%d:KB", + unit: UnitKB, + fmt: "%d", + current: 2 * int64(_KB), + elapsed: 1 * time.Second, + expected: "2KB/s", + }, + { + name: "UnitKB:% .f:KB", + unit: UnitKB, + fmt: "% .2f", + current: 2 * int64(_KB), + elapsed: 1 * time.Second, + expected: "2.00 KB/s", + }, + { + name: "UnitKB:%d:MB", + unit: UnitKB, + fmt: "%d", + current: 2 * int64(_MB), + elapsed: 1 * time.Second, + expected: "2MB/s", + }, + { + name: "UnitKB:% .2f:MB", + unit: UnitKB, + fmt: "% .2f", + current: 2 * int64(_MB), + elapsed: 1 * time.Second, + expected: "2.00 MB/s", + }, + { + name: "UnitKB:%d:GB", + unit: UnitKB, + fmt: "%d", + current: 2 * int64(_GB), + elapsed: 1 * time.Second, + expected: "2GB/s", + }, + { + name: "UnitKB:% .2f:GB", + unit: UnitKB, + fmt: "% .2f", + current: 2 * int64(_GB), + elapsed: 1 * time.Second, + expected: "2.00 GB/s", + }, + { + name: "UnitKB:%d:TB", + unit: UnitKB, + fmt: "%d", + current: 2 * int64(_TB), + elapsed: 1 * time.Second, + expected: "2TB/s", + }, + { + name: "UnitKB:% .2f:TB", + unit: UnitKB, + fmt: "% .2f", + current: 2 * int64(_TB), + elapsed: 1 * time.Second, + expected: "2.00 TB/s", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) + stat := &Statistics{ + Current: tc.current, + } + res := decor.Decor(stat) + if res != tc.expected { + t.Fatalf("expected: %q, got: %q\n", tc.expected, res) } }) } diff --git a/decor/spinner.go b/decor/spinner.go new file mode 100644 index 0000000..abfb2f7 --- /dev/null +++ b/decor/spinner.go @@ -0,0 +1,21 @@ +package decor + +var defaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} + +// Spinner returns spinner decorator. +// +// `frames` spinner frames, if nil or len==0, default is used +// +// `wcc` optional WC config +func Spinner(frames []string, wcc ...WC) Decorator { + if len(frames) == 0 { + frames = defaultSpinnerStyle + } + var count uint + f := func(s *Statistics) string { + frame := frames[count%uint(len(frames))] + count++ + return frame + } + return Any(f, wcc...) +} diff --git a/decorators_test.go b/decorators_test.go index a057197..4bc475a 100644 --- a/decorators_test.go +++ b/decorators_test.go @@ -4,8 +4,8 @@ "sync" "testing" - . "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" + . "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" ) func TestNameDecorator(t *testing.T) { @@ -48,7 +48,7 @@ func TestPercentageDwidthSync(t *testing.T) { testCases := [][]step{ - []step{ + { { &decor.Statistics{Total: 100, Current: 8}, decor.Percentage(decor.WCSyncWidth), @@ -60,7 +60,7 @@ "9 %", }, }, - []step{ + { { &decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidth), @@ -72,7 +72,7 @@ "10 %", }, }, - []step{ + { { &decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidth), @@ -92,7 +92,7 @@ func TestPercentageDwidthSyncDidentRight(t *testing.T) { testCases := [][]step{ - []step{ + { { &decor.Statistics{Total: 100, Current: 8}, decor.Percentage(decor.WCSyncWidthR), @@ -104,7 +104,7 @@ "9 %", }, }, - []step{ + { { &decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidthR), @@ -116,7 +116,7 @@ "10 %", }, }, - []step{ + { { &decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidthR), @@ -136,7 +136,7 @@ func TestPercentageDSyncSpace(t *testing.T) { testCases := [][]step{ - []step{ + { { &decor.Statistics{Total: 100, Current: 8}, decor.Percentage(decor.WCSyncSpace), @@ -148,7 +148,7 @@ " 9 %", }, }, - []step{ + { { &decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncSpace), @@ -160,7 +160,7 @@ " 10 %", }, }, - []step{ + { { &decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncSpace), @@ -211,7 +211,7 @@ func toSyncMatrix(ss []step) map[int][]chan int { var column []chan int for _, s := range ss { - if ok, ch := s.decorator.Syncable(); ok { + if ch, ok := s.decorator.Sync(); ok { column = append(column, ch) } } diff --git a/doc.go b/doc.go index 1624595..5ada717 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,2 @@ -// Copyright (C) 2016-2018 Vladimir Bauer -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - // Package mpb is a library for rendering progress bars in terminal applications. package mpb diff --git a/draw_test.go b/draw_test.go index 9374528..cc59513 100644 --- a/draw_test.go +++ b/draw_test.go @@ -13,9 +13,44 @@ total, current int64 barWidth int trimSpace bool + reverse bool rup int64 want string }{ + 0: { + { + name: "t,c,bw{60,20,80}", + total: 60, + current: 20, + barWidth: 80, + want: "", + }, + { + name: "t,c,bw{60,20,80}", + total: 60, + current: 20, + barWidth: 80, + trimSpace: true, + want: "", + }, + }, + 1: { + { + name: "t,c,bw{60,20,80}", + total: 60, + current: 20, + barWidth: 80, + want: "", + }, + { + name: "t,c,bw{60,20,80}", + total: 60, + current: 20, + barWidth: 80, + trimSpace: true, + want: "", + }, + }, 2: { { name: "t,c,bw{60,20,80}", @@ -64,7 +99,7 @@ current: 20, barWidth: 80, trimSpace: true, - want: "[]", + want: "[>-]", }, }, 5: { @@ -90,7 +125,7 @@ total: 60, current: 20, barWidth: 80, - want: " [] ", + want: " [>-] ", }, { name: "t,c,bw,trim{60,20,80,true}", @@ -197,6 +232,15 @@ barWidth: 100, trimSpace: true, want: "[===============================>------------------------------------------------------------------]", + }, + { + name: "t,c,bw,trim,rev{100,33,100,true,true}", + total: 100, + current: 33, + barWidth: 100, + trimSpace: true, + reverse: true, + want: "[------------------------------------------------------------------<===============================]", }, { name: "t,c,bw,rup{100,33,100,33}", @@ -214,6 +258,16 @@ rup: 33, trimSpace: true, want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]", + }, + { + name: "t,c,bw,rup,trim,rev{100,33,100,33,true,true}", + total: 100, + current: 33, + barWidth: 100, + rup: 33, + trimSpace: true, + reverse: true, + want: "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]", }, { name: "t,c,bw,rup{100,40,100,32}", @@ -268,7 +322,7 @@ var tmpBuf bytes.Buffer for termWidth, cases := range testSuite { for _, tc := range cases { - s := newTestState() + s := newTestState(tc.reverse) s.width = tc.barWidth s.total = tc.total s.current = tc.current @@ -279,7 +333,7 @@ } } tmpBuf.Reset() - tmpBuf.ReadFrom(s.draw(termWidth)) + tmpBuf.ReadFrom(s.draw(termWidth, newStatistics(s))) by := tmpBuf.Bytes() by = by[:len(by)-1] @@ -295,9 +349,9 @@ } } -func newTestState() *bState { +func newTestState(reverse bool) *bState { s := &bState{ - filler: newDefaultBarFiller(), + filler: NewBarFiller(DefaultBarStyle, reverse), bufP: new(bytes.Buffer), bufB: new(bytes.Buffer), bufA: new(bytes.Buffer), diff --git a/example_test.go b/example_test.go index 2f4f9d3..b25928d 100644 --- a/example_test.go +++ b/example_test.go @@ -1,29 +1,25 @@ package mpb_test import ( + crand "crypto/rand" "io" "io/ioutil" "math/rand" - "net/http" "time" - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" ) func Example() { - p := mpb.New( - // override default (80) width - mpb.WithWidth(64), - // override default 120ms refresh rate - mpb.WithRefreshRate(180*time.Millisecond), - ) + // initialize progress container, with custom width + p := mpb.New(mpb.WithWidth(64)) total := 100 name := "Single Bar:" - // adding a single bar + // adding a single bar, which will inherit container's width bar := p.AddBar(int64(total), - // override default "[=>-]" style + // override DefaultBarStyle, which is "[=>-]<+" mpb.BarStyle("╢▌▌░╟"), mpb.PrependDecorators( // display our name with one space on the right @@ -39,10 +35,13 @@ // simulating some work max := 100 * time.Millisecond for i := 0; i < total; i++ { + // start variable is solely for EWMA calculation + // EWMA's unit of measure is an iteration's duration start := time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) + bar.Increment() + // we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract + bar.DecoratorEwmaUpdate(time.Since(start)) } // wait for our bar to complete and flush p.Wait() @@ -62,23 +61,24 @@ } func ExampleBar_ProxyReader() { + // import crand "crypto/rand" + + var total int64 = 1024 * 1024 * 500 + reader := io.LimitReader(crand.Reader, total) + p := mpb.New() - // make http get request, ignoring errors - resp, _ := http.Get("https://homebrew.bintray.com/bottles/libtiff-4.0.7.sierra.bottle.tar.gz") - defer resp.Body.Close() - - // Assuming ContentLength > 0 - bar := p.AddBar(resp.ContentLength, + bar := p.AddBar(total, mpb.AppendDecorators( - decor.CountersKibiByte("%6.1f / %6.1f"), + decor.CountersKibiByte("% .2f / % .2f"), ), ) // create proxy reader - reader := bar.ProxyReader(resp.Body) + proxyReader := bar.ProxyReader(reader) + defer proxyReader.Close() // and copy from reader, ignoring errors - io.Copy(ioutil.Discard, reader) + io.Copy(ioutil.Discard, proxyReader) p.Wait() } diff --git a/examples/barNewLineExtend/main.go b/examples/barNewLineExtend/main.go deleted file mode 100644 index 08a6515..0000000 --- a/examples/barNewLineExtend/main.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "fmt" - "io" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - var wg sync.WaitGroup - p := mpb.New(mpb.WithWaitGroup(&wg)) - total, numBars := 100, 3 - wg.Add(numBars) - - for i := 0; i < numBars; i++ { - name := fmt.Sprintf("Bar#%d:", i) - efn := func(w io.Writer, s *decor.Statistics) { - if s.Completed { - fmt.Fprintf(w, "Bar id: %d has been completed\n", s.ID) - } - } - bar := p.AddBar(int64(total), mpb.BarNewLineExtend(efn), - mpb.PrependDecorators( - // simple name decorator - decor.Name(name), - // decor.DSyncWidth bit enables column width synchronization - decor.Percentage(decor.WCSyncSpace), - ), - mpb.AppendDecorators( - // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", - ), - ), - ) - // simulating some work - go func() { - defer wg.Done() - max := 100 * time.Millisecond - for i := 0; i < total; i++ { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) - } - }() - } - // wait for all bars to complete and flush - p.Wait() -} diff --git a/examples/cancel/main.go b/examples/cancel/main.go deleted file mode 100644 index 9da8ed3..0000000 --- a/examples/cancel/main.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "context" - "fmt" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - var wg sync.WaitGroup - p := mpb.New( - mpb.WithWaitGroup(&wg), - mpb.WithContext(ctx), - ) - total := 300 - numBars := 3 - wg.Add(numBars) - - for i := 0; i < numBars; i++ { - name := fmt.Sprintf("Bar#%d:", i) - bar := p.AddBar(int64(total), - mpb.PrependDecorators( - decor.Name(name), - decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), - ), - mpb.AppendDecorators( - decor.Percentage(decor.WC{W: 5}), - ), - ) - - go func() { - defer wg.Done() - max := 100 * time.Millisecond - for !bar.Completed() { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) - } - }() - } - - p.Wait() -} diff --git a/examples/complex/main.go b/examples/complex/main.go deleted file mode 100644 index c610e10..0000000 --- a/examples/complex/main.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - doneWg := new(sync.WaitGroup) - p := mpb.New(mpb.WithWidth(64), mpb.WithWaitGroup(doneWg)) - numBars := 4 - - var bars []*mpb.Bar - var downloadWgg []*sync.WaitGroup - for i := 0; i < numBars; i++ { - wg := new(sync.WaitGroup) - wg.Add(1) - downloadWgg = append(downloadWgg, wg) - task := fmt.Sprintf("Task#%02d:", i) - job := "downloading" - b := p.AddBar(rand.Int63n(201)+100, - mpb.BarRemoveOnComplete(), - mpb.PrependDecorators( - decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), - decor.Name(job, decor.WCSyncSpaceR), - decor.CountersNoUnit("%d / %d", decor.WCSyncWidth), - ), - mpb.AppendDecorators(decor.Percentage(decor.WC{W: 5})), - ) - go newTask(wg, b, i+1) - bars = append(bars, b) - } - - for i := 0; i < numBars; i++ { - doneWg.Add(1) - i := i - go func() { - task := fmt.Sprintf("Task#%02d:", i) - job := "installing" - // preparing delayed bars - b := p.AddBar(rand.Int63n(101)+100, - mpb.BarReplaceOnComplete(bars[i]), - mpb.BarClearOnComplete(), - mpb.PrependDecorators( - decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), - decor.OnComplete(decor.Name(job, decor.WCSyncSpaceR), "done!"), - decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_MMSS, 60, decor.WCSyncWidth), "")), - mpb.AppendDecorators( - decor.OnComplete(decor.Percentage(decor.WC{W: 5}), ""), - ), - ) - // waiting for download to complete, before starting install job - downloadWgg[i].Wait() - go newTask(doneWg, b, numBars-i) - }() - } - - p.Wait() -} - -func newTask(wg *sync.WaitGroup, b *mpb.Bar, incrBy int) { - defer wg.Done() - max := 100 * time.Millisecond - for !b.Completed() { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - b.IncrBy(incrBy, time.Since(start)) - } -} diff --git a/examples/differentWidth/main.go b/examples/differentWidth/main.go deleted file mode 100644 index c327b34..0000000 --- a/examples/differentWidth/main.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - var wg sync.WaitGroup - p := mpb.New( - mpb.WithWaitGroup(&wg), - // container's width. - mpb.WithWidth(60), - ) - total, numBars := 100, 3 - wg.Add(numBars) - - for i := 0; i < numBars; i++ { - name := fmt.Sprintf("Bar#%d:", i) - bar := p.AddBar(int64(total), - // set BarWidth 40 for bar 1 and 2 - mpb.OptionOnCondition(mpb.BarWidth(40), func() bool { return i > 0 }), - mpb.PrependDecorators( - // simple name decorator - decor.Name(name), - // decor.DSyncWidth bit enables column width synchronization - decor.Percentage(decor.WCSyncSpace), - ), - mpb.AppendDecorators( - // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", - ), - ), - ) - // simulating some work - go func() { - defer wg.Done() - max := 100 * time.Millisecond - for i := 0; i < total; i++ { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) - } - }() - } - // wait for all bars to complete and flush - p.Wait() -} diff --git a/examples/dynTotal/main.go b/examples/dynTotal/main.go deleted file mode 100644 index 18313c9..0000000 --- a/examples/dynTotal/main.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "io" - "math/rand" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - p := mpb.New(mpb.WithWidth(64)) - - var total int64 - bar := p.AddBar(total, - mpb.PrependDecorators(decor.Counters(decor.UnitKiB, "% .1f / % .1f")), - mpb.AppendDecorators(decor.Percentage()), - ) - - maxSleep := 100 * time.Millisecond - read := makeStream(200) - for { - n, err := read() - total += int64(n) - time.Sleep(time.Duration(rand.Intn(10)+1) * maxSleep / 10) - bar.IncrBy(n) - if err == io.EOF { - // total is known, final=true - bar.SetTotal(total, true) - break - } - // total is unknown, final=false - bar.SetTotal(total+2048, false) - } - - p.Wait() -} - -func makeStream(limit int) func() (int, error) { - return func() (int, error) { - if limit <= 0 { - return 0, io.EOF - } - limit-- - return rand.Intn(1024) + 1, nil - } -} diff --git a/examples/gifs/godEMrCZmJkHYH1X9dN4Nm0U7.svg b/examples/gifs/godEMrCZmJkHYH1X9dN4Nm0U7.svg deleted file mode 100644 index f794408..0000000 --- a/examples/gifs/godEMrCZmJkHYH1X9dN4Nm0U7.svg +++ /dev/null @@ -1 +0,0 @@ -~/go/src/github.com/vbauerster/mpb/examples/dynTotal~/go/src/github.com/vbauerster/mpb/examples/dynTotalmaster*gorun-racemain.gogorun-racemain.gogorun-racemain.go55.7KiB/56.7KiB[============================================================>-]98%100.7KiB/100.7KiB[==============================================================]100%~/go/src/github.com/vbauerster/mpb/examples/dynTotalmaster*13s~/go/src/github.com/vbauerster/mpb/examples/dynTotalmaster~/go/src/github.com/vbauerster/mpb/examples/dynTotalmastergorun-racemain.gogorun-racemain.gogorun-racemain.gogorun-racemain.go519b/1.5KiB[====================>-----------------------------------------]34%2.2KiB/3.2KiB[==========================================>-------------------]69%2.5KiB/3.5KiB[===========================================>------------------]72%3.2KiB/4.2KiB[==============================================>---------------]76%3.6KiB/4.6KiB[================================================>-------------]78%6.5KiB/7.5KiB[=====================================================>--------]87%8.0KiB/9.0KiB[======================================================>-------]89%10.6KiB/11.6KiB[========================================================>-----]91%12.1KiB/13.1KiB[========================================================>-----]92%13.1KiB/14.1KiB[=========================================================>----]93%13.2KiB/14.2KiB[=========================================================>----]93%13.9KiB/14.9KiB[=========================================================>----]93%15.5KiB/16.5KiB[=========================================================>----]94%15.8KiB/16.8KiB[=========================================================>----]94%17.6KiB/18.6KiB[==========================================================>---]95%18.9KiB/19.9KiB[==========================================================>---]95%20.0KiB/21.0KiB[==========================================================>---]95%21.0KiB/22.0KiB[==========================================================>---]95%21.3KiB/22.3KiB[==========================================================>---]96%22.6KiB/23.6KiB[==========================================================>---]96%23.9KiB/24.9KiB[===========================================================>--]96%25.7KiB/26.7KiB[===========================================================>--]96%26.1KiB/27.1KiB[===========================================================>--]96%26.8KiB/27.8KiB[===========================================================>--]96%28.0KiB/29.0KiB[===========================================================>--]97%28.4KiB/29.4KiB[===========================================================>--]97%29.6KiB/30.6KiB[===========================================================>--]97%30.0KiB/31.0KiB[===========================================================>--]97%30.4KiB/31.4KiB[===========================================================>--]97%31.8KiB/32.8KiB[===========================================================>--]97%34.4KiB/35.4KiB[===========================================================>--]97%34.5KiB/35.5KiB[===========================================================>--]97%37.0KiB/38.0KiB[===========================================================>--]97%38.5KiB/39.5KiB[===========================================================>--]97%40.4KiB/41.4KiB[============================================================>-]98%41.1KiB/42.1KiB[============================================================>-]98%42.2KiB/43.2KiB[============================================================>-]98%43.9KiB/44.9KiB[============================================================>-]98%44.9KiB/45.9KiB[============================================================>-]98%46.2KiB/47.2KiB[============================================================>-]98%46.9KiB/47.9KiB[============================================================>-]98%48.4KiB/49.4KiB[============================================================>-]98%48.7KiB/49.7KiB[============================================================>-]98%49.3KiB/50.3KiB[============================================================>-]98%50.1KiB/51.1KiB[============================================================>-]98%50.5KiB/51.5KiB[============================================================>-]98%50.6KiB/51.6KiB[============================================================>-]98%50.8KiB/51.8KiB[============================================================>-]98%51.7KiB/52.7KiB[============================================================>-]98%52.7KiB/53.7KiB[============================================================>-]98%53.6KiB/54.6KiB[============================================================>-]98%57.5KiB/58.5KiB[============================================================>-]98%58.3KiB/59.3KiB[============================================================>-]98%58.7KiB/59.7KiB[============================================================>-]98%60.1KiB/61.1KiB[============================================================>-]98%62.0KiB/63.0KiB[============================================================>-]98%63.7KiB/64.7KiB[============================================================>-]98%64.7KiB/65.7KiB[============================================================>-]98%65.2KiB/66.2KiB[============================================================>-]98%65.8KiB/66.8KiB[============================================================>-]99%66.4KiB/67.4KiB[============================================================>-]99%67.6KiB/68.6KiB[============================================================>-]99%68.5KiB/69.5KiB[============================================================>-]99%70.0KiB/71.0KiB[============================================================>-]99%70.4KiB/71.4KiB[============================================================>-]99%70.8KiB/71.8KiB[============================================================>-]99%72.3KiB/73.3KiB[============================================================>-]99%73.1KiB/74.1KiB[============================================================>-]99%74.4KiB/75.4KiB[============================================================>-]99%75.7KiB/76.7KiB[============================================================>-]99%78.2KiB/79.2KiB[============================================================>-]99%79.3KiB/80.3KiB[============================================================>-]99%80.1KiB/81.1KiB[============================================================>-]99%81.3KiB/82.3KiB[============================================================>-]99%82.3KiB/83.3KiB[============================================================>-]99%82.6KiB/83.6KiB[============================================================>-]99%84.0KiB/85.0KiB[============================================================>-]99%84.7KiB/85.7KiB[============================================================>-]99%85.9KiB/86.9KiB[============================================================>-]99%87.7KiB/88.7KiB[============================================================>-]99%88.8KiB/89.8KiB[============================================================>-]99%90.3KiB/91.3KiB[============================================================>-]99%91.6KiB/92.6KiB[============================================================>-]99%92.8KiB/93.8KiB[============================================================>-]99%93.5KiB/94.5KiB[============================================================>-]99%93.6KiB/94.6KiB[============================================================>-]99%95.1KiB/96.1KiB[============================================================>-]99%96.4KiB/97.4KiB[============================================================>-]99%97.6KiB/98.6KiB[============================================================>-]99%98.8KiB/99.8KiB[============================================================>-]99%99.7KiB/100.7KiB[============================================================>-]99%100.6KiB/101.6KiB[============================================================>-]99%100.7KiB/101.7KiB[============================================================>-]99% \ No newline at end of file diff --git a/examples/gifs/hIpTa3A5rQz65ssiVuRJu87X6.svg b/examples/gifs/hIpTa3A5rQz65ssiVuRJu87X6.svg deleted file mode 100644 index 163921c..0000000 --- a/examples/gifs/hIpTa3A5rQz65ssiVuRJu87X6.svg +++ /dev/null @@ -1 +0,0 @@ -~/go/src/github.com/vbauerster/mpb/examples/io/single~/go/src/github.com/vbauerster/mpb/examples/io/singlemaster*gorun-racemain.gogorun-racemain.gogorun-racemain.go40.6MiB/40.6MiB[==========================================================|00:00]7.54MiB/s~/go/src/github.com/vbauerster/mpb/examples/io/singlemaster*46s~/go/src/github.com/vbauerster/mpb/examples/io/singlemastergitclean-fdxgorun-racemain.gogorun-racemain.gogorun-racemain.go16.6KiB/40.6MiB[----------------------------------------------------------|00:00]0b/s66.5KiB/40.6MiB[----------------------------------------------------------|00:00]0b/s134.5KiB/40.6MiB[----------------------------------------------------------|09:01]6.18MiB/s339.6KiB/40.6MiB[----------------------------------------------------------|08:18]6.41MiB/s577.6KiB/40.6MiB[>---------------------------------------------------------|07:32]7.05MiB/s866.6KiB/40.6MiB[>---------------------------------------------------------|06:43]7.36MiB/s1.5MiB/40.6MiB[=>--------------------------------------------------------|05:12]8.12MiB/s1.8MiB/40.6MiB[==>-------------------------------------------------------|04:28]8.48MiB/s2.4MiB/40.6MiB[==>-------------------------------------------------------|03:29]16.62MiB/s2.9MiB/40.6MiB[===>------------------------------------------------------|02:52]15.37MiB/s3.5MiB/40.6MiB[====>-----------------------------------------------------|02:17]14.22MiB/s3.9MiB/40.6MiB[=====>----------------------------------------------------|01:59]13.18MiB/s4.3MiB/40.6MiB[=====>----------------------------------------------------|01:40]12.42MiB/s4.9MiB/40.6MiB[======>---------------------------------------------------|01:19]18.55MiB/s5.2MiB/40.6MiB[======>---------------------------------------------------|01:11]17.39MiB/s5.5MiB/40.6MiB[=======>--------------------------------------------------|01:03]16.23MiB/s5.6MiB/40.6MiB[=======>--------------------------------------------------|01:02]16.04MiB/s6.2MiB/40.6MiB[========>-------------------------------------------------|00:50]17.91MiB/s6.5MiB/40.6MiB[========>-------------------------------------------------|00:47]16.80MiB/s6.7MiB/40.6MiB[=========>------------------------------------------------|00:45]15.85MiB/s7.0MiB/40.6MiB[=========>------------------------------------------------|00:41]14.66MiB/s7.3MiB/40.6MiB[=========>------------------------------------------------|00:38]17.03MiB/s7.5MiB/40.6MiB[==========>-----------------------------------------------|00:39]16.26MiB/s7.7MiB/40.6MiB[==========>-----------------------------------------------|00:38]15.16MiB/s8.0MiB/40.6MiB[==========>-----------------------------------------------|00:36]14.16MiB/s8.1MiB/40.6MiB[===========>----------------------------------------------|00:35]13.56MiB/s8.4MiB/40.6MiB[===========>----------------------------------------------|00:36]13.08MiB/s8.6MiB/40.6MiB[===========>----------------------------------------------|00:34]12.51MiB/s8.8MiB/40.6MiB[============>---------------------------------------------|00:34]12.05MiB/s9.0MiB/40.6MiB[============>---------------------------------------------|00:35]11.56MiB/s9.3MiB/40.6MiB[============>---------------------------------------------|00:32]11.12MiB/s9.5MiB/40.6MiB[=============>--------------------------------------------|00:31]10.75MiB/s9.7MiB/40.6MiB[=============>--------------------------------------------|00:30]10.23MiB/s10.0MiB/40.6MiB[=============>--------------------------------------------|00:29]9.89MiB/s10.2MiB/40.6MiB[==============>-------------------------------------------|00:30]9.54MiB/s10.4MiB/40.6MiB[==============>-------------------------------------------|00:28]9.34MiB/s10.7MiB/40.6MiB[==============>-------------------------------------------|00:27]9.04MiB/s10.9MiB/40.6MiB[===============>------------------------------------------|00:26]8.79MiB/s11.0MiB/40.6MiB[===============>------------------------------------------|00:26]8.75MiB/s11.3MiB/40.6MiB[===============>------------------------------------------|00:25]11.13MiB/s11.5MiB/40.6MiB[===============>------------------------------------------|00:24]10.81MiB/s11.7MiB/40.6MiB[================>-----------------------------------------|00:24]10.45MiB/s11.9MiB/40.6MiB[================>-----------------------------------------|00:25]10.00MiB/s12.1MiB/40.6MiB[================>-----------------------------------------|00:25]9.63MiB/s12.3MiB/40.6MiB[=================>----------------------------------------|00:24]9.26MiB/s12.4MiB/40.6MiB[=================>----------------------------------------|00:24]9.14MiB/s12.6MiB/40.6MiB[=================>----------------------------------------|00:23]8.79MiB/s12.8MiB/40.6MiB[=================>----------------------------------------|00:23]10.07MiB/s12.9MiB/40.6MiB[=================>----------------------------------------|00:23]9.96MiB/s13.1MiB/40.6MiB[==================>---------------------------------------|00:22]9.80MiB/s13.2MiB/40.6MiB[==================>---------------------------------------|00:22]9.71MiB/s13.4MiB/40.6MiB[==================>---------------------------------------|00:22]9.61MiB/s13.6MiB/40.6MiB[==================>---------------------------------------|00:25]9.36MiB/s13.7MiB/40.6MiB[===================>--------------------------------------|00:24]9.19MiB/s13.9MiB/40.6MiB[===================>--------------------------------------|00:24]8.93MiB/s14.0MiB/40.6MiB[===================>--------------------------------------|00:24]8.65MiB/s14.2MiB/40.6MiB[===================>--------------------------------------|00:24]8.48MiB/s14.4MiB/40.6MiB[====================>-------------------------------------|00:23]8.32MiB/s14.5MiB/40.6MiB[====================>-------------------------------------|00:23]8.20MiB/s14.7MiB/40.6MiB[====================>-------------------------------------|00:22]8.12MiB/s14.8MiB/40.6MiB[====================>-------------------------------------|00:22]7.97MiB/s15.0MiB/40.6MiB[====================>-------------------------------------|00:22]7.88MiB/s15.2MiB/40.6MiB[=====================>------------------------------------|00:21]7.74MiB/s15.3MiB/40.6MiB[=====================>------------------------------------|00:21]7.73MiB/s15.4MiB/40.6MiB[=====================>------------------------------------|00:20]9.07MiB/s15.6MiB/40.6MiB[=====================>------------------------------------|00:20]8.84MiB/s15.7MiB/40.6MiB[=====================>------------------------------------|00:20]8.72MiB/s15.9MiB/40.6MiB[======================>-----------------------------------|00:20]8.45MiB/s16.0MiB/40.6MiB[======================>-----------------------------------|00:20]8.27MiB/s16.1MiB/40.6MiB[======================>-----------------------------------|00:22]8.05MiB/s16.3MiB/40.6MiB[======================>-----------------------------------|00:21]7.88MiB/s16.4MiB/40.6MiB[======================>-----------------------------------|00:21]7.69MiB/s16.6MiB/40.6MiB[=======================>----------------------------------|00:21]7.60MiB/s16.7MiB/40.6MiB[=======================>----------------------------------|00:22]7.49MiB/s16.9MiB/40.6MiB[=======================>----------------------------------|00:22]7.41MiB/s17.0MiB/40.6MiB[=======================>----------------------------------|00:21]7.43MiB/s17.2MiB/40.6MiB[========================>---------------------------------|00:21]7.38MiB/s17.4MiB/40.6MiB[========================>---------------------------------|00:23]7.23MiB/s17.5MiB/40.6MiB[========================>---------------------------------|00:23]7.15MiB/s17.7MiB/40.6MiB[========================>---------------------------------|00:24]6.99MiB/s17.9MiB/40.6MiB[=========================>--------------------------------|00:25]6.90MiB/s18.0MiB/40.6MiB[=========================>--------------------------------|00:24]6.83MiB/s18.2MiB/40.6MiB[=========================>--------------------------------|00:23]8.15MiB/s18.3MiB/40.6MiB[=========================>--------------------------------|00:24]7.99MiB/s18.4MiB/40.6MiB[=========================>--------------------------------|00:24]7.88MiB/s18.6MiB/40.6MiB[==========================>-------------------------------|00:24]7.62MiB/s18.7MiB/40.6MiB[==========================>-------------------------------|00:23]7.58MiB/s18.8MiB/40.6MiB[==========================>-------------------------------|00:23]7.47MiB/s19.0MiB/40.6MiB[==========================>-------------------------------|00:22]7.39MiB/s19.1MiB/40.6MiB[==========================>-------------------------------|00:22]7.33MiB/s19.2MiB/40.6MiB[===========================>------------------------------|00:22]8.48MiB/s19.4MiB/40.6MiB[===========================>------------------------------|00:25]8.27MiB/s19.5MiB/40.6MiB[===========================>------------------------------|00:25]8.13MiB/s19.6MiB/40.6MiB[===========================>------------------------------|00:25]7.89MiB/s19.7MiB/40.6MiB[===========================>------------------------------|00:25]7.67MiB/s19.8MiB/40.6MiB[===========================>------------------------------|00:24]7.52MiB/s20.0MiB/40.6MiB[============================>-----------------------------|00:24]7.42MiB/s20.1MiB/40.6MiB[============================>-----------------------------|00:24]7.25MiB/s20.2MiB/40.6MiB[============================>-----------------------------|00:23]7.12MiB/s20.3MiB/40.6MiB[============================>-----------------------------|00:23]6.94MiB/s20.5MiB/40.6MiB[============================>-----------------------------|00:24]6.75MiB/s20.6MiB/40.6MiB[============================>-----------------------------|00:24]6.59MiB/s20.7MiB/40.6MiB[=============================>----------------------------|00:27]6.57MiB/s20.8MiB/40.6MiB[=============================>----------------------------|00:27]6.45MiB/s21.0MiB/40.6MiB[=============================>----------------------------|00:28]6.40MiB/s21.1MiB/40.6MiB[=============================>----------------------------|00:30]6.36MiB/s21.2MiB/40.6MiB[=============================>----------------------------|00:29]6.28MiB/s21.3MiB/40.6MiB[=============================>----------------------------|00:28]6.22MiB/s21.4MiB/40.6MiB[==============================>---------------------------|00:27]6.19MiB/s21.5MiB/40.6MiB[==============================>---------------------------|00:29]6.10MiB/s21.7MiB/40.6MiB[==============================>---------------------------|00:28]6.09MiB/s21.8MiB/40.6MiB[==============================>---------------------------|00:27]6.04MiB/s21.9MiB/40.6MiB[==============================>---------------------------|00:27]5.88MiB/s22.0MiB/40.6MiB[===============================>--------------------------|00:27]5.83MiB/s22.2MiB/40.6MiB[===============================>--------------------------|00:26]5.78MiB/s22.3MiB/40.6MiB[===============================>--------------------------|00:25]5.80MiB/s22.4MiB/40.6MiB[===============================>--------------------------|00:24]5.76MiB/s22.5MiB/40.6MiB[===============================>--------------------------|00:24]5.72MiB/s22.7MiB/40.6MiB[===============================>--------------------------|00:23]5.70MiB/s22.8MiB/40.6MiB[================================>-------------------------|00:24]5.60MiB/s22.8MiB/40.6MiB[================================>-------------------------|00:24]5.58MiB/s23.0MiB/40.6MiB[================================>-------------------------|00:25]6.35MiB/s23.1MiB/40.6MiB[================================>-------------------------|00:25]6.33MiB/s23.2MiB/40.6MiB[================================>-------------------------|00:24]6.33MiB/s23.4MiB/40.6MiB[================================>-------------------------|00:24]6.31MiB/s23.5MiB/40.6MiB[=================================>------------------------|00:23]6.28MiB/s23.6MiB/40.6MiB[=================================>------------------------|00:23]6.20MiB/s23.7MiB/40.6MiB[=================================>------------------------|00:22]6.26MiB/s23.8MiB/40.6MiB[=================================>------------------------|00:22]6.26MiB/s23.9MiB/40.6MiB[=================================>------------------------|00:21]6.31MiB/s24.0MiB/40.6MiB[=================================>------------------------|00:21]6.26MiB/s24.2MiB/40.6MiB[==================================>-----------------------|00:22]6.25MiB/s24.3MiB/40.6MiB[==================================>-----------------------|00:25]6.20MiB/s24.4MiB/40.6MiB[==================================>-----------------------|00:24]6.20MiB/s24.5MiB/40.6MiB[==================================>-----------------------|00:23]6.14MiB/s24.7MiB/40.6MiB[==================================>-----------------------|00:24]6.16MiB/s24.8MiB/40.6MiB[==================================>-----------------------|00:24]6.13MiB/s24.9MiB/40.6MiB[===================================>----------------------|00:23]6.14MiB/s25.0MiB/40.6MiB[===================================>----------------------|00:23]6.17MiB/s25.1MiB/40.6MiB[===================================>----------------------|00:23]6.16MiB/s25.3MiB/40.6MiB[===================================>----------------------|00:22]6.05MiB/s25.4MiB/40.6MiB[===================================>----------------------|00:22]6.14MiB/s25.5MiB/40.6MiB[===================================>----------------------|00:21]6.06MiB/s25.6MiB/40.6MiB[====================================>---------------------|00:22]6.04MiB/s25.8MiB/40.6MiB[====================================>---------------------|00:21]6.02MiB/s25.9MiB/40.6MiB[====================================>---------------------|00:22]5.96MiB/s26.0MiB/40.6MiB[====================================>---------------------|00:24]6.05MiB/s26.1MiB/40.6MiB[====================================>---------------------|00:23]6.03MiB/s26.2MiB/40.6MiB[=====================================>--------------------|00:22]7.18MiB/s26.3MiB/40.6MiB[=====================================>--------------------|00:22]7.10MiB/s26.4MiB/40.6MiB[=====================================>--------------------|00:21]7.09MiB/s26.5MiB/40.6MiB[=====================================>--------------------|00:21]7.09MiB/s26.7MiB/40.6MiB[=====================================>--------------------|00:21]7.07MiB/s26.8MiB/40.6MiB[=====================================>--------------------|00:20]7.02MiB/s26.8MiB/40.6MiB[=====================================>--------------------|00:20]6.92MiB/s27.0MiB/40.6MiB[======================================>-------------------|00:19]6.89MiB/s27.1MiB/40.6MiB[======================================>-------------------|00:19]6.99MiB/s27.2MiB/40.6MiB[======================================>-------------------|00:18]6.98MiB/s27.3MiB/40.6MiB[======================================>-------------------|00:18]6.99MiB/s27.5MiB/40.6MiB[======================================>-------------------|00:20]6.98MiB/s27.5MiB/40.6MiB[======================================>-------------------|00:20]6.94MiB/s27.7MiB/40.6MiB[=======================================>------------------|00:20]7.98MiB/s27.8MiB/40.6MiB[=======================================>------------------|00:19]7.88MiB/s27.8MiB/40.6MiB[=======================================>------------------|00:19]7.81MiB/s28.0MiB/40.6MiB[=======================================>------------------|00:18]7.71MiB/s28.0MiB/40.6MiB[=======================================>------------------|00:18]7.58MiB/s28.1MiB/40.6MiB[=======================================>------------------|00:18]7.44MiB/s28.2MiB/40.6MiB[=======================================>------------------|00:18]7.38MiB/s28.3MiB/40.6MiB[========================================>-----------------|00:17]7.33MiB/s28.4MiB/40.6MiB[========================================>-----------------|00:17]7.19MiB/s28.5MiB/40.6MiB[========================================>-----------------|00:18]7.17MiB/s28.6MiB/40.6MiB[========================================>-----------------|00:17]7.17MiB/s28.7MiB/40.6MiB[========================================>-----------------|00:20]7.11MiB/s28.8MiB/40.6MiB[========================================>-----------------|00:19]7.03MiB/s28.9MiB/40.6MiB[========================================>-----------------|00:20]6.89MiB/s29.0MiB/40.6MiB[=========================================>----------------|00:20]6.79MiB/s29.1MiB/40.6MiB[=========================================>----------------|00:19]6.72MiB/s29.2MiB/40.6MiB[=========================================>----------------|00:21]6.60MiB/s29.4MiB/40.6MiB[=========================================>----------------|00:20]6.50MiB/s29.4MiB/40.6MiB[=========================================>----------------|00:20]6.48MiB/s29.5MiB/40.6MiB[=========================================>----------------|00:20]6.44MiB/s29.7MiB/40.6MiB[=========================================>----------------|00:19]6.37MiB/s29.7MiB/40.6MiB[==========================================>---------------|00:19]6.31MiB/s29.8MiB/40.6MiB[==========================================>---------------|00:21]6.31MiB/s29.9MiB/40.6MiB[==========================================>---------------|00:20]6.34MiB/s30.0MiB/40.6MiB[==========================================>---------------|00:20]6.32MiB/s30.2MiB/40.6MiB[==========================================>---------------|00:19]6.22MiB/s30.2MiB/40.6MiB[==========================================>---------------|00:19]6.18MiB/s30.3MiB/40.6MiB[==========================================>---------------|00:19]6.16MiB/s30.5MiB/40.6MiB[===========================================>--------------|00:19]6.21MiB/s30.6MiB/40.6MiB[===========================================>--------------|00:18]6.19MiB/s30.7MiB/40.6MiB[===========================================>--------------|00:17]6.18MiB/s30.8MiB/40.6MiB[===========================================>--------------|00:17]6.19MiB/s30.9MiB/40.6MiB[===========================================>--------------|00:17]5.99MiB/s31.0MiB/40.6MiB[===========================================>--------------|00:17]5.89MiB/s31.1MiB/40.6MiB[============================================>-------------|00:16]5.92MiB/s31.3MiB/40.6MiB[============================================>-------------|00:15]5.80MiB/s31.4MiB/40.6MiB[============================================>-------------|00:15]5.85MiB/s31.5MiB/40.6MiB[============================================>-------------|00:14]5.87MiB/s31.7MiB/40.6MiB[============================================>-------------|00:13]5.93MiB/s31.9MiB/40.6MiB[=============================================>------------|00:13]5.95MiB/s32.0MiB/40.6MiB[=============================================>------------|00:12]5.98MiB/s32.2MiB/40.6MiB[=============================================>------------|00:11]5.99MiB/s32.4MiB/40.6MiB[=============================================>------------|00:11]6.04MiB/s32.5MiB/40.6MiB[=============================================>------------|00:10]6.08MiB/s32.7MiB/40.6MiB[==============================================>-----------|00:10]6.03MiB/s32.9MiB/40.6MiB[==============================================>-----------|00:09]6.05MiB/s33.1MiB/40.6MiB[==============================================>-----------|00:09]6.08MiB/s33.3MiB/40.6MiB[===============================================>----------|00:08]6.13MiB/s33.6MiB/40.6MiB[===============================================>----------|00:07]6.30MiB/s33.8MiB/40.6MiB[===============================================>----------|00:07]6.33MiB/s34.0MiB/40.6MiB[================================================>---------|00:07]6.44MiB/s34.3MiB/40.6MiB[================================================>---------|00:06]6.88MiB/s34.6MiB/40.6MiB[================================================>---------|00:05]6.88MiB/s34.8MiB/40.6MiB[=================================================>--------|00:05]6.88MiB/s35.2MiB/40.6MiB[=================================================>--------|00:04]10.50MiB/s35.4MiB/40.6MiB[==================================================>-------|00:04]10.17MiB/s35.6MiB/40.6MiB[==================================================>-------|00:03]9.88MiB/s35.9MiB/40.6MiB[==================================================>-------|00:03]9.36MiB/s36.2MiB/40.6MiB[===================================================>------|00:03]9.27MiB/s36.4MiB/40.6MiB[===================================================>------|00:02]9.08MiB/s36.7MiB/40.6MiB[===================================================>------|00:02]8.83MiB/s37.0MiB/40.6MiB[====================================================>-----|00:02]8.64MiB/s37.2MiB/40.6MiB[====================================================>-----|00:02]8.48MiB/s37.5MiB/40.6MiB[=====================================================>----|00:02]8.26MiB/s37.9MiB/40.6MiB[=====================================================>----|00:02]8.16MiB/s38.1MiB/40.6MiB[=====================================================>----|00:01]8.21MiB/s38.4MiB/40.6MiB[======================================================>---|00:01]8.38MiB/s38.7MiB/40.6MiB[======================================================>---|00:01]8.31MiB/s39.0MiB/40.6MiB[=======================================================>--|00:01]8.40MiB/s39.3MiB/40.6MiB[=======================================================>--|00:01]8.21MiB/s39.6MiB/40.6MiB[========================================================>-|00:00]8.14MiB/s40.0MiB/40.6MiB[========================================================>-|00:00]7.97MiB/s40.2MiB/40.6MiB[==========================================================|00:00]7.75MiB/s \ No newline at end of file diff --git a/examples/gifs/wHzf1M7sd7B3zVa2scBMnjqRf.svg b/examples/gifs/wHzf1M7sd7B3zVa2scBMnjqRf.svg deleted file mode 100644 index 9e3b871..0000000 --- a/examples/gifs/wHzf1M7sd7B3zVa2scBMnjqRf.svg +++ /dev/null @@ -1 +0,0 @@ -~/go/src/github.com/vbauerster/mpb/examples/complex~/go/src/github.com/vbauerster/mpb/examples/complexmaster*gorun-racemain.gogorun-racemain.gogorun-racemain.goTask#03:installing00:08[======>-------------------------------------------------------]11%Task#03:installing00:07[==============>-----------------------------------------------]24%Task#02:done!Task#03:done!Task#01:done!Task#00:done!~/go/src/github.com/vbauerster/mpb/examples/complexmaster*19s~/go/src/github.com/vbauerster/mpb/examples/complexmastergorun-racemain.gogorun-racemain.gogorun-racemain.gogorun-racemain.goTask#00:downloading4/268[>-------------------------------------------------------------]1%Task#01:downloading2/274[--------------------------------------------------------------]1%Task#02:downloading3/114[=>------------------------------------------------------------]3%Task#03:downloading4/114[=>------------------------------------------------------------]4%Task#00:downloading6/268[>-------------------------------------------------------------]2%Task#01:downloading8/274[=>------------------------------------------------------------]3%Task#02:downloading9/114[====>---------------------------------------------------------]8%Task#03:downloading12/114[======>-------------------------------------------------------]11%Task#00:downloading9/268[=>------------------------------------------------------------]3%Task#01:downloading12/274[==>-----------------------------------------------------------]4%Task#02:downloading12/114[======>-------------------------------------------------------]11%Task#03:downloading24/114[============>-------------------------------------------------]21%Task#00:downloading11/268[==>-----------------------------------------------------------]4%Task#01:downloading16/274[===>----------------------------------------------------------]6%Task#02:downloading18/114[=========>----------------------------------------------------]16%Task#03:downloading36/114[===================>------------------------------------------]32%Task#00:downloading13/268[==>-----------------------------------------------------------]5%Task#01:downloading22/274[====>---------------------------------------------------------]8%Task#02:downloading27/114[==============>-----------------------------------------------]24%Task#03:downloading40/114[=====================>----------------------------------------]35%Task#00:downloading15/268[==>-----------------------------------------------------------]6%Task#01:downloading24/274[====>---------------------------------------------------------]9%Task#02:downloading30/114[===============>----------------------------------------------]26%Task#03:downloading48/114[=========================>------------------------------------]42%Task#00:downloading16/268[===>----------------------------------------------------------]6%Task#01:downloading30/274[======>-------------------------------------------------------]11%Task#02:downloading39/114[====================>-----------------------------------------]34%Task#03:downloading56/114[=============================>--------------------------------]49%Task#00:downloading18/268[===>----------------------------------------------------------]7%Task#01:downloading34/274[=======>------------------------------------------------------]12%Task#02:downloading42/114[======================>---------------------------------------]37%Task#03:downloading64/114[==================================>---------------------------]56%Task#00:downloading19/268[===>----------------------------------------------------------]7%Task#01:downloading40/274[========>-----------------------------------------------------]15%Task#02:downloading45/114[=======================>--------------------------------------]39%Task#03:downloading68/114[====================================>-------------------------]60%Task#00:downloading21/268[====>---------------------------------------------------------]8%Task#01:downloading44/274[=========>----------------------------------------------------]16%Task#02:downloading54/114[============================>---------------------------------]47%Task#03:downloading76/114[========================================>---------------------]67%Task#00:downloading25/268[=====>--------------------------------------------------------]9%Task#01:downloading52/274[===========>--------------------------------------------------]19%Task#02:downloading60/114[================================>-----------------------------]53%Task#03:downloading80/114[===========================================>------------------]70%Task#00:downloading27/268[=====>--------------------------------------------------------]10%Task#01:downloading54/274[===========>--------------------------------------------------]20%Task#02:downloading63/114[=================================>----------------------------]55%Task#03:downloading88/114[===============================================>--------------]77%Task#00:downloading29/268[======>-------------------------------------------------------]11%Task#01:downloading58/274[============>-------------------------------------------------]21%Task#02:downloading69/114[=====================================>------------------------]61%Task#03:downloading92/114[=================================================>------------]81%Task#00:downloading30/268[======>-------------------------------------------------------]11%Task#01:downloading60/274[=============>------------------------------------------------]22%Task#02:downloading75/114[========================================>---------------------]66%Task#03:downloading100/114[=====================================================>--------]88%Task#00:downloading32/268[======>-------------------------------------------------------]12%Task#01:downloading66/274[==============>-----------------------------------------------]24%Task#02:downloading78/114[=========================================>--------------------]68%Task#03:downloading108/114[==========================================================>---]95%Task#00:downloading34/268[=======>------------------------------------------------------]13%Task#01:downloading70/274[===============>----------------------------------------------]26%Task#02:downloading84/114[=============================================>----------------]74%Task#03:downloading114/114[==============================================================]100%Task#00:downloading35/268[=======>------------------------------------------------------]13%Task#01:downloading74/274[================>---------------------------------------------]27%Task#02:downloading90/114[================================================>-------------]79%Task#03:installing00:00[--------------------------------------------------------------]1%Task#00:downloading37/268[========>-----------------------------------------------------]14%Task#01:downloading76/274[================>---------------------------------------------]28%Task#02:downloading96/114[===================================================>----------]84%Task#03:installing00:00[=>------------------------------------------------------------]2%Task#00:downloading40/268[========>-----------------------------------------------------]15%Task#01:downloading78/274[=================>--------------------------------------------]28%Task#02:downloading102/114[======================================================>-------]89%Task#03:installing00:00[=>------------------------------------------------------------]4%Task#00:downloading42/268[=========>----------------------------------------------------]16%Task#01:downloading82/274[==================>-------------------------------------------]30%Task#02:downloading108/114[==========================================================>---]95%Task#03:installing00:00[==>-----------------------------------------------------------]4%Task#00:downloading44/268[=========>----------------------------------------------------]16%Task#01:downloading86/274[==================>-------------------------------------------]31%Task#02:downloading114/114[==============================================================]100%Task#03:installing00:00[==>-----------------------------------------------------------]6%Task#00:downloading47/268[==========>---------------------------------------------------]18%Task#01:downloading88/274[===================>------------------------------------------]32%Task#02:installing00:00[=>------------------------------------------------------------]3%Task#03:installing00:09[====>---------------------------------------------------------]7%Task#00:downloading50/268[===========>--------------------------------------------------]19%Task#01:downloading92/274[====================>-----------------------------------------]34%Task#02:installing00:00[===>----------------------------------------------------------]6%Task#03:installing00:09[====>---------------------------------------------------------]9%Task#00:downloading52/268[===========>--------------------------------------------------]19%Task#01:downloading96/274[=====================>----------------------------------------]35%Task#02:installing00:00[=====>--------------------------------------------------------]9%Task#00:downloading54/268[===========>--------------------------------------------------]20%Task#01:downloading98/274[=====================>----------------------------------------]36%Task#02:installing00:03[========>-----------------------------------------------------]14%Task#00:downloading56/268[============>-------------------------------------------------]21%Task#01:downloading102/274[======================>---------------------------------------]37%Task#02:installing00:03[=========>----------------------------------------------------]17%Task#03:installing00:08[=======>------------------------------------------------------]12%Task#00:downloading57/268[============>-------------------------------------------------]21%Task#01:downloading106/274[=======================>--------------------------------------]39%Task#02:installing00:03[===========>--------------------------------------------------]19%Task#03:installing00:08[=======>------------------------------------------------------]14%Task#00:downloading59/268[=============>------------------------------------------------]22%Task#01:downloading114/274[=========================>------------------------------------]42%Task#02:installing00:03[=============>------------------------------------------------]23%Task#03:installing00:08[========>-----------------------------------------------------]14%Task#00:downloading61/268[=============>------------------------------------------------]23%Task#01:downloading120/274[==========================>-----------------------------------]44%Task#02:installing00:03[===============>----------------------------------------------]25%Task#03:installing00:08[=========>----------------------------------------------------]16%Task#00:downloading63/268[==============>-----------------------------------------------]24%Task#01:downloading126/274[============================>---------------------------------]46%Task#02:installing00:02[=================>--------------------------------------------]29%Task#03:installing00:08[=========>----------------------------------------------------]17%Task#00:downloading67/268[===============>----------------------------------------------]25%Task#01:downloading130/274[============================>---------------------------------]47%Task#02:installing00:02[==================>-------------------------------------------]31%Task#03:installing00:08[==========>---------------------------------------------------]17%Task#00:downloading68/268[===============>----------------------------------------------]25%Task#01:downloading132/274[=============================>--------------------------------]48%Task#02:installing00:02[=====================>----------------------------------------]36%Task#03:installing00:08[===========>--------------------------------------------------]19%Task#00:downloading69/268[===============>----------------------------------------------]26%Task#01:downloading136/274[==============================>-------------------------------]50%Task#02:installing00:02[======================>---------------------------------------]37%Task#03:installing00:08[============>-------------------------------------------------]20%Task#00:downloading71/268[===============>----------------------------------------------]26%Task#01:downloading140/274[===============================>------------------------------]51%Task#02:installing00:02[========================>-------------------------------------]41%Task#03:installing00:07[============>-------------------------------------------------]22%Task#00:downloading73/268[================>---------------------------------------------]27%Task#01:downloading144/274[================================>-----------------------------]53%Task#02:installing00:02[=========================>------------------------------------]42%Task#00:downloading75/268[================>---------------------------------------------]28%Task#01:downloading150/274[=================================>----------------------------]55%Task#02:installing00:02[===========================>----------------------------------]45%Task#00:downloading78/268[=================>--------------------------------------------]29%Task#01:downloading154/274[==================================>---------------------------]56%Task#02:installing00:02[============================>---------------------------------]47%Task#03:installing00:07[===============>----------------------------------------------]26%Task#00:downloading80/268[==================>-------------------------------------------]30%Task#01:downloading158/274[===================================>--------------------------]58%Task#02:installing00:02[==============================>-------------------------------]50%Task#03:installing00:07[================>---------------------------------------------]27%Task#00:downloading82/268[==================>-------------------------------------------]31%Task#01:downloading162/274[====================================>-------------------------]59%Task#02:installing00:02[===============================>------------------------------]51%Task#03:installing00:06[=================>--------------------------------------------]29%Task#00:downloading87/268[===================>------------------------------------------]32%Task#01:downloading164/274[====================================>-------------------------]60%Task#02:installing00:01[==================================>---------------------------]56%Task#03:installing00:06[==================>-------------------------------------------]31%Task#00:downloading90/268[====================>-----------------------------------------]34%Task#01:downloading166/274[=====================================>------------------------]61%Task#02:installing00:01[====================================>-------------------------]60%Task#03:installing00:06[===================>------------------------------------------]32%Task#00:downloading92/268[====================>-----------------------------------------]34%Task#01:downloading172/274[======================================>-----------------------]63%Task#02:installing00:01[======================================>-----------------------]62%Task#03:installing00:05[====================>-----------------------------------------]34%Task#00:downloading93/268[=====================>----------------------------------------]35%Task#01:downloading176/274[=======================================>----------------------]64%Task#02:installing00:01[=======================================>----------------------]65%Task#03:installing00:05[=====================>----------------------------------------]35%Task#00:downloading96/268[=====================>----------------------------------------]36%Task#01:downloading178/274[=======================================>----------------------]65%Task#02:installing00:01[==========================================>-------------------]69%Task#03:installing00:05[=====================>----------------------------------------]36%Task#00:downloading99/268[======================>---------------------------------------]37%Task#01:downloading182/274[========================================>---------------------]66%Task#02:installing00:01[===========================================>------------------]71%Task#03:installing00:05[======================>---------------------------------------]37%Task#00:downloading102/268[=======================>--------------------------------------]38%Task#01:downloading186/274[=========================================>--------------------]68%Task#02:installing00:00[==============================================>---------------]75%Task#03:installing00:06[======================>---------------------------------------]37%Task#00:downloading105/268[=======================>--------------------------------------]39%Task#01:downloading188/274[==========================================>-------------------]69%Task#02:installing00:00[===============================================>--------------]78%Task#03:installing00:06[=======================>--------------------------------------]39%Task#00:downloading107/268[========================>-------------------------------------]40%Task#01:downloading192/274[==========================================>-------------------]70%Task#02:installing00:00[=================================================>------------]80%Task#03:installing00:05[========================>-------------------------------------]40%Task#00:downloading109/268[========================>-------------------------------------]41%Task#01:downloading194/274[===========================================>------------------]71%Task#02:installing00:00[==================================================>-----------]82%Task#03:installing00:05[========================>-------------------------------------]41%Task#00:downloading112/268[=========================>------------------------------------]42%Task#01:downloading198/274[============================================>-----------------]72%Task#02:installing00:00[===================================================>----------]84%Task#03:installing00:05[=========================>------------------------------------]42%Task#00:downloading114/268[=========================>------------------------------------]43%Task#01:downloading202/274[=============================================>----------------]74%Task#02:installing00:00[=====================================================>--------]88%Task#03:installing00:05[==========================>-----------------------------------]44%Task#00:downloading116/268[==========================>-----------------------------------]43%Task#01:downloading206/274[==============================================>---------------]75%Task#02:installing00:00[========================================================>-----]92%Task#03:installing00:04[===========================>----------------------------------]46%Task#00:downloading119/268[===========================>----------------------------------]44%Task#01:downloading210/274[===============================================>--------------]77%Task#02:installing00:00[=========================================================>----]94%Task#03:installing00:04[=============================>--------------------------------]48%Task#00:downloading122/268[===========================>----------------------------------]46%Task#01:downloading212/274[===============================================>--------------]77%Task#02:installing00:00[===========================================================>--]97%Task#03:installing00:04[=============================>--------------------------------]49%Task#00:downloading124/268[============================>---------------------------------]46%Task#01:downloading214/274[===============================================>--------------]78%Task#02:installing00:00[==============================================================]99%Task#03:installing00:04[==============================>-------------------------------]50%Task#00:downloading126/268[============================>---------------------------------]47%Task#01:downloading218/274[================================================>-------------]80%Task#02:installing00:00[==============================================================]100%Task#03:installing00:04[===============================>------------------------------]52%Task#00:downloading127/268[============================>---------------------------------]47%Task#01:downloading220/274[=================================================>------------]80%Task#03:installing00:04[================================>-----------------------------]53%Task#00:downloading130/268[=============================>--------------------------------]49%Task#01:downloading224/274[==================================================>-----------]82%Task#03:installing00:03[=================================>----------------------------]55%Task#00:downloading132/268[==============================>-------------------------------]49%Task#01:downloading230/274[===================================================>----------]84%Task#03:installing00:03[==================================>---------------------------]57%Task#00:downloading134/268[==============================>-------------------------------]50%Task#01:downloading234/274[====================================================>---------]85%Task#03:installing00:03[====================================>-------------------------]59%Task#00:downloading136/268[==============================>-------------------------------]51%Task#01:downloading238/274[=====================================================>--------]87%Task#03:installing00:03[====================================>-------------------------]60%Task#00:downloading139/268[===============================>------------------------------]52%Task#01:downloading242/274[======================================================>-------]88%Task#03:installing00:03[=====================================>------------------------]61%Task#00:downloading141/268[================================>-----------------------------]53%Task#01:downloading246/274[=======================================================>------]90%Task#03:installing00:03[======================================>-----------------------]63%Task#00:downloading143/268[================================>-----------------------------]53%Task#01:downloading254/274[========================================================>-----]93%Task#03:installing00:02[=======================================>----------------------]65%Task#00:downloading147/268[=================================>----------------------------]55%Task#01:downloading258/274[=========================================================>----]94%Task#03:installing00:02[========================================>---------------------]66%Task#00:downloading149/268[=================================>----------------------------]56%Task#01:downloading262/274[==========================================================>---]96%Task#03:installing00:02[=========================================>--------------------]68%Task#00:downloading150/268[==================================>---------------------------]56%Task#01:downloading268/274[============================================================>-]98%Task#03:installing00:02[==========================================>-------------------]69%Task#00:downloading152/268[==================================>---------------------------]57%Task#01:downloading272/274[==============================================================]99%Task#03:installing00:02[===========================================>------------------]70%Task#00:downloading156/268[===================================>--------------------------]58%Task#01:downloading274/274[==============================================================]100%Task#03:installing00:02[===========================================>------------------]71%Task#00:downloading160/268[====================================>-------------------------]60%Task#01:installing00:00[==>-----------------------------------------------------------]5%Task#03:installing00:02[============================================>-----------------]73%Task#00:downloading162/268[====================================>-------------------------]60%Task#01:installing00:00[====>---------------------------------------------------------]8%Task#03:installing00:02[=============================================>----------------]74%Task#00:downloading163/268[=====================================>------------------------]61%Task#01:installing00:00[=======>------------------------------------------------------]14%Task#03:installing00:02[=============================================>----------------]75%Task#00:downloading168/268[======================================>-----------------------]63%Task#01:installing00:00[==========>---------------------------------------------------]17%Task#03:installing00:02[==============================================>---------------]76%Task#00:downloading170/268[======================================>-----------------------]63%Task#01:installing00:02[============>-------------------------------------------------]20%Task#03:installing00:02[===============================================>--------------]77%Task#00:downloading173/268[=======================================>----------------------]65%Task#01:installing00:02[===============>----------------------------------------------]25%Task#03:installing00:01[================================================>-------------]78%Task#00:downloading176/268[========================================>---------------------]66%Task#01:installing00:02[==================>-------------------------------------------]31%Task#03:installing00:01[================================================>-------------]79%Task#00:downloading178/268[========================================>---------------------]66%Task#01:installing00:01[=====================>----------------------------------------]36%Task#03:installing00:01[=================================================>------------]81%Task#00:downloading180/268[=========================================>--------------------]67%Task#01:installing00:01[======================>---------------------------------------]37%Task#03:installing00:01[==================================================>-----------]83%Task#00:downloading182/268[=========================================>--------------------]68%Task#01:installing00:01[========================>-------------------------------------]41%Task#03:installing00:01[===================================================>----------]83%Task#00:downloading185/268[==========================================>-------------------]69%Task#01:installing00:01[=========================>------------------------------------]42%Task#03:installing00:01[===================================================>----------]84%Task#00:downloading188/268[==========================================>-------------------]70%Task#01:installing00:01[===========================>----------------------------------]46%Task#03:installing00:01[====================================================>---------]85%Task#00:downloading190/268[===========================================>------------------]71%Task#01:installing00:01[=============================>--------------------------------]49%Task#03:installing00:01[=====================================================>--------]87%Task#00:downloading192/268[===========================================>------------------]72%Task#01:installing00:01[================================>-----------------------------]53%Task#03:installing00:01[======================================================>-------]88%Task#00:downloading194/268[============================================>-----------------]72%Task#01:installing00:01[=================================>----------------------------]54%Task#03:installing00:00[======================================================>-------]89%Task#00:downloading197/268[=============================================>----------------]74%Task#01:installing00:01[===================================>--------------------------]58%Task#03:installing00:00[=======================================================>------]91%Task#00:downloading198/268[=============================================>----------------]74%Task#01:installing00:01[=====================================>------------------------]61%Task#03:installing00:00[========================================================>-----]93%Task#00:downloading202/268[==============================================>---------------]75%Task#01:installing00:01[======================================>-----------------------]63%Task#03:installing00:00[=========================================================>----]93%Task#00:downloading204/268[==============================================>---------------]76%Task#01:installing00:01[========================================>---------------------]66%Task#03:installing00:00[==========================================================>---]94%Task#00:downloading206/268[===============================================>--------------]77%Task#01:installing00:01[==========================================>-------------------]69%Task#03:installing00:00[===========================================================>--]97%Task#00:downloading209/268[===============================================>--------------]78%Task#01:installing00:00[============================================>-----------------]73%Task#03:installing00:00[===========================================================>--]98%Task#00:downloading212/268[================================================>-------------]79%Task#01:installing00:00[==============================================>---------------]76%Task#03:installing00:00[============================================================>-]99%Task#00:downloading213/268[================================================>-------------]79%Task#01:installing00:00[================================================>-------------]80%Task#03:installing00:00[==============================================================]100%Task#00:downloading215/268[=================================================>------------]80%Task#01:installing00:00[=================================================>------------]81%Task#00:downloading217/268[=================================================>------------]81%Task#01:installing00:00[=====================================================>--------]86%Task#00:downloading219/268[==================================================>-----------]82%Task#01:installing00:00[======================================================>-------]88%Task#00:downloading220/268[==================================================>-----------]82%Task#01:installing00:00[========================================================>-----]92%Task#00:downloading221/268[==================================================>-----------]82%Task#01:installing00:00[=========================================================>----]93%Task#00:downloading225/268[===================================================>----------]84%Task#01:installing00:00[===========================================================>--]97%Task#00:downloading227/268[====================================================>---------]85%Task#01:installing00:00[============================================================>-]98%Task#00:downloading229/268[====================================================>---------]85%Task#01:installing00:00[==============================================================]100%Task#00:downloading230/268[====================================================>---------]86%Task#00:downloading232/268[=====================================================>--------]87%Task#00:downloading233/268[=====================================================>--------]87%Task#00:downloading235/268[=====================================================>--------]88%Task#00:downloading236/268[======================================================>-------]88%Task#00:downloading237/268[======================================================>-------]88%Task#00:downloading240/268[=======================================================>------]90%Task#00:downloading241/268[=======================================================>------]90%Task#00:downloading244/268[=======================================================>------]91%Task#00:downloading246/268[========================================================>-----]92%Task#00:downloading249/268[=========================================================>----]93%Task#00:downloading251/268[=========================================================>----]94%Task#00:downloading253/268[==========================================================>---]94%Task#00:downloading256/268[==========================================================>---]96%Task#00:downloading257/268[==========================================================>---]96%Task#00:downloading259/268[===========================================================>--]97%Task#00:downloading261/268[===========================================================>--]97%Task#00:downloading262/268[============================================================>-]98%Task#00:downloading263/268[============================================================>-]98%Task#00:downloading266/268[==============================================================]99%Task#00:downloading268/268[==============================================================]100%Task#00:installing00:00[=>------------------------------------------------------------]3%Task#00:installing00:00[======>-------------------------------------------------------]12%Task#00:installing00:00[========>-----------------------------------------------------]14%Task#00:installing00:00[============>-------------------------------------------------]20%Task#00:installing00:00[=================>--------------------------------------------]29%Task#00:installing00:01[===================>------------------------------------------]32%Task#00:installing00:01[==========================>-----------------------------------]43%Task#00:installing00:01[==============================>-------------------------------]49%Task#00:installing00:00[=================================>----------------------------]55%Task#00:installing00:00[=====================================>------------------------]61%Task#00:installing00:00[========================================>---------------------]67%Task#00:installing00:00[============================================>-----------------]72%Task#00:installing00:00[=================================================>------------]81%Task#00:installing00:00[=====================================================>--------]87%Task#00:installing00:00[=========================================================>----]93%Task#00:installing00:00[============================================================>-]99%Task#00:installing00:00[==============================================================]100% \ No newline at end of file diff --git a/examples/io/multiple/main.go b/examples/io/multiple/main.go deleted file mode 100644 index 8a02e16..0000000 --- a/examples/io/multiple/main.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "fmt" - "io" - "log" - "net/http" - "os" - "path/filepath" - "sync" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func main() { - log.SetOutput(os.Stderr) - - url1 := "https://homebrew.bintray.com/bottles/youtube-dl-2016.12.12.sierra.bottle.tar.gz" - url2 := "https://homebrew.bintray.com/bottles/libtiff-4.0.7.sierra.bottle.tar.gz" - - var wg sync.WaitGroup - p := mpb.New(mpb.WithWidth(64), mpb.WithWaitGroup(&wg)) - - for i, url := range [...]string{url1, url2} { - wg.Add(1) - name := fmt.Sprintf("url%d:", i+1) - go download(&wg, p, name, url, i) - } - - p.Wait() -} - -func download(wg *sync.WaitGroup, p *mpb.Progress, name, url string, n int) { - defer wg.Done() - resp, err := http.Get(url) - if err != nil { - log.Printf("%s: %v", name, err) - return - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - err = fmt.Errorf("non-200 status: %s", resp.Status) - log.Printf("%s: %v", name, err) - return - } - - size := resp.ContentLength - - // create dest - destName := filepath.Base(url) - dest, err := os.Create(destName) - if err != nil { - err = fmt.Errorf("Can't create %s: %v", destName, err) - log.Printf("%s: %v", name, err) - return - } - - // create bar with appropriate decorators - bar := p.AddBar(size, mpb.BarPriority(n), - mpb.PrependDecorators( - decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), - decor.CountersKibiByte("%6.1f / %6.1f", decor.WCSyncWidth), - ), - mpb.AppendDecorators( - decor.EwmaETA(decor.ET_STYLE_HHMMSS, 1024*4, decor.WCSyncWidth), - decor.AverageSpeed(decor.UnitKiB, "% .2f"), - ), - ) - - // create proxy reader - reader := bar.ProxyReader(resp.Body) - // and copy from reader - _, err = io.Copy(dest, reader) - - if e := dest.Close(); err == nil { - err = e - } - if err != nil { - log.Printf("%s: %v", name, err) - } -} diff --git a/examples/io/single/main.go b/examples/io/single/main.go deleted file mode 100644 index 09d6c11..0000000 --- a/examples/io/single/main.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func main() { - url := "https://github.com/onivim/oni/releases/download/v0.3.4/Oni-0.3.4-amd64-linux.deb" - - resp, err := http.Get(url) - if err != nil { - panic(err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - fmt.Printf("Server return non-200 status: %s\n", resp.Status) - return - } - - size := resp.ContentLength - - // create dest - destName := filepath.Base(url) - dest, err := os.Create(destName) - if err != nil { - fmt.Printf("Can't create %s: %v\n", destName, err) - return - } - defer dest.Close() - - p := mpb.New( - mpb.WithWidth(60), - mpb.WithRefreshRate(180*time.Millisecond), - ) - - bar := p.AddBar(size, mpb.BarStyle("[=>-|"), - mpb.PrependDecorators( - decor.CountersKibiByte("% 6.1f / % 6.1f"), - ), - mpb.AppendDecorators( - decor.EwmaETA(decor.ET_STYLE_MMSS, float64(size)/2048), - decor.Name(" ] "), - decor.AverageSpeed(decor.UnitKiB, "% .2f"), - ), - ) - - // create proxy reader - reader := bar.ProxyReader(resp.Body) - - // and copy from reader, ignoring errors - io.Copy(dest, reader) - - p.Wait() -} diff --git a/examples/panic/main.go b/examples/panic/main.go deleted file mode 100644 index b951fe6..0000000 --- a/examples/panic/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - "os" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func main() { - var wg sync.WaitGroup - p := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithDebugOutput(os.Stderr)) - - wantPanic := "Some really long panic panic panic panic panic panic panic, really it is very long" - numBars := 3 - wg.Add(numBars) - - for i := 0; i < numBars; i++ { - name := fmt.Sprintf("b#%02d:", i) - bar := p.AddBar(100, mpb.BarID(i), mpb.PrependDecorators(panicDecorator(name, wantPanic))) - - go func() { - defer wg.Done() - for i := 0; i < 100; i++ { - time.Sleep(50 * time.Millisecond) - bar.Increment() - } - }() - } - - p.Wait() -} - -func panicDecorator(name, panicMsg string) decor.Decorator { - d := &decorator{ - msg: name, - panicMsg: panicMsg, - } - d.Init() - return d -} - -type decorator struct { - decor.WC - msg string - panicMsg string -} - -func (d *decorator) Decor(st *decor.Statistics) string { - if st.ID == 1 && st.Current >= 42 { - panic(d.panicMsg) - } - return d.FormatMsg(d.msg) -} diff --git a/examples/remove/main.go b/examples/remove/main.go deleted file mode 100644 index 308978f..0000000 --- a/examples/remove/main.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - var wg sync.WaitGroup - p := mpb.New(mpb.WithWaitGroup(&wg)) - total := 100 - numBars := 3 - wg.Add(numBars) - - for i := 0; i < numBars; i++ { - name := fmt.Sprintf("Bar#%d:", i) - b := p.AddBar(int64(total), mpb.BarID(i), - mpb.OptionOnCondition(mpb.BarRemoveOnComplete(), func() bool { return i == 0 }), - mpb.PrependDecorators( - decor.Name(name), - decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), - ), - mpb.AppendDecorators(decor.Percentage()), - ) - go func() { - defer wg.Done() - max := 100 * time.Millisecond - for i := 0; i < total; i++ { - start := time.Now() - if b.ID() == 2 && i == 42 { - p.Abort(b, true) - return - } - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - b.IncrBy(1, time.Since(start)) - } - }() - } - - p.Wait() -} diff --git a/examples/simple/main.go b/examples/simple/main.go deleted file mode 100644 index afc2050..0000000 --- a/examples/simple/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - var wg sync.WaitGroup - p := mpb.New(mpb.WithWaitGroup(&wg)) - total, numBars := 100, 3 - wg.Add(numBars) - - for i := 0; i < numBars; i++ { - name := fmt.Sprintf("Bar#%d:", i) - bar := p.AddBar(int64(total), - mpb.PrependDecorators( - // simple name decorator - decor.Name(name), - // decor.DSyncWidth bit enables column width synchronization - decor.Percentage(decor.WCSyncSpace), - ), - mpb.AppendDecorators( - // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", - ), - ), - ) - // simulating some work - go func() { - defer wg.Done() - max := 100 * time.Millisecond - for i := 0; i < total; i++ { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) - } - }() - } - // wait for all bars to complete and flush - p.Wait() -} diff --git a/examples/singleBar/main.go b/examples/singleBar/main.go deleted file mode 100644 index c712afb..0000000 --- a/examples/singleBar/main.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "math/rand" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func main() { - p := mpb.New( - // override default (80) width - mpb.WithWidth(64), - // override default 120ms refresh rate - mpb.WithRefreshRate(180*time.Millisecond), - ) - - total := 100 - name := "Single Bar:" - // adding a single bar - bar := p.AddBar(int64(total), - // override default "[=>-]" style - mpb.BarStyle("╢▌▌░╟"), - mpb.PrependDecorators( - // display our name with one space on the right - decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), - // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - // ETA decorator with ewma age of 60, and width reservation of 4 - decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WC{W: 4}), "done", - ), - ), - mpb.AppendDecorators(decor.Percentage()), - ) - // simulating some work - max := 100 * time.Millisecond - for i := 0; i < total; i++ { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) - } - // wait for our bar to complete and flush - p.Wait() -} diff --git a/examples/sort/main.go b/examples/sort/main.go deleted file mode 100644 index 3dd979c..0000000 --- a/examples/sort/main.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - var wg sync.WaitGroup - p := mpb.New(mpb.WithWaitGroup(&wg)) - total := 100 - numBars := 3 - wg.Add(numBars) - - for i := 0; i < numBars; i++ { - var name string - if i != 1 { - name = fmt.Sprintf("Bar#%d:", i) - } - b := p.AddBar(int64(total), - mpb.PrependDecorators( - decor.Name(name, decor.WCSyncWidth), - decor.CountersNoUnit("%d / %d", decor.WCSyncSpace), - ), - mpb.AppendDecorators( - decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WC{W: 3}), - ), - ) - go func() { - defer wg.Done() - max := 100 * time.Millisecond - for i := 0; i < total; i++ { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - if i&1 == 1 { - priority := total - int(b.Current()) - p.UpdateBarPriority(b, priority) - } - // ewma based decorators require work duration measurement - b.IncrBy(1, time.Since(start)) - } - }() - } - - p.Wait() -} diff --git a/examples/spinner/main.go b/examples/spinner/main.go deleted file mode 100644 index 1f81897..0000000 --- a/examples/spinner/main.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - var wg sync.WaitGroup - p := mpb.New( - mpb.WithWaitGroup(&wg), - mpb.WithWidth(13), - ) - total, numBars := 101, 3 - wg.Add(numBars) - - for i := 0; i < numBars; i++ { - name := fmt.Sprintf("Bar#%d:", i) - var bar *mpb.Bar - if i == 0 { - bar = p.AddBar(int64(total), - // override default "[=>-]" style - mpb.BarStyle("╢▌▌░╟"), - mpb.PrependDecorators( - // simple name decorator - decor.Name(name), - ), - mpb.AppendDecorators( - // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", - ), - ), - ) - } else { - bar = p.AddSpinner(int64(total), mpb.SpinnerOnMiddle, - // override default {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} style - mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}), - mpb.PrependDecorators( - // simple name decorator - decor.Name(name), - ), - mpb.AppendDecorators( - // replace ETA decorator with "done" message, OnComplete event - decor.OnComplete( - // ETA decorator with ewma age of 60 - decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", - ), - ), - ) - } - - // simulating some work - go func() { - defer wg.Done() - max := 100 * time.Millisecond - for i := 0; i < total; i++ { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) - } - }() - } - // wait for all bars to complete and flush - p.Wait() -} diff --git a/examples/stress/main.go b/examples/stress/main.go deleted file mode 100644 index 09d4e55..0000000 --- a/examples/stress/main.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "sync" - "time" - - "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/decor" -) - -const ( - totalBars = 32 -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - var wg sync.WaitGroup - p := mpb.New( - mpb.WithWaitGroup(&wg), - mpb.WithRefreshRate(50*time.Millisecond), - ) - wg.Add(totalBars) - - for i := 0; i < totalBars; i++ { - name := fmt.Sprintf("Bar#%02d: ", i) - total := rand.Intn(320) + 10 - bar := p.AddBar(int64(total), - mpb.PrependDecorators( - decor.Name(name), - decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), - ), - mpb.AppendDecorators( - decor.Percentage(decor.WC{W: 5}), - ), - ) - - go func() { - defer wg.Done() - max := 100 * time.Millisecond - for !bar.Completed() { - start := time.Now() - time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // ewma based decorators require work duration measurement - bar.IncrBy(1, time.Since(start)) - } - }() - } - - p.Wait() -} diff --git a/export_test.go b/export_test.go index 1d05eda..7f5cb84 100644 --- a/export_test.go +++ b/export_test.go @@ -1,6 +1,4 @@ package mpb -var ( - SyncWidth = syncWidth - DefaultBarStyle = defaultBarStyle -) +// make syncWidth func public in test +var SyncWidth = syncWidth diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..672191f --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/vbauerster/mpb/v5 + +require ( + github.com/VividCortex/ewma v1.1.1 + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d + golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 + golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect +) + +go 1.14 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9a41197 --- /dev/null +++ b/go.sum @@ -0,0 +1,13 @@ +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= +golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go.test.sh b/go.test.sh deleted file mode 100755 index 34dbbfb..0000000 --- a/go.test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e -echo "" > coverage.txt - -for d in $(go list ./... | grep -v vendor); do - go test -race -coverprofile=profile.out -covermode=atomic $d - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi -done diff --git a/internal/percentage.go b/internal/percentage.go index 0483d25..7e261cb 100644 --- a/internal/percentage.go +++ b/internal/percentage.go @@ -3,10 +3,13 @@ import "math" // Percentage is a helper function, to calculate percentage. -func Percentage(total, current, width int64) int64 { +func Percentage(total, current int64, width int) float64 { if total <= 0 { return 0 } - p := float64(width*current) / float64(total) - return int64(math.Round(p)) + return float64(int64(width)*current) / float64(total) } + +func PercentageRound(total, current int64, width int) float64 { + return math.Round(Percentage(total, current, width)) +} diff --git a/internal/percentage_test.go b/internal/percentage_test.go index 4a649ce..a2fa3f7 100644 --- a/internal/percentage_test.go +++ b/internal/percentage_test.go @@ -4,9 +4,11 @@ func TestPercentage(t *testing.T) { // key is barWidth - testSuite := map[int64][]struct { - name string - total, current, expected int64 + testSuite := map[int][]struct { + name string + total int64 + current int64 + expected int64 }{ 100: { {"t,c,e{-1,-1,0}", -1, -1, 0}, @@ -64,7 +66,7 @@ for width, cases := range testSuite { for _, tc := range cases { - got := Percentage(tc.total, tc.current, width) + got := int64(PercentageRound(tc.total, tc.current, width)) if got != tc.expected { t.Errorf("width %d; %s: Expected: %d, got: %d\n", width, tc.name, tc.expected, got) } diff --git a/options.go b/options.go index 44a6ee3..0488702 100644 --- a/options.go +++ b/options.go @@ -1,23 +1,21 @@ package mpb import ( - "context" "io" + "io/ioutil" "sync" "time" - - "github.com/vbauerster/mpb/cwriter" ) -// ProgressOption is a function option which changes the default -// behavior of progress pool, if passed to mpb.New(...ProgressOption). -type ProgressOption func(*pState) +// ContainerOption is a function option which changes the default +// behavior of progress container, if passed to mpb.New(...ContainerOption). +type ContainerOption func(*pState) // WithWaitGroup provides means to have a single joint point. If // *sync.WaitGroup is provided, you can safely call just p.Wait() // without calling Wait() on provided *sync.WaitGroup. Makes sense // when there are more than one bar to render. -func WithWaitGroup(wg *sync.WaitGroup) ProgressOption { +func WithWaitGroup(wg *sync.WaitGroup) ContainerOption { return func(s *pState) { s.uwg = wg } @@ -25,66 +23,83 @@ // WithWidth sets container width. Default is 80. Bars inherit this // width, as long as no BarWidth is applied. -func WithWidth(w int) ProgressOption { +func WithWidth(w int) ContainerOption { return func(s *pState) { - if w >= 0 { - s.width = w + if w < 0 { + return } + s.width = w } } // WithRefreshRate overrides default 120ms refresh rate. -func WithRefreshRate(d time.Duration) ProgressOption { +func WithRefreshRate(d time.Duration) ContainerOption { return func(s *pState) { - if d < 10*time.Millisecond { - return - } s.rr = d } } // WithManualRefresh disables internal auto refresh time.Ticker. // Refresh will occur upon receive value from provided ch. -func WithManualRefresh(ch <-chan time.Time) ProgressOption { +func WithManualRefresh(ch <-chan time.Time) ContainerOption { return func(s *pState) { - s.manualRefreshCh = ch + s.refreshSrc = ch } } -// WithContext provided context will be used for cancellation purposes. -func WithContext(ctx context.Context) ProgressOption { +// WithRenderDelay delays rendering. By default rendering starts as +// soon as bar is added, with this option it's possible to delay +// rendering process by keeping provided chan unclosed. In other words +// rendering will start as soon as provided chan is closed. +func WithRenderDelay(ch <-chan struct{}) ContainerOption { return func(s *pState) { - if ctx == nil { - return - } - s.ctx = ctx + s.renderDelay = ch } } // WithShutdownNotifier provided chanel will be closed, after all bars // have been rendered. -func WithShutdownNotifier(ch chan struct{}) ProgressOption { +func WithShutdownNotifier(ch chan struct{}) ContainerOption { return func(s *pState) { s.shutdownNotifier = ch } } -// WithOutput overrides default output os.Stdout. -func WithOutput(w io.Writer) ProgressOption { +// WithOutput overrides default os.Stdout output. Setting it to nil +// will effectively disable auto refresh rate and discard any output, +// useful if you want to disable progress bars with little overhead. +func WithOutput(w io.Writer) ContainerOption { return func(s *pState) { if w == nil { + s.refreshSrc = make(chan time.Time) + s.output = ioutil.Discard return } - s.cw = cwriter.New(w) + s.output = w } } // WithDebugOutput sets debug output. -func WithDebugOutput(w io.Writer) ProgressOption { +func WithDebugOutput(w io.Writer) ContainerOption { + if w == nil { + return nil + } return func(s *pState) { - if w == nil { - return - } s.debugOut = w } } + +// PopCompletedMode will pop and stop rendering completed bars. +func PopCompletedMode() ContainerOption { + return func(s *pState) { + s.popCompleted = true + } +} + +// ContainerOptOn returns option when condition evaluates to true. +func ContainerOptOn(option ContainerOption, condition func() bool) ContainerOption { + if condition() { + return option + } + return nil +} diff --git a/priority_queue.go b/priority_queue.go index 7bc588c..29d9bd5 100644 --- a/priority_queue.go +++ b/priority_queue.go @@ -1,6 +1,4 @@ package mpb - -import "container/heap" // A priorityQueue implements heap.Interface type priorityQueue []*Bar @@ -18,23 +16,17 @@ } func (pq *priorityQueue) Push(x interface{}) { - n := len(*pq) + s := *pq bar := x.(*Bar) - bar.index = n - *pq = append(*pq, bar) + bar.index = len(s) + s = append(s, bar) + *pq = s } func (pq *priorityQueue) Pop() interface{} { - old := *pq - n := len(old) - bar := old[n-1] + s := *pq + *pq = s[0 : len(s)-1] + bar := s[len(s)-1] bar.index = -1 // for safety - *pq = old[0 : n-1] return bar } - -// update modifies the priority of a Bar in the queue. -func (pq *priorityQueue) update(bar *Bar, priority int) { - bar.priority = priority - heap.Fix(pq, bar.index) -} diff --git a/progress.go b/progress.go index f9e25af..a366b92 100644 --- a/progress.go +++ b/progress.go @@ -1,16 +1,19 @@ package mpb import ( + "bytes" "container/heap" "context" "fmt" "io" "io/ioutil" + "log" "os" "sync" "time" - "github.com/vbauerster/mpb/cwriter" + "github.com/vbauerster/mpb/v5/cwriter" + "github.com/vbauerster/mpb/v5/decor" ) const ( @@ -22,47 +25,56 @@ // Progress represents the container that renders Progress bars type Progress struct { - wg *sync.WaitGroup + ctx context.Context uwg *sync.WaitGroup + cwg *sync.WaitGroup + bwg *sync.WaitGroup operateState chan func(*pState) done chan struct{} + refreshCh chan time.Time + once sync.Once + dlogger *log.Logger } type pState struct { - bHeap *priorityQueue - shutdownPending []*Bar - heapUpdated bool - zeroWait bool - idCounter int - width int - format string - rr time.Duration - cw *cwriter.Writer - pMatrix map[int][]chan int - aMatrix map[int][]chan int + bHeap priorityQueue + heapUpdated bool + pMatrix map[int][]chan int + aMatrix map[int][]chan int + barShutdownQueue []*Bar + barPopQueue []*Bar // following are provided/overrided by user - ctx context.Context + idCount int + width int + popCompleted bool + rr time.Duration uwg *sync.WaitGroup - manualRefreshCh <-chan time.Time + refreshSrc <-chan time.Time + renderDelay <-chan struct{} shutdownNotifier chan struct{} - waitBars map[*Bar]*Bar + parkedBars map[*Bar]*Bar + output io.Writer debugOut io.Writer } -// New creates new Progress instance, which orchestrates bars rendering -// process. Accepts mpb.ProgressOption funcs for customization. -func New(options ...ProgressOption) *Progress { - pq := make(priorityQueue, 0) - heap.Init(&pq) +// New creates new Progress container instance. It's not possible to +// reuse instance after *Progress.Wait() method has been called. +func New(options ...ContainerOption) *Progress { + return NewWithContext(context.Background(), options...) +} + +// NewWithContext creates new Progress container instance with provided +// context. It's not possible to reuse instance after *Progress.Wait() +// method has been called. +func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { s := &pState{ - ctx: context.Background(), - bHeap: &pq, - width: pwidth, - cw: cwriter.New(os.Stdout), - rr: prr, - waitBars: make(map[*Bar]*Bar), - debugOut: ioutil.Discard, + bHeap: priorityQueue{}, + width: pwidth, + rr: prr, + parkedBars: make(map[*Bar]*Bar), + output: os.Stdout, + debugOut: ioutil.Discard, } for _, opt := range options { @@ -72,81 +84,91 @@ } p := &Progress{ + ctx: ctx, uwg: s.uwg, - wg: new(sync.WaitGroup), + cwg: new(sync.WaitGroup), + bwg: new(sync.WaitGroup), operateState: make(chan func(*pState)), done: make(chan struct{}), - } - go p.serve(s) + dlogger: log.New(s.debugOut, "[mpb] ", log.Lshortfile), + } + + p.cwg.Add(1) + go p.serve(s, cwriter.New(s.output)) return p } -// AddBar creates a new progress bar and adds to the container. +// AddBar creates a new progress bar and adds it to the rendering queue. func (p *Progress) AddBar(total int64, options ...BarOption) *Bar { - return p.Add(total, newDefaultBarFiller(), options...) -} - -// AddSpinner creates a new spinner bar and adds to the container. + return p.Add(total, NewBarFiller(DefaultBarStyle, false), options...) +} + +// AddSpinner creates a new spinner bar and adds it to the rendering queue. func (p *Progress) AddSpinner(total int64, alignment SpinnerAlignment, options ...BarOption) *Bar { - filler := &spinnerFiller{ - frames: defaultSpinnerStyle, - alignment: alignment, - } - return p.Add(total, filler, options...) + return p.Add(total, NewSpinnerFiller(DefaultSpinnerStyle, alignment), options...) } // Add creates a bar which renders itself by provided filler. -func (p *Progress) Add(total int64, filler Filler, options ...BarOption) *Bar { +// Set total to 0, if you plan to update it later. +// Panics if *Progress instance is done, i.e. called after *Progress.Wait(). +func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) *Bar { if filler == nil { - filler = newDefaultBarFiller() - } - p.wg.Add(1) + filler = NewBarFiller(DefaultBarStyle, false) + } + p.bwg.Add(1) result := make(chan *Bar) select { - case p.operateState <- func(s *pState) { - b := newBar(s.ctx, p.wg, filler, s.idCounter, s.width, total, options...) - if b.runningBar != nil { - s.waitBars[b.runningBar] = b + case p.operateState <- func(ps *pState) { + bs := ps.makeBarState(total, filler, options...) + bar := newBar(p, bs) + if bs.runningBar != nil { + bs.runningBar.noPop = true + ps.parkedBars[bs.runningBar] = bar } else { - heap.Push(s.bHeap, b) - s.heapUpdated = true - } - s.idCounter++ - result <- b + heap.Push(&ps.bHeap, bar) + ps.heapUpdated = true + } + ps.idCount++ + result <- bar }: - return <-result + bar := <-result + bar.subscribeDecorators() + return bar case <-p.done: - p.wg.Done() - return nil - } -} - -// Abort is only effective while bar progress is running, it means -// remove bar now without waiting for its completion. If bar is already -// completed, there is nothing to abort. If you need to remove bar -// after completion, use BarRemoveOnComplete BarOption. -func (p *Progress) Abort(b *Bar, remove bool) { + p.bwg.Done() + panic(fmt.Sprintf("%T instance can't be reused after it's done!", p)) + } +} + +func (p *Progress) dropBar(b *Bar) { select { case p.operateState <- func(s *pState) { if b.index < 0 { return } - if remove { - s.heapUpdated = heap.Remove(s.bHeap, b.index) != nil - } - s.shutdownPending = append(s.shutdownPending, b) + heap.Remove(&s.bHeap, b.index) + s.heapUpdated = true }: case <-p.done: } } -// UpdateBarPriority provides a way to change bar's order position. -// Zero is highest priority, i.e. bar will be on top. +func (p *Progress) setBarPriority(b *Bar, priority int) { + select { + case p.operateState <- func(s *pState) { + if b.index < 0 { + return + } + b.priority = priority + heap.Fix(&s.bHeap, b.index) + }: + case <-p.done: + } +} + +// UpdateBarPriority same as *Bar.SetPriority(int). func (p *Progress) UpdateBarPriority(b *Bar, priority int) { - select { - case p.operateState <- func(s *pState) { s.bHeap.update(b, priority) }: - case <-p.done: - } + p.setBarPriority(b, priority) } // BarCount returns bars count @@ -160,29 +182,151 @@ } } -// Wait first waits for user provided *sync.WaitGroup, if any, then -// waits far all bars to complete and finally shutdowns master goroutine. +// Wait waits far all bars to complete and finally shutdowns container. // After this method has been called, there is no way to reuse *Progress // instance. func (p *Progress) Wait() { if p.uwg != nil { + // wait for user wg p.uwg.Wait() } - p.wg.Wait() - - select { - case p.operateState <- func(s *pState) { s.zeroWait = true }: - <-p.done - case <-p.done: - } + // wait for bars to quit, if any + p.bwg.Wait() + + p.once.Do(p.shutdown) + + // wait for container to quit + p.cwg.Wait() +} + +func (p *Progress) shutdown() { + close(p.done) +} + +func (p *Progress) serve(s *pState, cw *cwriter.Writer) { + defer p.cwg.Done() + + p.refreshCh = s.newTicker(p.done) + + for { + select { + case op := <-p.operateState: + op(s) + case <-p.refreshCh: + if err := s.render(cw); err != nil { + go p.dlogger.Println(err) + } + case <-s.shutdownNotifier: + return + } + } +} + +func (s *pState) render(cw *cwriter.Writer) error { + if s.heapUpdated { + s.updateSyncMatrix() + s.heapUpdated = false + } + syncWidth(s.pMatrix) + syncWidth(s.aMatrix) + + tw, err := cw.GetWidth() + if err != nil { + tw = s.width + } + for i := 0; i < s.bHeap.Len(); i++ { + bar := s.bHeap[i] + go bar.render(tw) + } + + return s.flush(cw) +} + +func (s *pState) flush(cw *cwriter.Writer) error { + var lineCount int + bm := make(map[*Bar]struct{}, s.bHeap.Len()) + for s.bHeap.Len() > 0 { + b := heap.Pop(&s.bHeap).(*Bar) + cw.ReadFrom(<-b.frameCh) + if b.toShutdown { + // shutdown at next flush + // this ensures no bar ends up with less than 100% rendered + defer func() { + s.barShutdownQueue = append(s.barShutdownQueue, b) + }() + } + lineCount += b.extendedLines + 1 + bm[b] = struct{}{} + } + + for _, b := range s.barShutdownQueue { + if parkedBar := s.parkedBars[b]; parkedBar != nil { + parkedBar.priority = b.priority + heap.Push(&s.bHeap, parkedBar) + delete(s.parkedBars, b) + b.toDrop = true + } + if b.toDrop { + delete(bm, b) + s.heapUpdated = true + } else if s.popCompleted { + if b := b; !b.noPop { + defer func() { + s.barPopQueue = append(s.barPopQueue, b) + }() + } + } + b.cancel() + } + s.barShutdownQueue = s.barShutdownQueue[0:0] + + for _, b := range s.barPopQueue { + delete(bm, b) + s.heapUpdated = true + lineCount -= b.extendedLines + 1 + } + s.barPopQueue = s.barPopQueue[0:0] + + for b := range bm { + heap.Push(&s.bHeap, b) + } + + return cw.Flush(lineCount) +} + +func (s *pState) newTicker(done <-chan struct{}) chan time.Time { + ch := make(chan time.Time) + if s.shutdownNotifier == nil { + s.shutdownNotifier = make(chan struct{}) + } + go func() { + if s.renderDelay != nil { + <-s.renderDelay + } + if s.refreshSrc == nil { + ticker := time.NewTicker(s.rr) + defer ticker.Stop() + s.refreshSrc = ticker.C + } + for { + select { + case tick := <-s.refreshSrc: + ch <- tick + case <-done: + close(s.shutdownNotifier) + return + } + } + }() + return ch } func (s *pState) updateSyncMatrix() { s.pMatrix = make(map[int][]chan int) s.aMatrix = make(map[int][]chan int) for i := 0; i < s.bHeap.Len(); i++ { - bar := (*s.bHeap)[i] + bar := s.bHeap[i] table := bar.wSyncTable() pRow, aRow := table[0], table[1] @@ -196,56 +340,35 @@ } } -func (s *pState) render(tw int) { - if s.heapUpdated { - s.updateSyncMatrix() - s.heapUpdated = false - } - syncWidth(s.pMatrix) - syncWidth(s.aMatrix) - - for i := 0; i < s.bHeap.Len(); i++ { - bar := (*s.bHeap)[i] - go bar.render(s.debugOut, tw) - } - - if err := s.flush(s.bHeap.Len()); err != nil { - fmt.Fprintf(s.debugOut, "%s %s %v\n", "[mpb]", time.Now(), err) - } -} - -func (s *pState) flush(lineCount int) error { - for s.bHeap.Len() > 0 { - bar := heap.Pop(s.bHeap).(*Bar) - frameReader := <-bar.frameReaderCh - defer func() { - if frameReader.toShutdown { - // shutdown at next flush, in other words decrement underlying WaitGroup - // only after the bar with completed state has been flushed. this - // ensures no bar ends up with less than 100% rendered. - s.shutdownPending = append(s.shutdownPending, bar) - if replacementBar, ok := s.waitBars[bar]; ok { - heap.Push(s.bHeap, replacementBar) - s.heapUpdated = true - delete(s.waitBars, bar) - } - if frameReader.removeOnComplete { - s.heapUpdated = true - return - } - } - heap.Push(s.bHeap, bar) - }() - s.cw.ReadFrom(frameReader) - lineCount += frameReader.extendedLines - } - - for i := len(s.shutdownPending) - 1; i >= 0; i-- { - close(s.shutdownPending[i].shutdown) - s.shutdownPending = s.shutdownPending[:i] - } - - return s.cw.Flush(lineCount) +func (s *pState) makeBarState(total int64, filler BarFiller, options ...BarOption) *bState { + bs := &bState{ + total: total, + baseF: extractBaseFiller(filler), + filler: filler, + priority: s.idCount, + id: s.idCount, + width: s.width, + debugOut: s.debugOut, + extender: func(r io.Reader, _ int, _ *decor.Statistics) (io.Reader, int) { + return r, 0 + }, + } + + for _, opt := range options { + if opt != nil { + opt(bs) + } + } + + if s.popCompleted && !bs.noPop { + bs.priority = -1 + } + + bs.bufP = bytes.NewBuffer(make([]byte, 0, bs.width)) + bs.bufB = bytes.NewBuffer(make([]byte, 0, bs.width)) + bs.bufA = bytes.NewBuffer(make([]byte, 0, bs.width)) + + return bs } func syncWidth(matrix map[int][]chan int) { @@ -254,8 +377,7 @@ go func() { var maxWidth int for _, ch := range column { - w := <-ch - if w > maxWidth { + if w := <-ch; w > maxWidth { maxWidth = w } } @@ -265,3 +387,13 @@ }() } } + +func extractBaseFiller(f BarFiller) BarFiller { + type wrapper interface { + Base() BarFiller + } + if f, ok := f.(wrapper); ok { + return extractBaseFiller(f.Base()) + } + return f +} diff --git a/progress_posix.go b/progress_posix.go deleted file mode 100644 index 545245a..0000000 --- a/progress_posix.go +++ /dev/null @@ -1,70 +0,0 @@ -// +build !windows - -package mpb - -import ( - "os" - "os/signal" - "syscall" - "time" -) - -func (p *Progress) serve(s *pState) { - - var ticker *time.Ticker - var refreshCh <-chan time.Time - var winch chan os.Signal - var resumeTimer *time.Timer - var resumeEvent <-chan time.Time - winchIdleDur := s.rr * 2 - - if s.manualRefreshCh == nil { - ticker = time.NewTicker(s.rr) - refreshCh = ticker.C - winch = make(chan os.Signal, 2) - signal.Notify(winch, syscall.SIGWINCH) - } else { - refreshCh = s.manualRefreshCh - } - - for { - select { - case op := <-p.operateState: - op(s) - case <-refreshCh: - if s.zeroWait { - if s.manualRefreshCh == nil { - signal.Stop(winch) - ticker.Stop() - } - if s.shutdownNotifier != nil { - close(s.shutdownNotifier) - } - close(p.done) - return - } - tw, err := s.cw.GetWidth() - if err != nil { - tw = s.width - } - s.render(tw) - case <-winch: - tw, err := s.cw.GetWidth() - if err != nil { - tw = s.width - } - s.render(tw - tw/8) - if resumeTimer != nil && resumeTimer.Reset(winchIdleDur) { - break - } - ticker.Stop() - resumeTimer = time.NewTimer(winchIdleDur) - resumeEvent = resumeTimer.C - case <-resumeEvent: - ticker = time.NewTicker(s.rr) - refreshCh = ticker.C - resumeEvent = nil - resumeTimer = nil - } - } -} diff --git a/progress_test.go b/progress_test.go index de34785..3390d2c 100644 --- a/progress_test.go +++ b/progress_test.go @@ -3,22 +3,13 @@ import ( "bytes" "context" - "fmt" "io/ioutil" "math/rand" "sync" "testing" "time" - "github.com/vbauerster/mpb" - . "github.com/vbauerster/mpb" - "github.com/vbauerster/mpb/cwriter" -) - -var ( - cursorUp = fmt.Sprintf("%c[%dA", cwriter.ESC, 1) - clearLine = fmt.Sprintf("%c[2K\r", cwriter.ESC) - clearCursorAndLine = cursorUp + clearLine + "github.com/vbauerster/mpb/v5" ) func init() { @@ -26,7 +17,7 @@ } func TestBarCount(t *testing.T) { - p := New(WithOutput(ioutil.Discard)) + p := mpb.New(mpb.WithOutput(ioutil.Discard)) var wg sync.WaitGroup wg.Add(1) @@ -47,23 +38,23 @@ t.Errorf("BarCount want: %q, got: %q\n", 1, count) } - p.Abort(b, true) + b.Abort(true) p.Wait() } func TestBarAbort(t *testing.T) { - p := New(WithOutput(ioutil.Discard)) + p := mpb.New(mpb.WithOutput(ioutil.Discard)) var wg sync.WaitGroup wg.Add(1) - bars := make([]*Bar, 3) + bars := make([]*mpb.Bar, 3) for i := 0; i < 3; i++ { b := p.AddBar(100) bars[i] = b go func(n int) { - for i := 0; i < 100; i++ { - if n == 0 && i == 33 { - p.Abort(b, true) + for i := 0; !b.Completed(); i++ { + if n == 0 && i >= 33 { + b.Abort(true) wg.Done() } b.Increment() @@ -77,17 +68,16 @@ if count != 2 { t.Errorf("BarCount want: %q, got: %q\n", 2, count) } - p.Abort(bars[1], true) - p.Abort(bars[2], true) + bars[1].Abort(true) + bars[2].Abort(true) p.Wait() } func TestWithContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) shutdown := make(chan struct{}) - p := mpb.New( + p := mpb.NewWithContext(ctx, mpb.WithOutput(ioutil.Discard), - mpb.WithContext(ctx), mpb.WithRefreshRate(50*time.Millisecond), mpb.WithShutdownNotifier(shutdown), ) @@ -119,8 +109,7 @@ func getLastLine(bb []byte) []byte { split := bytes.Split(bb, []byte("\n")) - lastLine := split[len(split)-2] - return lastLine[len(clearCursorAndLine):] + return split[len(split)-2] } func randomDuration(max time.Duration) time.Duration { diff --git a/progress_windows.go b/progress_windows.go deleted file mode 100644 index cab03d3..0000000 --- a/progress_windows.go +++ /dev/null @@ -1,43 +0,0 @@ -// +build windows - -package mpb - -import ( - "time" -) - -func (p *Progress) serve(s *pState) { - - var ticker *time.Ticker - var refreshCh <-chan time.Time - - if s.manualRefreshCh == nil { - ticker = time.NewTicker(s.rr) - refreshCh = ticker.C - } else { - refreshCh = s.manualRefreshCh - } - - for { - select { - case op := <-p.operateState: - op(s) - case <-refreshCh: - if s.zeroWait { - if s.manualRefreshCh == nil { - ticker.Stop() - } - if s.shutdownNotifier != nil { - close(s.shutdownNotifier) - } - close(p.done) - return - } - tw, err := s.cw.GetWidth() - if err != nil { - tw = s.width - } - s.render(tw) - } - } -} diff --git a/proxyreader.go b/proxyreader.go index d2692cc..316f438 100644 --- a/proxyreader.go +++ b/proxyreader.go @@ -2,21 +2,89 @@ import ( "io" + "io/ioutil" "time" ) -// proxyReader is io.Reader wrapper, for proxy read bytes type proxyReader struct { io.ReadCloser bar *Bar - iT time.Time } -func (pr *proxyReader) Read(p []byte) (n int, err error) { - n, err = pr.ReadCloser.Read(p) +func (x *proxyReader) Read(p []byte) (int, error) { + n, err := x.ReadCloser.Read(p) + x.bar.IncrBy(n) + if err == io.EOF { + go x.bar.SetTotal(0, true) + } + return n, err +} + +type proxyWriterTo struct { + io.ReadCloser // *proxyReader + wt io.WriterTo + bar *Bar +} + +func (x *proxyWriterTo) WriteTo(w io.Writer) (int64, error) { + n, err := x.wt.WriteTo(w) + x.bar.IncrInt64(n) + if err == io.EOF { + go x.bar.SetTotal(0, true) + } + return n, err +} + +type ewmaProxyReader struct { + io.ReadCloser // *proxyReader + bar *Bar + iT time.Time +} + +func (x *ewmaProxyReader) Read(p []byte) (int, error) { + n, err := x.ReadCloser.Read(p) if n > 0 { - pr.bar.IncrBy(n, time.Since(pr.iT)) - pr.iT = time.Now() + x.bar.DecoratorEwmaUpdate(time.Since(x.iT)) + x.iT = time.Now() } - return + return n, err } + +type ewmaProxyWriterTo struct { + io.ReadCloser // *ewmaProxyReader + wt io.WriterTo // *proxyWriterTo + bar *Bar + iT time.Time +} + +func (x *ewmaProxyWriterTo) WriteTo(w io.Writer) (int64, error) { + n, err := x.wt.WriteTo(w) + if n > 0 { + x.bar.DecoratorEwmaUpdate(time.Since(x.iT)) + x.iT = time.Now() + } + return n, err +} + +func newProxyReader(r io.Reader, bar *Bar) io.ReadCloser { + rc := toReadCloser(r) + rc = &proxyReader{rc, bar} + + if wt, isWriterTo := r.(io.WriterTo); bar.hasEwmaDecorators { + now := time.Now() + rc = &ewmaProxyReader{rc, bar, now} + if isWriterTo { + rc = &ewmaProxyWriterTo{rc, wt, bar, now} + } + } else if isWriterTo { + rc = &proxyWriterTo{rc, wt, bar} + } + return rc +} + +func toReadCloser(r io.Reader) io.ReadCloser { + if rc, ok := r.(io.ReadCloser); ok { + return rc + } + return ioutil.NopCloser(r) +} diff --git a/proxyreader_test.go b/proxyreader_test.go index 7006674..14bbca8 100644 --- a/proxyreader_test.go +++ b/proxyreader_test.go @@ -1,12 +1,13 @@ package mpb_test import ( + "bytes" "io" "io/ioutil" "strings" "testing" - "github.com/vbauerster/mpb" + "github.com/vbauerster/mpb/v5" ) const content = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do @@ -28,26 +29,62 @@ } func TestProxyReader(t *testing.T) { - p := mpb.New(mpb.WithOutput(ioutil.Discard)) - reader := &testReader{Reader: strings.NewReader(content)} + tReader := &testReader{strings.NewReader(content), false} - total := len(content) - bar := p.AddBar(100, mpb.TrimSpace()) + bar := p.AddBar(int64(len(content)), mpb.TrimSpace()) - written, err := io.Copy(ioutil.Discard, bar.ProxyReader(reader)) + var buf bytes.Buffer + _, err := io.Copy(&buf, bar.ProxyReader(tReader)) if err != nil { t.Errorf("Error copying from reader: %+v\n", err) } p.Wait() - if !reader.called { + if !tReader.called { t.Error("Read not called") } - if written != int64(total) { - t.Errorf("Expected written: %d, got: %d\n", total, written) + if got := buf.String(); got != content { + t.Errorf("Expected content: %s, got: %s\n", content, got) } } + +type testWriterTo struct { + io.Reader + wt io.WriterTo + called bool +} + +func (wt *testWriterTo) WriteTo(w io.Writer) (n int64, err error) { + wt.called = true + return wt.wt.WriteTo(w) +} + +func TestProxyWriterTo(t *testing.T) { + p := mpb.New(mpb.WithOutput(ioutil.Discard)) + + var reader io.Reader = strings.NewReader(content) + wt := reader.(io.WriterTo) + tReader := &testWriterTo{reader, wt, false} + + bar := p.AddBar(int64(len(content)), mpb.TrimSpace()) + + var buf bytes.Buffer + _, err := io.Copy(&buf, bar.ProxyReader(tReader)) + if err != nil { + t.Errorf("Error copying from reader: %+v\n", err) + } + + p.Wait() + + if !tReader.called { + t.Error("WriteTo not called") + } + + if got := buf.String(); got != content { + t.Errorf("Expected content: %s, got: %s\n", content, got) + } +} diff --git a/spinner_filler.go b/spinner_filler.go index 36299fe..517725f 100644 --- a/spinner_filler.go +++ b/spinner_filler.go @@ -5,7 +5,7 @@ "strings" "unicode/utf8" - "github.com/vbauerster/mpb/decor" + "github.com/vbauerster/mpb/v5/decor" ) // SpinnerAlignment enum. @@ -18,12 +18,25 @@ SpinnerOnRight ) -var defaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} +// DefaultSpinnerStyle is a slice of strings, which makes a spinner. +var DefaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} type spinnerFiller struct { frames []string count uint alignment SpinnerAlignment +} + +// NewSpinnerFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. +func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller { + if len(style) == 0 { + style = DefaultSpinnerStyle + } + filler := &spinnerFiller{ + frames: style, + alignment: alignment, + } + return filler } func (s *spinnerFiller) Fill(w io.Writer, width int, stat *decor.Statistics) {