diff --git a/README.md b/README.md new file mode 100644 index 0000000..32ceb4b --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# Multi Progress Bar for Go + +Mutex free progress bar library, for console programs. + +It is inspired by [uiprogress](https://github.com/gosuri/uiprogress) library, +but unlike the last one, implementation is mutex free, following Go's idiom: + +> Don't communicate by sharing memory, share memory by communicating. + +## Features + +* __Multiple Bars__: mpb can render multiple progress bars that can be tracked concurrently +* __Dynamic Addition__: Add additional progress bar at any time +* __Dynamic Removal__: Remove rendering progress bar at any time +* __Dynamic Sorting__: Sort bars by progression +* __Custom Decorator Functions__: Add custom functions around the bar along with helper functions +* __Predefined Decoratros__: Elapsed time, [Ewmaest](https://github.com/dgryski/trifles/tree/master/ewmaest) based ETA, Percentage, Bytes counter + +## Usage + +Following is the simplest use case: + +```go + // No need to initialize sync.WaitGroup, as it is initialized implicitly + p := mpb.New() // Star mpb container + for i := 0; i < 3; i++ { + p.Wg.Add(1) // add wg counter + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(100).PrependName(name, len(name)).AppendPercentage() + go func() { + // you can p.AddBar() here, but ordering will be non deterministic + for i := 0; i < 100; i++ { + time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) + bar.Incr(1) + } + }() + } + p.WaitAndStop() // Wait for goroutines to finish + // p.AddBar(1) // panic: you cannot reuse p, create new one! + fmt.Println("finish") +``` + +This will produce following: + +![example](example/gifs/simple.gif) + +### Removing bar + +![example](example/gifs/remove.gif) + +The source code: [example/remove/main.go](example/remove/main.go) + +### Sorting bars by progress + +![example](example/gifs/sort.gif) + +The source code: [example/sort/main.go](example/sort/main.go) + +### Multiple io + +![example](example/gifs/io-multiple.gif) + +The source code: [example/io/multiple/main.go](example/io/multiple/main.go) + +## Installation + +```sh +$ go get -u github.com/vbauerster/mpb +``` diff --git a/example/gifs/io-multiple.gif b/example/gifs/io-multiple.gif new file mode 100644 index 0000000..792b678 Binary files /dev/null and b/example/gifs/io-multiple.gif differ diff --git a/example/gifs/remove.gif b/example/gifs/remove.gif new file mode 100644 index 0000000..56985c9 Binary files /dev/null and b/example/gifs/remove.gif differ diff --git a/example/gifs/simple.gif b/example/gifs/simple.gif new file mode 100644 index 0000000..919490d Binary files /dev/null and b/example/gifs/simple.gif differ diff --git a/example/gifs/sort.gif b/example/gifs/sort.gif new file mode 100644 index 0000000..621e3d1 Binary files /dev/null and b/example/gifs/sort.gif differ diff --git a/example/interrupt/main.go b/example/interrupt/main.go new file mode 100644 index 0000000..858367c --- /dev/null +++ b/example/interrupt/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "math/rand" + "time" + + "github.com/vbauerster/mpb" +) + +const ( + totalItem = 100 + maxBlockSize = 14 +) + +func main() { + decor := func(s *mpb.Statistics) string { + str := fmt.Sprintf("%d/%d", s.Current, s.Total) + return fmt.Sprintf("%-7s", str) + } + + p := mpb.New() + bar := p.AddBar(totalItem).AppendETA().PrependFunc(decor) + p.Wg.Add(1) + + blockSize := rand.Intn(maxBlockSize) + 1 + for i := 0; bar.InProgress(); i++ { + time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) + bar.Incr(blockSize) + if bar.Current() > 42 { + p.RemoveBar(bar) + } + blockSize = rand.Intn(maxBlockSize) + 1 + } + + p.WaitAndStop() + fmt.Println("stop") +} diff --git a/example/prependPercent/main.go b/example/prependPercent/main.go deleted file mode 100644 index aae14f8..0000000 --- a/example/prependPercent/main.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "time" - - "github.com/vbauerster/mpb" -) - -const ( - maxBlockSize = 12 -) - -func main() { - - p := mpb.New().SetWidth(64) - // p := mpb.New().RefreshRate(100 * time.Millisecond).SetWidth(64) - - name1 := "Bar#1:" - bar1 := p.AddBar(50).AppendETA().PrependPercentage(3).PrependName(name1, len(name1)) - p.Wg.Add(1) - go func() { - blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; i < 50; i++ { - time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar1.Incr(1) - blockSize = rand.Intn(maxBlockSize) + 1 - } - }() - - bar2 := p.AddBar(100).AppendETA().PrependPercentage(3).PrependName("", 0-len(name1)) - p.Wg.Add(1) - go func() { - blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; i < 100; i++ { - time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar2.Incr(1) - blockSize = rand.Intn(maxBlockSize) + 1 - } - }() - - bar3 := p.AddBar(80).AppendETA().PrependPercentage(3).PrependName("Bar#3:", 0) - p.Wg.Add(1) - go func() { - blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; i < 80; i++ { - time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar3.Incr(1) - blockSize = rand.Intn(maxBlockSize) + 1 - } - }() - - time.Sleep(3 * time.Second) - p.RemoveBar(bar2) - - p.WaitAndStop() - fmt.Println("stop") - // p.AddBar(1) // panic: send on closed channnel -} diff --git a/example/remove/main.go b/example/remove/main.go index 858367c..aae14f8 100644 --- a/example/remove/main.go +++ b/example/remove/main.go @@ -9,30 +9,52 @@ ) const ( - totalItem = 100 - maxBlockSize = 14 + maxBlockSize = 12 ) func main() { - decor := func(s *mpb.Statistics) string { - str := fmt.Sprintf("%d/%d", s.Current, s.Total) - return fmt.Sprintf("%-7s", str) - } - p := mpb.New() - bar := p.AddBar(totalItem).AppendETA().PrependFunc(decor) + p := mpb.New().SetWidth(64) + // p := mpb.New().RefreshRate(100 * time.Millisecond).SetWidth(64) + + name1 := "Bar#1:" + bar1 := p.AddBar(50).AppendETA().PrependPercentage(3).PrependName(name1, len(name1)) p.Wg.Add(1) + go func() { + blockSize := rand.Intn(maxBlockSize) + 1 + for i := 0; i < 50; i++ { + time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) + bar1.Incr(1) + blockSize = rand.Intn(maxBlockSize) + 1 + } + }() - blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; bar.InProgress(); i++ { - time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar.Incr(blockSize) - if bar.Current() > 42 { - p.RemoveBar(bar) + bar2 := p.AddBar(100).AppendETA().PrependPercentage(3).PrependName("", 0-len(name1)) + p.Wg.Add(1) + go func() { + blockSize := rand.Intn(maxBlockSize) + 1 + for i := 0; i < 100; i++ { + time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) + bar2.Incr(1) + blockSize = rand.Intn(maxBlockSize) + 1 } - blockSize = rand.Intn(maxBlockSize) + 1 - } + }() + + bar3 := p.AddBar(80).AppendETA().PrependPercentage(3).PrependName("Bar#3:", 0) + p.Wg.Add(1) + go func() { + blockSize := rand.Intn(maxBlockSize) + 1 + for i := 0; i < 80; i++ { + time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) + bar3.Incr(1) + blockSize = rand.Intn(maxBlockSize) + 1 + } + }() + + time.Sleep(3 * time.Second) + p.RemoveBar(bar2) p.WaitAndStop() fmt.Println("stop") + // p.AddBar(1) // panic: send on closed channnel } diff --git a/example/simple/main.go b/example/simple/main.go new file mode 100644 index 0000000..72cb9c9 --- /dev/null +++ b/example/simple/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "math/rand" + "time" + + "github.com/vbauerster/mpb" +) + +func main() { + // No need to initialize sync.WaitGroup, as it is initialized implicitly + p := mpb.New() // Star mpb container + for i := 0; i < 3; i++ { + p.Wg.Add(1) // add wg counter + name := fmt.Sprintf("Bar#%d:", i) + bar := p.AddBar(100).PrependName(name, len(name)).AppendPercentage() + go func() { + // you can p.AddBar() here, but ordering will be non deterministic + for i := 0; i < 100; i++ { + time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) + bar.Incr(1) + } + }() + } + p.WaitAndStop() // Wait for goroutines to finish + // p.AddBar(1) // panic: you cannot reuse p, create new one! + fmt.Println("finish") +} diff --git a/example/simple/simple.go b/example/simple/simple.go deleted file mode 100644 index 00a58f2..0000000 --- a/example/simple/simple.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "time" - - "github.com/vbauerster/mpb" -) - -const ( - totalItem = 100 - maxBlockSize = 10 -) - -func main() { - decor := func(s *mpb.Statistics) string { - str := fmt.Sprintf("%d/%d", s.Current, s.Total) - return fmt.Sprintf("%-7s", str) - } - - p := mpb.New() - bar := p.AddBar(totalItem).AppendETA().PrependFunc(decor) - // if you omit the following line, bar rendering goroutine may not have a - // chance to coplete, thus better always use. - p.Wg.Add(1) - - blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; i < 100; i++ { - time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar.Incr(1) - blockSize = rand.Intn(maxBlockSize) + 1 - } - - p.WaitAndStop() - fmt.Println("stop") -}