diff --git a/bar.go b/bar.go index 915be51..30c64f2 100644 --- a/bar.go +++ b/bar.go @@ -314,7 +314,7 @@ } func draw(s *state, termWidth int, prependWs, appendWs *widthSync) []byte { - if len(s.prependFuncs) != len(prependWs.listen) || len(s.appendFuncs) != len(appendWs.listen) { + if len(s.prependFuncs) != len(prependWs.Listen) || len(s.appendFuncs) != len(appendWs.Listen) { return []byte{} } if termWidth <= 0 { @@ -327,14 +327,14 @@ var prependBlock []byte for i, f := range s.prependFuncs { prependBlock = append(prependBlock, - []byte(f(stat, prependWs.listen[i], prependWs.result[i]))...) + []byte(f(stat, prependWs.Listen[i], prependWs.Result[i]))...) } // render append functions to the right of the bar var appendBlock []byte for i, f := range s.appendFuncs { appendBlock = append(appendBlock, - []byte(f(stat, appendWs.listen[i], appendWs.result[i]))...) + []byte(f(stat, appendWs.Listen[i], appendWs.Result[i]))...) } prependCount := utf8.RuneCount(prependBlock) diff --git a/decor/decorators.go b/decor/decorators.go index 1cda8b3..bcefa44 100644 --- a/decor/decorators.go +++ b/decor/decorators.go @@ -46,13 +46,30 @@ // DecoratorFunc is a function that can be prepended and appended to the progress bar type DecoratorFunc func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string +// Name deprecated, use StaticName instead func Name(name string, minWidth int, conf byte) DecoratorFunc { + return StaticName(name, minWidth, conf) +} + +// StaticName to be used, when there is no plan to change the name during whole +// life of a progress rendering process +func StaticName(name string, minWidth int, conf byte) DecoratorFunc { + nameFn := func(s *Statistics) string { + return name + } + return DynamicName(nameFn, minWidth, conf) +} + +// DynamicName to be used, when there is a plan to chane the name once or +// several times during progress rendering process +func DynamicName(nameFn func(*Statistics) string, minWidth int, conf byte) DecoratorFunc { format := "%%" if (conf & DidentRight) != 0 { format += "-" } format += "%ds" return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { + name := nameFn(s) if (conf & DwidthSync) != 0 { myWidth <- utf8.RuneCountInString(name) max := <-maxWidth diff --git a/decorators_test.go b/decorators_test.go new file mode 100644 index 0000000..61cc163 --- /dev/null +++ b/decorators_test.go @@ -0,0 +1,155 @@ +package mpb_test + +import ( + "sync" + "testing" + "time" + + . "github.com/vbauerster/mpb" + "github.com/vbauerster/mpb/decor" +) + +func TestStaticName(t *testing.T) { + tests := []struct { + fn decor.DecoratorFunc + want string + }{ + { + fn: decor.StaticName("Test", 0, 0), + want: "Test", + }, + { + fn: decor.StaticName("Test", len("Test"), 0), + want: "Test", + }, + { + fn: decor.StaticName("Test", 10, 0), + want: " Test", + }, + { + fn: decor.StaticName("Test", 10, decor.DidentRight), + want: "Test ", + }, + } + + for _, test := range tests { + got := test.fn(nil, nil, nil) + if got != test.want { + t.Errorf("Want: %q, Got: %q\n", test.want, got) + } + } +} + +type step struct { + stat *decor.Statistics + want string +} + +func TestPercentageDwidthSync(t *testing.T) { + + testCases := [][]step{ + []step{ + {&decor.Statistics{Total: 100, Current: 8}, "8 %"}, + {&decor.Statistics{Total: 100, Current: 9}, "9 %"}, + }, + []step{ + {&decor.Statistics{Total: 100, Current: 9}, " 9 %"}, + {&decor.Statistics{Total: 100, Current: 10}, "10 %"}, + }, + []step{ + {&decor.Statistics{Total: 100, Current: 9}, " 9 %"}, + {&decor.Statistics{Total: 100, Current: 100}, "100 %"}, + }, + } + + dfn := decor.Percentage(3, decor.DwidthSync) + testDecoratorConcurrently(t, dfn, testCases) +} + +func TestPercentageDwidthSyncDidentRight(t *testing.T) { + + testCases := [][]step{ + []step{ + {&decor.Statistics{Total: 100, Current: 8}, "8 %"}, + {&decor.Statistics{Total: 100, Current: 9}, "9 %"}, + }, + []step{ + {&decor.Statistics{Total: 100, Current: 9}, "9 % "}, + {&decor.Statistics{Total: 100, Current: 10}, "10 %"}, + }, + []step{ + {&decor.Statistics{Total: 100, Current: 9}, "9 % "}, + {&decor.Statistics{Total: 100, Current: 100}, "100 %"}, + }, + } + + dfn := decor.Percentage(3, decor.DwidthSync|decor.DidentRight) + testDecoratorConcurrently(t, dfn, testCases) +} + +func TestPercentageDSyncSpace(t *testing.T) { + + testCases := [][]step{ + []step{ + {&decor.Statistics{Total: 100, Current: 8}, " 8 %"}, + {&decor.Statistics{Total: 100, Current: 9}, " 9 %"}, + }, + []step{ + {&decor.Statistics{Total: 100, Current: 9}, " 9 %"}, + {&decor.Statistics{Total: 100, Current: 10}, " 10 %"}, + }, + []step{ + {&decor.Statistics{Total: 100, Current: 9}, " 9 %"}, + {&decor.Statistics{Total: 100, Current: 100}, " 100 %"}, + }, + } + + dfn := decor.Percentage(3, decor.DSyncSpace) + testDecoratorConcurrently(t, dfn, testCases) +} + +func testDecoratorConcurrently(t *testing.T, dfn decor.DecoratorFunc, testCases [][]step) { + if len(testCases) == 0 { + t.Fail() + } + + numBars := len(testCases[0]) + var wg sync.WaitGroup + for _, columnCase := range testCases { + wg.Add(numBars) + timeout := make(chan struct{}) + time.AfterFunc(100*time.Millisecond, func() { + close(timeout) + }) + ws := NewWidthSync(timeout, numBars, 1) + res := make([]chan string, numBars) + for i := 0; i < numBars; i++ { + res[i] = make(chan string, 1) + go func(s step, ch chan string) { + defer wg.Done() + ch <- dfn(s.stat, ws.Listen[0], ws.Result[0]) + }(columnCase[i], res[i]) + } + wg.Wait() + + var i int + for got := range fanIn(res...) { + want := columnCase[i].want + if got != want { + t.Errorf("Want: %q, Got: %q\n", want, got) + } + i++ + } + } +} + +func fanIn(in ...chan string) <-chan string { + ch := make(chan string) + go func() { + defer close(ch) + for _, ich := range in { + ch <- <-ich + } + }() + return ch +} diff --git a/export_test.go b/export_test.go new file mode 100644 index 0000000..6e925d9 --- /dev/null +++ b/export_test.go @@ -0,0 +1,3 @@ +package mpb + +var NewWidthSync = newWidthSync diff --git a/progress.go b/progress.go index 4c7e9a3..cdff1f0 100644 --- a/progress.go +++ b/progress.go @@ -14,8 +14,8 @@ BeforeRender func([]*Bar) widthSync struct { - listen []chan int - result []chan int + Listen []chan int + Result []chan int } // progress config, fields are adjustable by user indirectly @@ -237,12 +237,12 @@ func newWidthSync(timeout <-chan struct{}, numBars, numColumn int) *widthSync { ws := &widthSync{ - listen: make([]chan int, numColumn), - result: make([]chan int, numColumn), + Listen: make([]chan int, numColumn), + Result: make([]chan int, numColumn), } for i := 0; i < numColumn; i++ { - ws.listen[i] = make(chan int, numBars) - ws.result[i] = make(chan int, numBars) + ws.Listen[i] = make(chan int, numBars) + ws.Result[i] = make(chan int, numBars) } for i := 0; i < numColumn; i++ { go func(listenCh <-chan int, resultCh chan<- int) { @@ -267,7 +267,7 @@ for i := 0; i < len(widths); i++ { resultCh <- result } - }(ws.listen[i], ws.result[i]) + }(ws.Listen[i], ws.Result[i]) } return ws }