Codebase list golang-github-vbauerster-mpb / f7c9c39
New upstream version 3.4.0 Dmitry Smirnov 6 years ago
34 changed file(s) with 1042 addition(s) and 660 deletion(s). Raw diff Collapse all Expand all
00 language: go
11 sudo: false
22 go:
3 - 1.8.x
4 - 1.9.x
3 - 1.10.x
4 - tip
55
66 before_install:
77 - go get -t -v ./...
3030 p := mpb.New(
3131 // override default (80) width
3232 mpb.WithWidth(64),
33 // override default "[=>-]" format
34 mpb.WithFormat("╢▌▌░╟"),
3533 // override default 120ms refresh rate
3634 mpb.WithRefreshRate(180*time.Millisecond),
3735 )
4038 name := "Single Bar:"
4139 // adding a single bar
4240 bar := p.AddBar(int64(total),
41 // override default "[=>-]" style
42 mpb.BarStyle("╢▌▌░╟"),
4343 mpb.PrependDecorators(
4444 // display our name with one space on the right
4545 decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}),
+79
-135
bar.go less more
11
22 import (
33 "bytes"
4 "context"
45 "fmt"
56 "io"
67 "io/ioutil"
1011 "unicode/utf8"
1112
1213 "github.com/vbauerster/mpb/decor"
13 "github.com/vbauerster/mpb/internal"
1414 )
15
16 const (
17 rLeft = iota
18 rFill
19 rTip
20 rEmpty
21 rRight
22 )
23
24 const formatLen = 5
25
26 type barRunes [formatLen]rune
2715
2816 // Bar represents a progress Bar
2917 type Bar struct {
4432 shutdown chan struct{}
4533 }
4634
35 // Filler interface.
36 // Bar renders by calling Filler's Fill method. You can literally have
37 // any bar kind, by implementing this interface and passing it to the
38 // Add method.
39 type Filler interface {
40 Fill(w io.Writer, width int, s *decor.Statistics)
41 }
42
43 // FillerFunc is function type adapter to convert function into Filler.
44 type FillerFunc func(w io.Writer, width int, stat *decor.Statistics)
45
46 func (f FillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) {
47 f(w, width, stat)
48 }
49
4750 type (
4851 bState struct {
52 filler Filler
4953 id int
5054 width int
55 alignment int
5156 total int64
5257 current int64
53 runes barRunes
54 trimLeftSpace bool
55 trimRightSpace bool
58 trimSpace bool
5659 toComplete bool
5760 removeOnComplete bool
5861 barClearOnComplete bool
7275 runningBar *Bar
7376 }
7477 refill struct {
75 char rune
76 till int64
78 r rune
79 limit int64
7780 }
7881 frameReader struct {
7982 io.Reader
8386 }
8487 )
8588
86 func newBar(wg *sync.WaitGroup, id int, total int64, cancel <-chan struct{}, options ...BarOption) *Bar {
87 if total <= 0 {
88 total = time.Now().Unix()
89 }
89 func newBar(
90 ctx context.Context,
91 wg *sync.WaitGroup,
92 filler Filler,
93 id, width int,
94 total int64,
95 options ...BarOption,
96 ) *Bar {
9097
9198 s := &bState{
99 filler: filler,
92100 id: id,
93101 priority: id,
102 width: width,
94103 total: total,
95104 }
96105
103112 s.bufP = bytes.NewBuffer(make([]byte, 0, s.width))
104113 s.bufB = bytes.NewBuffer(make([]byte, 0, s.width))
105114 s.bufA = bytes.NewBuffer(make([]byte, 0, s.width))
115 if s.newLineExtendFn != nil {
116 s.bufNL = bytes.NewBuffer(make([]byte, 0, s.width))
117 }
106118
107119 b := &Bar{
108120 priority: s.priority,
120132 b.priority = b.runningBar.priority
121133 }
122134
123 if s.newLineExtendFn != nil {
124 s.bufNL = bytes.NewBuffer(make([]byte, 0, s.width))
125 }
126
127 go b.serve(wg, s, cancel)
135 go b.serve(ctx, wg, s)
128136 return b
129137 }
130138
177185 }
178186
179187 // SetTotal sets total dynamically.
180 // Set final to true, when total is known, it will trigger bar complete event.
181 func (b *Bar) SetTotal(total int64, final bool) bool {
188 // Set complete to true, to trigger bar complete event now.
189 func (b *Bar) SetTotal(total int64, complete bool) {
182190 select {
183191 case b.operateState <- func(s *bState) {
184 if total > 0 {
185 s.total = total
186 }
187 if final {
192 s.total = total
193 if complete && !s.toComplete {
188194 s.current = s.total
189195 s.toComplete = true
190196 }
191197 }:
192 return true
193 case <-b.done:
194 return false
195 }
196 }
197
198 // SetRefill sets fill rune to r, up until n.
199 func (b *Bar) SetRefill(n int, r rune) {
200 if n <= 0 {
201 return
202 }
198 case <-b.done:
199 }
200 }
201
202 // SetRefill sets refill, if supported by underlying Filler.
203 func (b *Bar) SetRefill(amount int64) {
203204 b.operateState <- func(s *bState) {
204 s.refill = &refill{r, int64(n)}
205 }
206 }
207
208 // RefillBy is deprecated, use SetRefill
209 func (b *Bar) RefillBy(n int, r rune) {
210 b.SetRefill(n, r)
205 if f, ok := s.filler.(interface{ SetRefill(int64) }); ok {
206 f.SetRefill(amount)
207 }
208 }
211209 }
212210
213211 // Increment is a shorthand for b.IncrBy(1).
216214 }
217215
218216 // IncrBy increments progress bar by amount of n.
219 // wdd is optional work duration i.e. time.Since(start),
220 // which expected to be provided, if any ewma based decorator is used.
217 // wdd is optional work duration i.e. time.Since(start), which expected
218 // to be provided, if any ewma based decorator is used.
221219 func (b *Bar) IncrBy(n int, wdd ...time.Duration) {
222220 select {
223221 case b.operateState <- func(s *bState) {
224222 s.current += int64(n)
225 if s.current >= s.total {
223 if s.total > 0 && s.current >= s.total {
226224 s.current = s.total
227225 s.toComplete = true
228226 }
237235 // Completed reports whether the bar is in completed state.
238236 func (b *Bar) Completed() bool {
239237 // omit select here, because primary usage of the method is for loop
240 // condition, like for !bar.Completed() {...}
241 // so when toComplete=true it is called once (at which time, the bar is still alive),
242 // then quits the loop and never suppose to be called afterwards.
238 // condition, like for !bar.Completed() {...} so when toComplete=true
239 // it is called once (at which time, the bar is still alive), then
240 // quits the loop and never suppose to be called afterwards.
243241 return <-b.boolCh
244242 }
245243
252250 }
253251 }
254252
255 func (b *Bar) serve(wg *sync.WaitGroup, s *bState, cancel <-chan struct{}) {
253 func (b *Bar) serve(ctx context.Context, wg *sync.WaitGroup, s *bState) {
256254 defer wg.Done()
255 cancel := ctx.Done()
257256 for {
258257 select {
259258 case op := <-b.operateState:
321320 }
322321
323322 func (s *bState) draw(termWidth int) io.Reader {
324 defer s.bufA.WriteByte('\n')
325
326323 if s.panicMsg != "" {
327324 return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", termWidth), s.panicMsg))
328325 }
337334 s.bufA.WriteString(d.Decor(stat))
338335 }
339336
337 if s.barClearOnComplete && s.completeFlushed {
338 s.bufA.WriteByte('\n')
339 return io.MultiReader(s.bufP, s.bufA)
340 }
341
340342 prependCount := utf8.RuneCount(s.bufP.Bytes())
341343 appendCount := utf8.RuneCount(s.bufA.Bytes())
342344
343 if s.barClearOnComplete && s.completeFlushed {
344 return io.MultiReader(s.bufP, s.bufA)
345 }
346
347 s.fillBar(s.width)
348 barCount := utf8.RuneCount(s.bufB.Bytes())
349 totalCount := prependCount + barCount + appendCount
350 if spaceCount := 0; totalCount > termWidth {
351 if !s.trimLeftSpace {
352 spaceCount++
353 }
354 if !s.trimRightSpace {
355 spaceCount++
356 }
357 s.fillBar(termWidth - prependCount - appendCount - spaceCount)
358 }
359
345 if !s.trimSpace {
346 // reserve space for edge spaces
347 termWidth -= 2
348 s.bufB.WriteByte(' ')
349 }
350
351 if prependCount+s.width+appendCount > termWidth {
352 s.filler.Fill(s.bufB, termWidth-prependCount-appendCount, stat)
353 } else {
354 s.filler.Fill(s.bufB, s.width, stat)
355 }
356
357 if !s.trimSpace {
358 s.bufB.WriteByte(' ')
359 }
360
361 s.bufA.WriteByte('\n')
360362 return io.MultiReader(s.bufP, s.bufB, s.bufA)
361 }
362
363 func (s *bState) fillBar(width int) {
364 defer func() {
365 s.bufB.WriteRune(s.runes[rRight])
366 if !s.trimRightSpace {
367 s.bufB.WriteByte(' ')
368 }
369 }()
370
371 s.bufB.Reset()
372 if !s.trimLeftSpace {
373 s.bufB.WriteByte(' ')
374 }
375 s.bufB.WriteRune(s.runes[rLeft])
376 if width <= 2 {
377 return
378 }
379
380 // bar s.width without leftEnd and rightEnd runes
381 barWidth := width - 2
382
383 completedWidth := internal.Percentage(s.total, s.current, int64(barWidth))
384
385 if s.refill != nil {
386 till := internal.Percentage(s.total, s.refill.till, int64(barWidth))
387 // append refill rune
388 var i int64
389 for i = 0; i < till; i++ {
390 s.bufB.WriteRune(s.refill.char)
391 }
392 for i = till; i < completedWidth; i++ {
393 s.bufB.WriteRune(s.runes[rFill])
394 }
395 } else {
396 var i int64
397 for i = 0; i < completedWidth; i++ {
398 s.bufB.WriteRune(s.runes[rFill])
399 }
400 }
401
402 if completedWidth < int64(barWidth) && completedWidth > 0 {
403 _, size := utf8.DecodeLastRune(s.bufB.Bytes())
404 s.bufB.Truncate(s.bufB.Len() - size)
405 s.bufB.WriteRune(s.runes[rTip])
406 }
407
408 for i := completedWidth; i < int64(barWidth); i++ {
409 s.bufB.WriteRune(s.runes[rEmpty])
410 }
411363 }
412364
413365 func (s *bState) wSyncTable() [][]chan int {
441393 }
442394 }
443395
444 func strToBarRunes(format string) (array barRunes) {
445 for i, n := 0, 0; len(format) > 0; i++ {
446 array[i], n = utf8.DecodeRuneInString(format)
447 format = format[n:]
448 }
449 return
450 }
451
452396 func countLines(b []byte) int {
453397 return bytes.Count(b, []byte("\n"))
454398 }
0 package mpb
1
2 import (
3 "io"
4 "unicode/utf8"
5
6 "github.com/vbauerster/mpb/decor"
7 "github.com/vbauerster/mpb/internal"
8 )
9
10 const (
11 rLeft = iota
12 rFill
13 rTip
14 rEmpty
15 rRight
16 rRevTip
17 rRefill
18 )
19
20 var defaultBarStyle = "[=>-]<+"
21
22 type barFiller struct {
23 format [][]byte
24 refillAmount int64
25 reverse bool
26 }
27
28 func newDefaultBarFiller() Filler {
29 bf := &barFiller{
30 format: make([][]byte, utf8.RuneCountInString(defaultBarStyle)),
31 }
32 bf.setStyle(defaultBarStyle)
33 return bf
34 }
35
36 func (s *barFiller) setStyle(style string) {
37 if !utf8.ValidString(style) {
38 return
39 }
40 src := make([][]byte, 0, utf8.RuneCountInString(style))
41 for _, r := range style {
42 src = append(src, []byte(string(r)))
43 }
44 copy(s.format, src)
45 }
46
47 func (s *barFiller) setReverse() {
48 s.reverse = true
49 }
50
51 func (s *barFiller) SetRefill(amount int64) {
52 s.refillAmount = amount
53 }
54
55 func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) {
56
57 // don't count rLeft and rRight [brackets]
58 width -= 2
59 if width < 2 {
60 return
61 }
62
63 w.Write(s.format[rLeft])
64 if width == 2 {
65 w.Write(s.format[rRight])
66 return
67 }
68
69 bb := make([][]byte, width)
70
71 cwidth := int(internal.Percentage(stat.Total, stat.Current, int64(width)))
72
73 for i := 0; i < cwidth; i++ {
74 bb[i] = s.format[rFill]
75 }
76
77 if s.refillAmount > 0 {
78 var rwidth int
79 if s.refillAmount > stat.Current {
80 rwidth = cwidth
81 } else {
82 rwidth = int(internal.Percentage(stat.Total, int64(s.refillAmount), int64(width)))
83 }
84 for i := 0; i < rwidth; i++ {
85 bb[i] = s.format[rRefill]
86 }
87 }
88
89 if cwidth > 0 && cwidth < width {
90 bb[cwidth-1] = s.format[rTip]
91 }
92
93 for i := cwidth; i < width; i++ {
94 bb[i] = s.format[rEmpty]
95 }
96
97 if s.reverse {
98 if cwidth > 0 && cwidth < width {
99 bb[cwidth-1] = s.format[rRevTip]
100 }
101 for i := len(bb) - 1; i >= 0; i-- {
102 w.Write(bb[i])
103 }
104 } else {
105 for i := 0; i < len(bb); i++ {
106 w.Write(bb[i])
107 }
108 }
109 w.Write(s.format[rRight])
110 }
55 "github.com/vbauerster/mpb/decor"
66 )
77
8 // BarOption is a function option which changes the default behavior of a bar,
9 // if passed to p.AddBar(int64, ...BarOption)
8 // BarOption is a function option which changes the default behavior of a bar.
109 type BarOption func(*bState)
1110
12 // AppendDecorators let you inject decorators to the bar's right side
11 // AppendDecorators let you inject decorators to the bar's right side.
1312 func AppendDecorators(appenders ...decor.Decorator) BarOption {
1413 return func(s *bState) {
1514 for _, decorator := range appenders {
2423 }
2524 }
2625
27 // PrependDecorators let you inject decorators to the bar's left side
26 // PrependDecorators let you inject decorators to the bar's left side.
2827 func PrependDecorators(prependers ...decor.Decorator) BarOption {
2928 return func(s *bState) {
3029 for _, decorator := range prependers {
3938 }
4039 }
4140
42 // BarTrimLeft trims left side space of the bar
43 func BarTrimLeft() BarOption {
44 return func(s *bState) {
45 s.trimLeftSpace = true
46 }
47 }
48
49 // BarTrimRight trims right space of the bar
50 func BarTrimRight() BarOption {
51 return func(s *bState) {
52 s.trimRightSpace = true
53 }
54 }
55
56 // BarTrim trims both left and right spaces of the bar
57 func BarTrim() BarOption {
58 return func(s *bState) {
59 s.trimLeftSpace = true
60 s.trimRightSpace = true
61 }
62 }
63
64 // BarID overwrites internal bar id
41 // BarID sets bar id.
6542 func BarID(id int) BarOption {
6643 return func(s *bState) {
6744 s.id = id
6845 }
6946 }
7047
71 // BarRemoveOnComplete is a flag, if set whole bar line will be removed on complete event.
72 // If both BarRemoveOnComplete and BarClearOnComplete are set, first bar section gets cleared
73 // and then whole bar line gets removed completely.
48 // BarWidth sets bar width independent of the container.
49 func BarWidth(width int) BarOption {
50 return func(s *bState) {
51 s.width = width
52 }
53 }
54
55 // BarRemoveOnComplete is a flag, if set whole bar line will be removed
56 // on complete event. If both BarRemoveOnComplete and BarClearOnComplete
57 // are set, first bar section gets cleared and then whole bar line
58 // gets removed completely.
7459 func BarRemoveOnComplete() BarOption {
7560 return func(s *bState) {
7661 s.removeOnComplete = true
7762 }
7863 }
7964
80 // BarReplaceOnComplete is indicator for delayed bar start, after the `runningBar` is complete.
81 // To achieve bar replacement effect, `runningBar` should has its `BarRemoveOnComplete` option set.
65 // BarReplaceOnComplete is indicator for delayed bar start, after the
66 // `runningBar` is complete. To achieve bar replacement effect,
67 // `runningBar` should has its `BarRemoveOnComplete` option set.
8268 func BarReplaceOnComplete(runningBar *Bar) BarOption {
69 return BarParkTo(runningBar)
70 }
71
72 // BarParkTo same as BarReplaceOnComplete
73 func BarParkTo(runningBar *Bar) BarOption {
8374 return func(s *bState) {
8475 s.runningBar = runningBar
8576 }
8677 }
8778
88 // BarClearOnComplete is a flag, if set will clear bar section on complete event.
89 // If you need to remove a whole bar line, refer to BarRemoveOnComplete.
79 // BarClearOnComplete is a flag, if set will clear bar section on
80 // complete event. If you need to remove a whole bar line, refer to
81 // BarRemoveOnComplete.
9082 func BarClearOnComplete() BarOption {
9183 return func(s *bState) {
9284 s.barClearOnComplete = true
9385 }
9486 }
9587
96 // BarPriority sets bar's priority.
97 // Zero is highest priority, i.e. bar will be on top.
98 // If `BarReplaceOnComplete` option is supplied, this option is ignored.
88 // BarPriority sets bar's priority. Zero is highest priority, i.e. bar
89 // will be on top. If `BarReplaceOnComplete` option is supplied, this
90 // option is ignored.
9991 func BarPriority(priority int) BarOption {
10092 return func(s *bState) {
10193 s.priority = priority
10294 }
10395 }
10496
105 // BarNewLineExtend takes user defined efn, which gets called each render cycle.
106 // Any write to provided writer of efn, will appear on new line of respective bar.
97 // BarNewLineExtend takes user defined efn, which gets called each
98 // render cycle. Any write to provided writer of efn, will appear on
99 // new line of respective bar.
107100 func BarNewLineExtend(efn func(io.Writer, *decor.Statistics)) BarOption {
108101 return func(s *bState) {
109102 s.newLineExtendFn = efn
110103 }
111104 }
112105
113 func barWidth(w int) BarOption {
106 // TrimSpace trims bar's edge spaces.
107 func TrimSpace() BarOption {
114108 return func(s *bState) {
115 s.width = w
109 s.trimSpace = true
116110 }
117111 }
118112
119 func barFormat(format string) BarOption {
113 // BarStyle sets custom bar style, default one is "[=>-]<+".
114 //
115 // '[' left bracket rune
116 //
117 // '=' fill rune
118 //
119 // '>' tip rune
120 //
121 // '-' empty rune
122 //
123 // ']' right bracket rune
124 //
125 // '<' reverse tip rune, used when BarReverse option is set
126 //
127 // '+' refill rune, used when *Bar.SetRefill(int64) is called
128 //
129 // It's ok to provide first five runes only, for example mpb.BarStyle("╢▌▌░╟")
130 func BarStyle(style string) BarOption {
131 chk := func(filler Filler) (interface{}, bool) {
132 if style == "" {
133 return nil, false
134 }
135 t, ok := filler.(*barFiller)
136 return t, ok
137 }
138 cb := func(t interface{}) {
139 t.(*barFiller).setStyle(style)
140 }
141 return MakeFillerTypeSpecificBarOption(chk, cb)
142 }
143
144 // BarReverse reverse mode, bar will progress from right to left.
145 func BarReverse() BarOption {
146 chk := func(filler Filler) (interface{}, bool) {
147 t, ok := filler.(*barFiller)
148 return t, ok
149 }
150 cb := func(t interface{}) {
151 t.(*barFiller).setReverse()
152 }
153 return MakeFillerTypeSpecificBarOption(chk, cb)
154 }
155
156 // SpinnerStyle sets custom spinner style.
157 // Effective when Filler type is spinner.
158 func SpinnerStyle(frames []string) BarOption {
159 chk := func(filler Filler) (interface{}, bool) {
160 if len(frames) == 0 {
161 return nil, false
162 }
163 t, ok := filler.(*spinnerFiller)
164 return t, ok
165 }
166 cb := func(t interface{}) {
167 t.(*spinnerFiller).frames = frames
168 }
169 return MakeFillerTypeSpecificBarOption(chk, cb)
170 }
171
172 // MakeFillerTypeSpecificBarOption makes BarOption specific to Filler's
173 // actual type. If you implement your own Filler, so most probably
174 // you'll need this. See BarStyle or SpinnerStyle for example.
175 func MakeFillerTypeSpecificBarOption(
176 typeChecker func(Filler) (interface{}, bool),
177 cb func(interface{}),
178 ) BarOption {
120179 return func(s *bState) {
121 s.runes = strToBarRunes(format)
180 if t, ok := typeChecker(s.filler); ok {
181 cb(t)
182 }
122183 }
123184 }
185
186 // OptionOnCondition returns option when condition evaluates to true.
187 func OptionOnCondition(option BarOption, condition func() bool) BarOption {
188 if condition() {
189 return option
190 }
191 return nil
192 }
66 "strings"
77 "testing"
88 "time"
9 "unicode/utf8"
910
1011 . "github.com/vbauerster/mpb"
1112 "github.com/vbauerster/mpb/decor"
5960
6061 total := 100
6162 till := 30
62 refillRune := '+'
63
64 bar := p.AddBar(int64(total), BarTrim())
65
66 bar.SetRefill(till, refillRune)
63 refillRune, _ := utf8.DecodeLastRuneInString(DefaultBarStyle)
64
65 bar := p.AddBar(int64(total), TrimSpace())
66
67 bar.SetRefill(int64(till))
6768 bar.IncrBy(till)
6869
6970 for i := 0; i < total-till; i++ {
7576
7677 wantBar := fmt.Sprintf("[%s%s]",
7778 strings.Repeat(string(refillRune), till-1),
78 strings.Repeat("=", total-till-1))
79
80 if !strings.Contains(buf.String(), wantBar) {
81 t.Errorf("Want bar: %s, got bar: %s\n", wantBar, buf.String())
79 strings.Repeat("=", total-till-1),
80 )
81
82 got := string(getLastLine(buf.Bytes()))
83
84 if !strings.Contains(got, wantBar) {
85 t.Errorf("Want bar: %q, got bar: %q\n", wantBar, got)
86 }
87 }
88
89 func TestBarHas100PercentWithOnCompleteDecorator(t *testing.T) {
90 var buf bytes.Buffer
91
92 p := New(WithOutput(&buf))
93
94 total := 50
95
96 bar := p.AddBar(int64(total),
97 AppendDecorators(
98 decor.OnComplete(
99 decor.Percentage(), "done",
100 ),
101 ),
102 )
103
104 for i := 0; i < total; i++ {
105 bar.Increment()
106 time.Sleep(10 * time.Millisecond)
107 }
108
109 p.Wait()
110
111 hundred := "100 %"
112 if !bytes.Contains(buf.Bytes(), []byte(hundred)) {
113 t.Errorf("Bar's buffer does not contain: %q\n", hundred)
114 }
115 }
116
117 func TestBarHas100PercentWithBarRemoveOnComplete(t *testing.T) {
118 var buf bytes.Buffer
119
120 p := New(WithOutput(&buf))
121
122 total := 50
123
124 bar := p.AddBar(int64(total),
125 BarRemoveOnComplete(),
126 AppendDecorators(decor.Percentage()),
127 )
128
129 for i := 0; i < total; i++ {
130 bar.Increment()
131 time.Sleep(10 * time.Millisecond)
132 }
133
134 p.Wait()
135
136 hundred := "100 %"
137 if !bytes.Contains(buf.Bytes(), []byte(hundred)) {
138 t.Errorf("Bar's buffer does not contain: %q\n", hundred)
139 }
140 }
141
142 func TestBarStyle(t *testing.T) {
143 var buf bytes.Buffer
144 customFormat := "╢▌▌░╟"
145 p := New(WithOutput(&buf))
146 total := 80
147 bar := p.AddBar(int64(total), BarStyle(customFormat), TrimSpace())
148
149 for i := 0; i < total; i++ {
150 bar.Increment()
151 time.Sleep(10 * time.Millisecond)
152 }
153
154 p.Wait()
155
156 runes := []rune(customFormat)
157 wantBar := fmt.Sprintf("%s%s%s",
158 string(runes[0]),
159 strings.Repeat(string(runes[1]), total-2),
160 string(runes[len(runes)-1]),
161 )
162 got := string(getLastLine(buf.Bytes()))
163
164 if got != wantBar {
165 t.Errorf("Want bar: %q:%d, got bar: %q:%d\n", wantBar, utf8.RuneCountInString(wantBar), got, utf8.RuneCountInString(got))
82166 }
83167 }
84168
2121 clearCursorAndLine = cursorUp + clearLine
2222 )
2323
24 // Writer is a buffered the writer that updates the terminal.
25 // The contents of writer will be flushed when Flush is called.
24 // Writer is a buffered the writer that updates the terminal. The
25 // contents of writer will be flushed when Flush is called.
2626 type Writer struct {
2727 out io.Writer
2828 buf bytes.Buffer
6363 return w.buf.WriteString(s)
6464 }
6565
66 // ReadFrom reads from the provided io.Reader and writes to the underlying buffer.
66 // ReadFrom reads from the provided io.Reader and writes to the
67 // underlying buffer.
6768 func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
6869 return w.buf.ReadFrom(r)
6970 }
7071
72 // GetWidth returns width of underlying terminal.
7173 func (w *Writer) GetWidth() (int, error) {
7274 if w.isTerminal {
7375 tw, _, err := terminal.GetSize(w.fd)
77 "syscall"
88 "unsafe"
99
10 "github.com/mattn/go-isatty"
10 isatty "github.com/mattn/go-isatty"
1111 )
1212
1313 var kernel32 = syscall.NewLazyDLL("kernel32.dll")
140140 return Counters(0, pairFormat, wcc...)
141141 }
142142
143 // CountersKibiByte is a wrapper around Counters with predefined unit UnitKiB (bytes/1024).
143 // CountersKibiByte is a wrapper around Counters with predefined unit
144 // UnitKiB (bytes/1024).
144145 func CountersKibiByte(pairFormat string, wcc ...WC) Decorator {
145146 return Counters(UnitKiB, pairFormat, wcc...)
146147 }
147148
148 // CountersKiloByte is a wrapper around Counters with predefined unit UnitKB (bytes/1000).
149 // CountersKiloByte is a wrapper around Counters with predefined unit
150 // UnitKB (bytes/1000).
149151 func CountersKiloByte(pairFormat string, wcc ...WC) Decorator {
150152 return Counters(UnitKB, pairFormat, wcc...)
151153 }
3030 DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight
3131 )
3232
33 // TimeStyle enum.
34 type TimeStyle int
35
36 // TimeStyle kinds.
3337 const (
34 ET_STYLE_GO = iota
38 ET_STYLE_GO TimeStyle = iota
3539 ET_STYLE_HHMMSS
3640 ET_STYLE_HHMM
3741 ET_STYLE_MMSS
4650 }
4751
4852 // Decorator interface.
49 // A decorator must implement this interface, in order to be used with mpb library.
53 // A decorator must implement this interface, in order to be used with
54 // mpb library.
5055 type Decorator interface {
5156 Decor(*Statistics) string
5257 Syncable
5358 }
5459
5560 // Syncable interface.
56 // All decorators implement this interface implicitly.
57 // Its Syncable method exposes width sync channel, if sync is enabled.
61 // All decorators implement this interface implicitly. Its Syncable
62 // method exposes width sync channel, if sync is enabled.
5863 type Syncable interface {
5964 Syncable() (bool, chan int)
6065 }
6166
6267 // OnCompleteMessenger interface.
63 // Decorators implementing this interface suppose to return provided string on complete event.
68 // Decorators implementing this interface suppose to return provided
69 // string on complete event.
6470 type OnCompleteMessenger interface {
6571 OnCompleteMessage(string)
6672 }
6773
6874 // AmountReceiver interface.
69 // If decorator needs to receive increment amount,
70 // so this is the right interface to implement.
75 // If decorator needs to receive increment amount, so this is the right
76 // interface to implement.
7177 type AmountReceiver interface {
7278 NextAmount(int, ...time.Duration)
7379 }
7480
7581 // ShutdownListener interface.
76 // If decorator needs to be notified once upon bar shutdown event,
77 // so this is the right interface to implement.
82 // If decorator needs to be notified once upon bar shutdown event, so
83 // this is the right interface to implement.
7884 type ShutdownListener interface {
7985 Shutdown()
8086 }
8995
9096 // WC is a struct with two public fields W and C, both of int type.
9197 // W represents width and C represents bit set of width related config.
98 // A decorator should embed WC, in order to become Syncable.
9299 type WC struct {
93100 W int
94101 C int
125132 }
126133 }
127134
135 // Syncable is implementation of Syncable interface.
128136 func (wc *WC) Syncable() (bool, chan int) {
129137 return (wc.C & DSyncWidth) != 0, wc.wsync
130138 }
131139
132 // OnComplete returns decorator, which wraps provided decorator, with sole
133 // purpose to display provided message on complete event.
140 // OnComplete returns decorator, which wraps provided decorator, with
141 // sole purpose to display provided message on complete event.
134142 //
135143 // `decorator` Decorator to wrap
136144 //
99 // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
1010 //
1111 // `wcc` optional WC config
12 func Elapsed(style int, wcc ...WC) Decorator {
12 func Elapsed(style TimeStyle, wcc ...WC) Decorator {
1313 var wc WC
1414 for _, widthConf := range wcc {
1515 wc = widthConf
2525
2626 type elapsedDecorator struct {
2727 WC
28 style int
28 style TimeStyle
2929 startTime time.Time
3030 msg string
3131 completeMsg *string
55 "time"
66
77 "github.com/VividCortex/ewma"
8 "github.com/vbauerster/mpb/internal"
98 )
109
1110 type TimeNormalizer func(time.Duration) time.Duration
1716 // `age` is the previous N samples to average over.
1817 //
1918 // `wcc` optional WC config
20 func EwmaETA(style int, age float64, wcc ...WC) Decorator {
19 func EwmaETA(style TimeStyle, age float64, wcc ...WC) Decorator {
2120 return MovingAverageETA(style, ewma.NewMovingAverage(age), NopNormalizer(), wcc...)
2221 }
2322
3029 // `normalizer` available implementations are [NopNormalizer|FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer]
3130 //
3231 // `wcc` optional WC config
33 func MovingAverageETA(style int, average MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator {
32 func MovingAverageETA(style TimeStyle, average MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator {
3433 var wc WC
3534 for _, widthConf := range wcc {
3635 wc = widthConf
4746
4847 type movingAverageETA struct {
4948 WC
50 style int
49 style TimeStyle
5150 average ewma.MovingAverage
5251 completeMsg *string
5352 normalizer TimeNormalizer
5857 return d.FormatMsg(*d.completeMsg)
5958 }
6059
61 v := internal.Round(d.average.Value())
60 v := math.Round(d.average.Value())
6261 remaining := d.normalizer(time.Duration((st.Total - st.Current) * int64(v)))
6362 hours := int64((remaining / time.Hour) % 60)
6463 minutes := int64((remaining / time.Minute) % 60)
104103 // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
105104 //
106105 // `wcc` optional WC config
107 func AverageETA(style int, wcc ...WC) Decorator {
106 func AverageETA(style TimeStyle, wcc ...WC) Decorator {
108107 var wc WC
109108 for _, widthConf := range wcc {
110109 wc = widthConf
120119
121120 type averageETA struct {
122121 WC
123 style int
122 style TimeStyle
124123 startTime time.Time
125124 completeMsg *string
126125 }
132131
133132 var str string
134133 timeElapsed := time.Since(d.startTime)
135 v := internal.Round(float64(timeElapsed) / float64(st.Current))
134 v := math.Round(float64(timeElapsed) / float64(st.Current))
136135 if math.IsInf(v, 0) || math.IsNaN(v) {
137136 v = 0
138137 }
55 "github.com/VividCortex/ewma"
66 )
77
8 // MovingAverage is the interface that computes a moving average over a time-
9 // series stream of numbers. The average may be over a window or exponentially
10 // decaying.
8 // MovingAverage is the interface that computes a moving average over
9 // a time-series stream of numbers. The average may be over a window
10 // or exponentially decaying.
1111 type MovingAverage interface {
1212 Add(float64)
1313 Value() float64
5656 s.count++
5757 }
5858
59 // NewMedianEwma is ewma based MovingAverage, which gets its values from median MovingAverage.
59 // NewMedianEwma is ewma based MovingAverage, which gets its values
60 // from median MovingAverage.
6061 func NewMedianEwma(age ...float64) MovingAverage {
6162 return &medianEwma{
6263 MovingAverage: ewma.NewMovingAverage(age...),
136136 return MovingAverageSpeed(unit, unitFormat, ewma.NewMovingAverage(age), wcc...)
137137 }
138138
139 // MovingAverageSpeed decorator relies on MovingAverage implementation to calculate its average.
139 // MovingAverageSpeed decorator relies on MovingAverage implementation
140 // to calculate its average.
140141 //
141142 // `unit` one of [0|UnitKiB|UnitKB] zero for no unit
142143 //
22 import (
33 "bytes"
44 "testing"
5 "unicode/utf8"
56 )
67
78 func TestDraw(t *testing.T) {
89 // key is termWidth
9 testSuite := map[int]map[string]struct {
10 testSuite := map[int][]struct {
11 name string
1012 total, current int64
1113 barWidth int
12 barRefill *refill
14 trimSpace bool
15 rup int64
1316 want string
1417 }{
18 2: {
19 {
20 name: "t,c,bw{60,20,80}",
21 total: 60,
22 current: 20,
23 barWidth: 80,
24 want: " ",
25 },
26 {
27 name: "t,c,bw,trim{60,20,80,true}",
28 total: 60,
29 current: 20,
30 barWidth: 80,
31 trimSpace: true,
32 want: "",
33 },
34 },
35 3: {
36 {
37 name: "t,c,bw{60,20,80}",
38 total: 60,
39 current: 20,
40 barWidth: 80,
41 want: " ",
42 },
43 {
44 name: "t,c,bw,trim{60,20,80,true}",
45 total: 60,
46 current: 20,
47 barWidth: 80,
48 trimSpace: true,
49 want: "",
50 },
51 },
52 4: {
53 {
54 name: "t,c,bw{60,20,80}",
55 total: 60,
56 current: 20,
57 barWidth: 80,
58 want: " ",
59 },
60 {
61 name: "t,c,bw,trim{60,20,80,true}",
62 total: 60,
63 current: 20,
64 barWidth: 80,
65 trimSpace: true,
66 want: "[]",
67 },
68 },
69 5: {
70 {
71 name: "t,c,bw{60,20,80}",
72 total: 60,
73 current: 20,
74 barWidth: 80,
75 want: " ",
76 },
77 {
78 name: "t,c,bw,trim{60,20,80,true}",
79 total: 60,
80 current: 20,
81 barWidth: 80,
82 trimSpace: true,
83 want: "[>--]",
84 },
85 },
86 6: {
87 {
88 name: "t,c,bw{60,20,80}",
89 total: 60,
90 current: 20,
91 barWidth: 80,
92 want: " [] ",
93 },
94 {
95 name: "t,c,bw,trim{60,20,80,true}",
96 total: 60,
97 current: 20,
98 barWidth: 80,
99 trimSpace: true,
100 want: "[>---]",
101 },
102 },
103 7: {
104 {
105 name: "t,c,bw{60,20,80}",
106 total: 60,
107 current: 20,
108 barWidth: 80,
109 want: " [>--] ",
110 },
111 {
112 name: "t,c,bw,trim{60,20,80,true}",
113 total: 60,
114 current: 20,
115 barWidth: 80,
116 trimSpace: true,
117 want: "[=>---]",
118 },
119 },
120 8: {
121 {
122 name: "t,c,bw{60,20,80}",
123 total: 60,
124 current: 20,
125 barWidth: 80,
126 want: " [>---] ",
127 },
128 {
129 name: "t,c,bw,trim{60,20,80,true}",
130 total: 60,
131 current: 20,
132 barWidth: 80,
133 trimSpace: true,
134 want: "[=>----]",
135 },
136 },
137 80: {
138 {
139 name: "t,c,bw{60,20,80}",
140 total: 60,
141 current: 20,
142 barWidth: 80,
143 want: " [========================>---------------------------------------------------] ",
144 },
145 {
146 name: "t,c,bw,trim{60,20,80,true}",
147 total: 60,
148 current: 20,
149 barWidth: 80,
150 trimSpace: true,
151 want: "[=========================>----------------------------------------------------]",
152 },
153 },
15154 100: {
16 "t,c,bw{100,100,0}": {
155 {
156 name: "t,c,bw{100,100,0}",
17157 total: 100,
18158 current: 0,
19159 barWidth: 100,
20 want: "[--------------------------------------------------------------------------------------------------]",
21 },
22 "t,c,bw{100,1,100}": {
160 want: " [------------------------------------------------------------------------------------------------] ",
161 },
162 {
163 name: "t,c,bw,trim{100,100,0,true}",
164 total: 100,
165 current: 0,
166 barWidth: 100,
167 trimSpace: true,
168 want: "[--------------------------------------------------------------------------------------------------]",
169 },
170 {
171 name: "t,c,bw{100,1,100}",
23172 total: 100,
24173 current: 1,
25174 barWidth: 100,
26 want: "[>-------------------------------------------------------------------------------------------------]",
27 },
28 "t,c,bw{100,40,100}": {
175 want: " [>-----------------------------------------------------------------------------------------------] ",
176 },
177 {
178 name: "t,c,bw,trim{100,1,100,true}",
179 total: 100,
180 current: 1,
181 barWidth: 100,
182 trimSpace: true,
183 want: "[>-------------------------------------------------------------------------------------------------]",
184 },
185 {
186 name: "t,c,bw{100,33,100}",
187 total: 100,
188 current: 33,
189 barWidth: 100,
190 want: " [===============================>----------------------------------------------------------------] ",
191 },
192 {
193 name: "t,c,bw,trim{100,33,100,true}",
194 total: 100,
195 current: 33,
196 barWidth: 100,
197 trimSpace: true,
198 want: "[===============================>------------------------------------------------------------------]",
199 },
200 {
201 name: "t,c,bw,rup{100,33,100,33}",
202 total: 100,
203 current: 33,
204 barWidth: 100,
205 rup: 33,
206 want: " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ",
207 },
208 {
209 name: "t,c,bw,rup,trim{100,33,100,33,true}",
210 total: 100,
211 current: 33,
212 barWidth: 100,
213 rup: 33,
214 trimSpace: true,
215 want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]",
216 },
217 {
218 name: "t,c,bw,rup{100,40,100,32}",
29219 total: 100,
30220 current: 40,
31221 barWidth: 100,
32 want: "[======================================>-----------------------------------------------------------]",
33 },
34 "t,c,bw{100,40,100}refill{'+', 32}": {
222 rup: 33,
223 want: " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ",
224 },
225 {
226 name: "t,c,bw,rup,trim{100,40,100,32,true}",
35227 total: 100,
36228 current: 40,
37229 barWidth: 100,
38 barRefill: &refill{'+', 32},
39 want: "[+++++++++++++++++++++++++++++++=======>-----------------------------------------------------------]",
40 },
41 "t,c,bw{100,99,100}": {
230 rup: 33,
231 trimSpace: true,
232 want: "[++++++++++++++++++++++++++++++++======>-----------------------------------------------------------]",
233 },
234 {
235 name: "t,c,bw{100,99,100}",
42236 total: 100,
43237 current: 99,
44238 barWidth: 100,
45 want: "[================================================================================================>-]",
46 },
47 "t,c,bw{100,100,100}": {
239 want: " [==============================================================================================>-] ",
240 },
241 {
242 name: "t,c,bw,trim{100,99,100,true}",
243 total: 100,
244 current: 99,
245 barWidth: 100,
246 trimSpace: true,
247 want: "[================================================================================================>-]",
248 },
249 {
250 name: "t,c,bw{100,100,100}",
48251 total: 100,
49252 current: 100,
50253 barWidth: 100,
51 want: "[==================================================================================================]",
52 },
53 },
54 2: {
55 "t,c,bw{0,0,100}": {
56 barWidth: 100,
57 want: "[]",
58 },
59 "t,c,bw{60,20,80}": {
60 total: 60,
61 current: 20,
62 barWidth: 80,
63 want: "[]",
64 },
65 },
66 3: {
67 "t,c,bw{100,20,100}": {
68 total: 100,
69 current: 20,
70 barWidth: 100,
71 want: "[-]",
72 },
73 "t,c,bw{100,98,100}": {
74 total: 100,
75 current: 98,
76 barWidth: 100,
77 want: "[=]",
78 },
79 "t,c,bw{100,100,100}": {
80 total: 100,
81 current: 100,
82 barWidth: 100,
83 want: "[=]",
84 },
85 },
86 5: {
87 "t,c,bw{100,20,100}": {
88 total: 100,
89 current: 20,
90 barWidth: 100,
91 want: "[>--]",
92 },
93 "t,c,bw{100,98,100}": {
94 total: 100,
95 current: 98,
96 barWidth: 100,
97 want: "[===]",
98 },
99 "t,c,bw{100,100,100}": {
100 total: 100,
101 current: 100,
102 barWidth: 100,
103 want: "[===]",
104 },
105 },
106 6: {
107 "t,c,bw{100,20,100}": {
108 total: 100,
109 current: 20,
110 barWidth: 100,
111 want: "[>---]",
112 },
113 "t,c,bw{100,98,100}": {
114 total: 100,
115 current: 98,
116 barWidth: 100,
117 want: "[====]",
118 },
119 "t,c,bw{100,100,100}": {
120 total: 100,
121 current: 100,
122 barWidth: 100,
123 want: "[====]",
124 },
125 },
126 20: {
127 "t,c,bw{100,20,100}": {
128 total: 100,
129 current: 20,
130 barWidth: 100,
131 want: "[===>--------------]",
132 },
133 "t,c,bw{100,60,100}": {
134 total: 100,
135 current: 60,
136 barWidth: 100,
137 want: "[==========>-------]",
138 },
139 "t,c,bw{100,98,100}": {
140 total: 100,
141 current: 98,
142 barWidth: 100,
143 want: "[==================]",
144 },
145 "t,c,bw{100,100,100}": {
146 total: 100,
147 current: 100,
148 barWidth: 100,
149 want: "[==================]",
150 },
151 },
152 50: {
153 "t,c,bw{100,20,100}": {
154 total: 100,
155 current: 20,
156 barWidth: 100,
157 want: "[=========>--------------------------------------]",
158 },
159 "t,c,bw{100,60,100}": {
160 total: 100,
161 current: 60,
162 barWidth: 100,
163 want: "[============================>-------------------]",
164 },
165 "t,c,bw{100,98,100}": {
166 total: 100,
167 current: 98,
168 barWidth: 100,
169 want: "[==============================================>-]",
170 },
171 "t,c,bw{100,100,100}": {
172 total: 100,
173 current: 100,
174 barWidth: 100,
175 want: "[================================================]",
254 want: " [================================================================================================] ",
255 },
256 {
257 name: "t,c,bw,trim{100,100,100,true}",
258 total: 100,
259 current: 100,
260 barWidth: 100,
261 trimSpace: true,
262 want: "[==================================================================================================]",
176263 },
177264 },
178265 }
179266
180267 var tmpBuf bytes.Buffer
181268 for termWidth, cases := range testSuite {
182 for name, tc := range cases {
269 for _, tc := range cases {
183270 s := newTestState()
184271 s.width = tc.barWidth
185272 s.total = tc.total
186273 s.current = tc.current
187 if tc.barRefill != nil {
188 s.refill = tc.barRefill
274 s.trimSpace = tc.trimSpace
275 if tc.rup > 0 {
276 if f, ok := s.filler.(interface{ SetRefill(int64) }); ok {
277 f.SetRefill(tc.rup)
278 }
189279 }
190280 tmpBuf.Reset()
191281 tmpBuf.ReadFrom(s.draw(termWidth))
192 got := tmpBuf.String()
193 want := tc.want + "\n"
194 if got != want {
195 t.Errorf("termWidth %d; %s: want: %s %d, got: %s %d\n", termWidth, name, want, len(want), got, len(got))
282 by := tmpBuf.Bytes()
283 by = by[:len(by)-1]
284
285 if utf8.RuneCount(by) > termWidth {
286 t.Errorf("termWidth:%d %q barWidth:%d overflow termWidth\n", termWidth, tc.name, utf8.RuneCount(by))
287 }
288
289 got := string(by)
290 if got != tc.want {
291 t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", termWidth, tc.name, tc.want, len(tc.want), got, len(got))
196292 }
197293 }
198294 }
200296
201297 func newTestState() *bState {
202298 s := &bState{
203 trimLeftSpace: true,
204 trimRightSpace: true,
205 bufP: new(bytes.Buffer),
206 bufB: new(bytes.Buffer),
207 bufA: new(bytes.Buffer),
299 filler: newDefaultBarFiller(),
300 bufP: new(bytes.Buffer),
301 bufB: new(bytes.Buffer),
302 bufA: new(bytes.Buffer),
208303 }
209 s.runes = strToBarRunes(pformat)
210304 return s
211305 }
1414 p := mpb.New(
1515 // override default (80) width
1616 mpb.WithWidth(64),
17 // override default "[=>-]" format
18 mpb.WithFormat("╢▌▌░╟"),
1917 // override default 120ms refresh rate
2018 mpb.WithRefreshRate(180*time.Millisecond),
2119 )
2422 name := "Single Bar:"
2523 // adding a single bar
2624 bar := p.AddBar(int64(total),
25 // override default "[=>-]" style
26 mpb.BarStyle("╢▌▌░╟"),
2727 mpb.PrependDecorators(
2828 // display our name with one space on the right
2929 decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}),
0 //+build go1.7
1
20 package main
31
42 import (
0 package main
1
2 import (
3 "fmt"
4 "math/rand"
5 "sync"
6 "time"
7
8 "github.com/vbauerster/mpb"
9 "github.com/vbauerster/mpb/decor"
10 )
11
12 func init() {
13 rand.Seed(time.Now().UnixNano())
14 }
15
16 func main() {
17 var wg sync.WaitGroup
18 p := mpb.New(
19 mpb.WithWaitGroup(&wg),
20 // container's width.
21 mpb.WithWidth(60),
22 )
23 total, numBars := 100, 3
24 wg.Add(numBars)
25
26 for i := 0; i < numBars; i++ {
27 name := fmt.Sprintf("Bar#%d:", i)
28 bar := p.AddBar(int64(total),
29 // set BarWidth 40 for bar 1 and 2
30 mpb.OptionOnCondition(mpb.BarWidth(40), func() bool { return i > 0 }),
31 mpb.PrependDecorators(
32 // simple name decorator
33 decor.Name(name),
34 // decor.DSyncWidth bit enables column width synchronization
35 decor.Percentage(decor.WCSyncSpace),
36 ),
37 mpb.AppendDecorators(
38 // replace ETA decorator with "done" message, OnComplete event
39 decor.OnComplete(
40 // ETA decorator with ewma age of 60
41 decor.EwmaETA(decor.ET_STYLE_GO, 60), "done",
42 ),
43 ),
44 )
45 // simulating some work
46 go func() {
47 defer wg.Done()
48 max := 100 * time.Millisecond
49 for i := 0; i < total; i++ {
50 start := time.Now()
51 time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10)
52 // ewma based decorators require work duration measurement
53 bar.IncrBy(1, time.Since(start))
54 }
55 }()
56 }
57 // wait for all bars to complete and flush
58 p.Wait()
59 }
3838
3939 p := mpb.New(
4040 mpb.WithWidth(60),
41 mpb.WithFormat("[=>-|"),
4241 mpb.WithRefreshRate(180*time.Millisecond),
4342 )
4443
45 bar := p.AddBar(size,
44 bar := p.AddBar(size, mpb.BarStyle("[=>-|"),
4645 mpb.PrependDecorators(
4746 decor.CountersKibiByte("% 6.1f / % 6.1f"),
4847 ),
2222
2323 for i := 0; i < numBars; i++ {
2424 name := fmt.Sprintf("Bar#%d:", i)
25
26 var bOption mpb.BarOption
27 if i == 0 {
28 bOption = mpb.BarRemoveOnComplete()
29 }
30
3125 b := p.AddBar(int64(total), mpb.BarID(i),
32 bOption,
26 mpb.OptionOnCondition(mpb.BarRemoveOnComplete(), func() bool { return i == 0 }),
3327 mpb.PrependDecorators(
3428 decor.Name(name),
3529 decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace),
1111 p := mpb.New(
1212 // override default (80) width
1313 mpb.WithWidth(64),
14 // override default "[=>-]" format
15 mpb.WithFormat("╢▌▌░╟"),
1614 // override default 120ms refresh rate
1715 mpb.WithRefreshRate(180*time.Millisecond),
1816 )
2119 name := "Single Bar:"
2220 // adding a single bar
2321 bar := p.AddBar(int64(total),
22 // override default "[=>-]" style
23 mpb.BarStyle("╢▌▌░╟"),
2424 mpb.PrependDecorators(
2525 // display our name with one space on the right
2626 decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}),
0 package main
1
2 import (
3 "fmt"
4 "math/rand"
5 "sync"
6 "time"
7
8 "github.com/vbauerster/mpb"
9 "github.com/vbauerster/mpb/decor"
10 )
11
12 func init() {
13 rand.Seed(time.Now().UnixNano())
14 }
15
16 func main() {
17 var wg sync.WaitGroup
18 p := mpb.New(
19 mpb.WithWaitGroup(&wg),
20 mpb.WithWidth(13),
21 )
22 total, numBars := 101, 3
23 wg.Add(numBars)
24
25 for i := 0; i < numBars; i++ {
26 name := fmt.Sprintf("Bar#%d:", i)
27 var bar *mpb.Bar
28 if i == 0 {
29 bar = p.AddBar(int64(total),
30 // override default "[=>-]" style
31 mpb.BarStyle("╢▌▌░╟"),
32 mpb.PrependDecorators(
33 // simple name decorator
34 decor.Name(name),
35 ),
36 mpb.AppendDecorators(
37 // replace ETA decorator with "done" message, OnComplete event
38 decor.OnComplete(
39 // ETA decorator with ewma age of 60
40 decor.EwmaETA(decor.ET_STYLE_GO, 60), "done",
41 ),
42 ),
43 )
44 } else {
45 bar = p.AddSpinner(int64(total), mpb.SpinnerOnMiddle,
46 // override default {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} style
47 mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}),
48 mpb.PrependDecorators(
49 // simple name decorator
50 decor.Name(name),
51 ),
52 mpb.AppendDecorators(
53 // replace ETA decorator with "done" message, OnComplete event
54 decor.OnComplete(
55 // ETA decorator with ewma age of 60
56 decor.EwmaETA(decor.ET_STYLE_GO, 60), "done",
57 ),
58 ),
59 )
60 }
61
62 // simulating some work
63 go func() {
64 defer wg.Done()
65 max := 100 * time.Millisecond
66 for i := 0; i < total; i++ {
67 start := time.Now()
68 time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10)
69 // ewma based decorators require work duration measurement
70 bar.IncrBy(1, time.Since(start))
71 }
72 }()
73 }
74 // wait for all bars to complete and flush
75 p.Wait()
76 }
1919
2020 func main() {
2121 var wg sync.WaitGroup
22 p := mpb.New(mpb.WithWaitGroup(&wg))
22 p := mpb.New(
23 mpb.WithWaitGroup(&wg),
24 mpb.WithRefreshRate(50*time.Millisecond),
25 )
2326 wg.Add(totalBars)
2427
2528 for i := 0; i < totalBars; i++ {
00 package mpb
11
2 var SyncWidth = syncWidth
2 var (
3 SyncWidth = syncWidth
4 DefaultBarStyle = defaultBarStyle
5 )
00 package internal
1
2 import "math"
13
24 // Percentage is a helper function, to calculate percentage.
35 func Percentage(total, current, width int64) int64 {
57 return 0
68 }
79 p := float64(width*current) / float64(total)
8 return int64(Round(p))
10 return int64(math.Round(p))
911 }
00 package internal
11
2 import (
3 "testing"
4 )
2 import "testing"
53
64 func TestPercentage(t *testing.T) {
75 // key is barWidth
8 testSuite := map[int64]map[string]struct {
6 testSuite := map[int64][]struct {
7 name string
98 total, current, expected int64
109 }{
1110 100: {
12 "t,c,e{-1,-1,0}": {-1, -1, 0},
13 "t,c,e{0,-1,0}": {0, -1, 0},
14 "t,c,e{0,0,0}": {0, 0, 0},
15 "t,c,e{0,1,0}": {0, 1, 0},
16 "t,c,e{100,0,0}": {100, 0, 0},
17 "t,c,e{100,10,10}": {100, 10, 10},
18 "t,c,e{100,15,15}": {100, 15, 15},
19 "t,c,e{100,50,50}": {100, 50, 50},
20 "t,c,e{100,99,99}": {100, 99, 99},
21 "t,c,e{100,100,100}": {100, 100, 100},
22 "t,c,e{100,101,101}": {100, 101, 101},
23 "t,c,e{100,102,101}": {100, 102, 102},
24 "t,c,e{120,0,0}": {120, 0, 0},
25 "t,c,e{120,10,8}": {120, 10, 8},
26 "t,c,e{120,15,13}": {120, 15, 13},
27 "t,c,e{120,50,42}": {120, 50, 42},
28 "t,c,e{120,60,50}": {120, 60, 50},
29 "t,c,e{120,99,83}": {120, 99, 83},
30 "t,c,e{120,101,84}": {120, 101, 84},
31 "t,c,e{120,118,98}": {120, 118, 98},
32 "t,c,e{120,119,99}": {120, 119, 99},
33 "t,c,e{120,120,100}": {120, 120, 100},
34 "t,c,e{120,121,101}": {120, 121, 101},
35 "t,c,e{120,122,101}": {120, 122, 102},
11 {"t,c,e{-1,-1,0}", -1, -1, 0},
12 {"t,c,e{0,-1,0}", 0, -1, 0},
13 {"t,c,e{0,0,0}", 0, 0, 0},
14 {"t,c,e{0,1,0}", 0, 1, 0},
15 {"t,c,e{100,0,0}", 100, 0, 0},
16 {"t,c,e{100,10,10}", 100, 10, 10},
17 {"t,c,e{100,15,15}", 100, 15, 15},
18 {"t,c,e{100,50,50}", 100, 50, 50},
19 {"t,c,e{100,99,99}", 100, 99, 99},
20 {"t,c,e{100,100,100}", 100, 100, 100},
21 {"t,c,e{100,101,101}", 100, 101, 101},
22 {"t,c,e{100,102,101}", 100, 102, 102},
23 {"t,c,e{120,0,0}", 120, 0, 0},
24 {"t,c,e{120,10,8}", 120, 10, 8},
25 {"t,c,e{120,15,13}", 120, 15, 13},
26 {"t,c,e{120,50,42}", 120, 50, 42},
27 {"t,c,e{120,60,50}", 120, 60, 50},
28 {"t,c,e{120,99,83}", 120, 99, 83},
29 {"t,c,e{120,101,84}", 120, 101, 84},
30 {"t,c,e{120,118,98}", 120, 118, 98},
31 {"t,c,e{120,119,99}", 120, 119, 99},
32 {"t,c,e{120,120,100}", 120, 120, 100},
33 {"t,c,e{120,121,101}", 120, 121, 101},
34 {"t,c,e{120,122,101}", 120, 122, 102},
3635 },
3736 80: {
38 "t,c,e{-1,-1,0}": {-1, -1, 0},
39 "t,c,e{0,-1,0}": {0, -1, 0},
40 "t,c,e{0,0,0}": {0, 0, 0},
41 "t,c,e{0,1,0}": {0, 1, 0},
42 "t,c,e{100,0,0}": {100, 0, 0},
43 "t,c,e{100,10,8}": {100, 10, 8},
44 "t,c,e{100,15,12}": {100, 15, 12},
45 "t,c,e{100,50,40}": {100, 50, 40},
46 "t,c,e{100,99,79}": {100, 99, 79},
47 "t,c,e{100,100,80}": {100, 100, 80},
48 "t,c,e{100,101,81}": {100, 101, 81},
49 "t,c,e{100,102,82}": {100, 102, 82},
50 "t,c,e{120,0,0}": {120, 0, 0},
51 "t,c,e{120,10,7}": {120, 10, 7},
52 "t,c,e{120,15,10}": {120, 15, 10},
53 "t,c,e{120,50,33}": {120, 50, 33},
54 "t,c,e{120,60,40}": {120, 60, 40},
55 "t,c,e{120,99,66}": {120, 99, 66},
56 "t,c,e{120,101,67}": {120, 101, 67},
57 "t,c,e{120,118,79}": {120, 118, 79},
58 "t,c,e{120,119,79}": {120, 119, 79},
59 "t,c,e{120,120,80}": {120, 120, 80},
60 "t,c,e{120,121,81}": {120, 121, 81},
61 "t,c,e{120,122,81}": {120, 122, 81},
37 {"t,c,e{-1,-1,0}", -1, -1, 0},
38 {"t,c,e{0,-1,0}", 0, -1, 0},
39 {"t,c,e{0,0,0}", 0, 0, 0},
40 {"t,c,e{0,1,0}", 0, 1, 0},
41 {"t,c,e{100,0,0}", 100, 0, 0},
42 {"t,c,e{100,10,8}", 100, 10, 8},
43 {"t,c,e{100,15,12}", 100, 15, 12},
44 {"t,c,e{100,50,40}", 100, 50, 40},
45 {"t,c,e{100,99,79}", 100, 99, 79},
46 {"t,c,e{100,100,80}", 100, 100, 80},
47 {"t,c,e{100,101,81}", 100, 101, 81},
48 {"t,c,e{100,102,82}", 100, 102, 82},
49 {"t,c,e{120,0,0}", 120, 0, 0},
50 {"t,c,e{120,10,7}", 120, 10, 7},
51 {"t,c,e{120,15,10}", 120, 15, 10},
52 {"t,c,e{120,50,33}", 120, 50, 33},
53 {"t,c,e{120,60,40}", 120, 60, 40},
54 {"t,c,e{120,99,66}", 120, 99, 66},
55 {"t,c,e{120,101,67}", 120, 101, 67},
56 {"t,c,e{120,118,79}", 120, 118, 79},
57 {"t,c,e{120,119,79}", 120, 119, 79},
58 {"t,c,e{120,120,80}", 120, 120, 80},
59 {"t,c,e{120,121,81}", 120, 121, 81},
60 {"t,c,e{120,122,81}", 120, 122, 81},
6261 },
6362 }
6463
6564 for width, cases := range testSuite {
66 for name, tc := range cases {
65 for _, tc := range cases {
6766 got := Percentage(tc.total, tc.current, width)
6867 if got != tc.expected {
69 t.Errorf("width %d; %s: Expected: %d, got: %d\n", width, name, tc.expected, got)
68 t.Errorf("width %d; %s: Expected: %d, got: %d\n", width, tc.name, tc.expected, got)
7069 }
7170 }
7271 }
+0
-49
internal/round.go less more
0 package internal
1
2 import "math"
3
4 const (
5 uvone = 0x3FF0000000000000
6 mask = 0x7FF
7 shift = 64 - 11 - 1
8 bias = 1023
9 signMask = 1 << 63
10 fracMask = 1<<shift - 1
11 )
12
13 // Round returns the nearest integer, rounding half away from zero.
14 //
15 // Special cases are:
16 // Round(±0) = ±0
17 // Round(±Inf) = ±Inf
18 // Round(NaN) = NaN
19 func Round(x float64) float64 {
20 // Round is a faster implementation of:
21 //
22 // func Round(x float64) float64 {
23 // t := Trunc(x)
24 // if Abs(x-t) >= 0.5 {
25 // return t + Copysign(1, x)
26 // }
27 // return t
28 // }
29 bits := math.Float64bits(x)
30 e := uint(bits>>shift) & mask
31 if e < bias {
32 // Round abs(x) < 1 including denormals.
33 bits &= signMask // +-0
34 if e == bias-1 {
35 bits |= uvone // +-1
36 }
37 } else if e < bias+shift {
38 // Round any abs(x) >= 1 containing a fractional component [0,1).
39 //
40 // Numbers with larger exponents are returned unchanged since they
41 // must be either an integer, infinity, or NaN.
42 const half = 1 << (shift - 1)
43 e -= bias
44 bits += half >> e
45 bits &^= fracMask >> e
46 }
47 return math.Float64frombits(bits)
48 }
00 package mpb
11
22 import (
3 "context"
34 "io"
45 "sync"
56 "time"
6 "unicode/utf8"
77
88 "github.com/vbauerster/mpb/cwriter"
99 )
1010
11 // ProgressOption is a function option which changes the default behavior of
12 // progress pool, if passed to mpb.New(...ProgressOption)
11 // ProgressOption is a function option which changes the default
12 // behavior of progress pool, if passed to mpb.New(...ProgressOption).
1313 type ProgressOption func(*pState)
1414
15 // WithWaitGroup provides means to have a single joint point.
16 // If *sync.WaitGroup is provided, you can safely call just p.Wait()
17 // without calling Wait() on provided *sync.WaitGroup.
18 // Makes sense when there are more than one bar to render.
15 // WithWaitGroup provides means to have a single joint point. If
16 // *sync.WaitGroup is provided, you can safely call just p.Wait()
17 // without calling Wait() on provided *sync.WaitGroup. Makes sense
18 // when there are more than one bar to render.
1919 func WithWaitGroup(wg *sync.WaitGroup) ProgressOption {
2020 return func(s *pState) {
2121 s.uwg = wg
2222 }
2323 }
2424
25 // WithWidth overrides default width 80
25 // WithWidth sets container width. Default is 80. Bars inherit this
26 // width, as long as no BarWidth is applied.
2627 func WithWidth(w int) ProgressOption {
2728 return func(s *pState) {
2829 if w >= 0 {
3132 }
3233 }
3334
34 // WithFormat overrides default bar format "[=>-]"
35 func WithFormat(format string) ProgressOption {
36 return func(s *pState) {
37 if utf8.RuneCountInString(format) == formatLen {
38 s.format = format
39 }
40 }
41 }
42
43 // WithRefreshRate overrides default 120ms refresh rate
35 // WithRefreshRate overrides default 120ms refresh rate.
4436 func WithRefreshRate(d time.Duration) ProgressOption {
4537 return func(s *pState) {
4638 if d < 10*time.Millisecond {
5850 }
5951 }
6052
61 // WithCancel provide your cancel channel,
62 // which you plan to close at some point.
63 func WithCancel(ch <-chan struct{}) ProgressOption {
53 // WithContext provided context will be used for cancellation purposes.
54 func WithContext(ctx context.Context) ProgressOption {
6455 return func(s *pState) {
65 s.cancel = ch
56 if ctx == nil {
57 return
58 }
59 s.ctx = ctx
6660 }
6761 }
6862
69 // WithShutdownNotifier provided chanel will be closed, after all bars have been rendered.
63 // WithShutdownNotifier provided chanel will be closed, after all bars
64 // have been rendered.
7065 func WithShutdownNotifier(ch chan struct{}) ProgressOption {
7166 return func(s *pState) {
7267 s.shutdownNotifier = ch
7368 }
7469 }
7570
76 // WithOutput overrides default output os.Stdout
71 // WithOutput overrides default output os.Stdout.
7772 func WithOutput(w io.Writer) ProgressOption {
7873 return func(s *pState) {
7974 if w == nil {
+0
-15
options_go1.7.go less more
0 //+build go1.7
1
2 package mpb
3
4 import "context"
5
6 // WithContext provided context will be used for cancellation purposes
7 func WithContext(ctx context.Context) ProgressOption {
8 return func(s *pState) {
9 if ctx == nil {
10 panic("ctx must not be nil")
11 }
12 s.cancel = ctx.Done()
13 }
14 }
11
22 import (
33 "container/heap"
4 "context"
45 "fmt"
56 "io"
67 "io/ioutil"
1617 prr = 120 * time.Millisecond
1718 // default width
1819 pwidth = 80
19 // default format
20 pformat = "[=>-]"
2120 )
2221
2322 // Progress represents the container that renders Progress bars
4140 pMatrix map[int][]chan int
4241 aMatrix map[int][]chan int
4342
44 // following are provided by user
43 // following are provided/overrided by user
44 ctx context.Context
4545 uwg *sync.WaitGroup
4646 manualRefreshCh <-chan time.Time
47 cancel <-chan struct{}
4847 shutdownNotifier chan struct{}
4948 waitBars map[*Bar]*Bar
5049 debugOut io.Writer
5150 }
5251
53 // New creates new Progress instance, which orchestrates bars rendering process.
54 // Accepts mpb.ProgressOption funcs for customization.
52 // New creates new Progress instance, which orchestrates bars rendering
53 // process. Accepts mpb.ProgressOption funcs for customization.
5554 func New(options ...ProgressOption) *Progress {
5655 pq := make(priorityQueue, 0)
5756 heap.Init(&pq)
5857 s := &pState{
58 ctx: context.Background(),
5959 bHeap: &pq,
6060 width: pwidth,
61 format: pformat,
6261 cw: cwriter.New(os.Stdout),
6362 rr: prr,
6463 waitBars: make(map[*Bar]*Bar),
8382
8483 // AddBar creates a new progress bar and adds to the container.
8584 func (p *Progress) AddBar(total int64, options ...BarOption) *Bar {
85 return p.Add(total, newDefaultBarFiller(), options...)
86 }
87
88 // AddSpinner creates a new spinner bar and adds to the container.
89 func (p *Progress) AddSpinner(total int64, alignment SpinnerAlignment, options ...BarOption) *Bar {
90 filler := &spinnerFiller{
91 frames: defaultSpinnerStyle,
92 alignment: alignment,
93 }
94 return p.Add(total, filler, options...)
95 }
96
97 // Add creates a bar which renders itself by provided filler.
98 func (p *Progress) Add(total int64, filler Filler, options ...BarOption) *Bar {
99 if filler == nil {
100 filler = newDefaultBarFiller()
101 }
86102 p.wg.Add(1)
87103 result := make(chan *Bar)
88104 select {
89105 case p.operateState <- func(s *pState) {
90 options = append(options, barWidth(s.width), barFormat(s.format))
91 b := newBar(p.wg, s.idCounter, total, s.cancel, options...)
106 b := newBar(s.ctx, p.wg, filler, s.idCounter, s.width, total, options...)
92107 if b.runningBar != nil {
93108 s.waitBars[b.runningBar] = b
94109 } else {
105120 }
106121 }
107122
108 // Abort is only effective while bar progress is running,
109 // it means remove bar now without waiting for its completion.
110 // If bar is already completed, there is nothing to abort.
111 // If you need to remove bar after completion, use BarRemoveOnComplete BarOption.
123 // Abort is only effective while bar progress is running, it means
124 // remove bar now without waiting for its completion. If bar is already
125 // completed, there is nothing to abort. If you need to remove bar
126 // after completion, use BarRemoveOnComplete BarOption.
112127 func (p *Progress) Abort(b *Bar, remove bool) {
113128 select {
114129 case p.operateState <- func(s *pState) {
144159 }
145160 }
146161
147 // Wait first waits for user provided *sync.WaitGroup, if any,
148 // then waits far all bars to complete and finally shutdowns master goroutine.
149 // After this method has been called, there is no way to reuse *Progress instance.
162 // Wait first waits for user provided *sync.WaitGroup, if any, then
163 // waits far all bars to complete and finally shutdowns master goroutine.
164 // After this method has been called, there is no way to reuse *Progress
165 // instance.
150166 func (p *Progress) Wait() {
151167 if p.uwg != nil {
152168 p.uwg.Wait()
204220 defer func() {
205221 if frameReader.toShutdown {
206222 // shutdown at next flush, in other words decrement underlying WaitGroup
207 // only after the bar with completed state has been flushed.
208 // this ensures no bar ends up with less than 100% rendered.
223 // only after the bar with completed state has been flushed. this
224 // ensures no bar ends up with less than 100% rendered.
209225 s.shutdownPending = append(s.shutdownPending, bar)
210226 if replacementBar, ok := s.waitBars[bar]; ok {
211227 heap.Push(s.bHeap, replacementBar)
+0
-50
progress_go1.7_test.go less more
0 //+build go1.7
1
2 package mpb_test
3
4 import (
5 "context"
6 "io/ioutil"
7 "testing"
8 "time"
9
10 "github.com/vbauerster/mpb"
11 )
12
13 func TestWithContext(t *testing.T) {
14 ctx, cancel := context.WithCancel(context.Background())
15 shutdown := make(chan struct{})
16 p := mpb.New(
17 mpb.WithOutput(ioutil.Discard),
18 mpb.WithContext(ctx),
19 mpb.WithShutdownNotifier(shutdown),
20 )
21
22 total := 1000
23 numBars := 3
24 bars := make([]*mpb.Bar, 0, numBars)
25 for i := 0; i < numBars; i++ {
26 bar := p.AddBar(int64(total))
27 bars = append(bars, bar)
28 go func() {
29 for !bar.Completed() {
30 time.Sleep(randomDuration(40 * time.Millisecond))
31 bar.Increment()
32 }
33 }()
34 }
35
36 time.AfterFunc(100*time.Millisecond, cancel)
37
38 p.Wait()
39 for _, bar := range bars {
40 if bar.Current() >= int64(total) {
41 t.Errorf("bar %d: total = %d, current = %d\n", bar.ID(), total, bar.Current())
42 }
43 }
44 select {
45 case <-shutdown:
46 case <-time.After(100 * time.Millisecond):
47 t.Error("Progress didn't stop")
48 }
49 }
11
22 import (
33 "bytes"
4 "context"
45 "fmt"
56 "io/ioutil"
67 "math/rand"
89 "testing"
910 "time"
1011
12 "github.com/vbauerster/mpb"
1113 . "github.com/vbauerster/mpb"
1214 "github.com/vbauerster/mpb/cwriter"
1315 )
7981 p.Wait()
8082 }
8183
82 func TestWithCancel(t *testing.T) {
83 cancel := make(chan struct{})
84 func TestWithContext(t *testing.T) {
85 ctx, cancel := context.WithCancel(context.Background())
8486 shutdown := make(chan struct{})
85 p := New(
86 WithOutput(ioutil.Discard),
87 WithCancel(cancel),
88 WithShutdownNotifier(shutdown),
87 p := mpb.New(
88 mpb.WithOutput(ioutil.Discard),
89 mpb.WithContext(ctx),
90 mpb.WithRefreshRate(50*time.Millisecond),
91 mpb.WithShutdownNotifier(shutdown),
8992 )
9093
91 for i := 0; i < 2; i++ {
92 bar := p.AddBar(int64(1000), BarID(i))
94 total := 10000
95 numBars := 3
96 bars := make([]*mpb.Bar, 0, numBars)
97 for i := 0; i < numBars; i++ {
98 bar := p.AddBar(int64(total))
99 bars = append(bars, bar)
93100 go func() {
94101 for !bar.Completed() {
102 bar.Increment()
95103 time.Sleep(randomDuration(100 * time.Millisecond))
96 bar.Increment()
97104 }
98105 }()
99106 }
100107
101 time.AfterFunc(100*time.Millisecond, func() {
102 close(cancel)
103 })
108 time.Sleep(50 * time.Millisecond)
109 cancel()
104110
105111 p.Wait()
106
107112 select {
108113 case <-shutdown:
109 case <-time.After(200 * time.Millisecond):
110 t.FailNow()
111 }
112 }
113
114 func TestWithFormat(t *testing.T) {
115 var buf bytes.Buffer
116 customFormat := "╢▌▌░╟"
117 p := New(WithOutput(&buf), WithFormat(customFormat))
118 bar := p.AddBar(100, BarTrim())
119
120 for i := 0; i < 100; i++ {
121 if i == 33 {
122 p.Abort(bar, true)
123 break
124 }
125 time.Sleep(randomDuration(100 * time.Millisecond))
126 bar.Increment()
127 }
128
129 p.Wait()
130
131 lastLine := getLastLine(buf.Bytes())
132
133 for _, r := range customFormat {
134 if !bytes.ContainsRune(lastLine, r) {
135 t.Errorf("Rune %#U not found in bar\n", r)
136 }
114 case <-time.After(100 * time.Millisecond):
115 t.Error("Progress didn't stop")
137116 }
138117 }
139118
00 package mpb_test
11
22 import (
3 "bytes"
43 "io"
54 "io/ioutil"
65 "strings"
1716 cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
1817 est laborum.`
1918
19 type testReader struct {
20 io.Reader
21 called bool
22 }
23
24 func (r *testReader) Read(p []byte) (n int, err error) {
25 r.called = true
26 return r.Reader.Read(p)
27 }
28
2029 func TestProxyReader(t *testing.T) {
21 var buf bytes.Buffer
22 p := mpb.New(mpb.WithOutput(&buf))
2330
24 reader := strings.NewReader(content)
31 p := mpb.New(mpb.WithOutput(ioutil.Discard))
32
33 reader := &testReader{Reader: strings.NewReader(content)}
2534
2635 total := len(content)
27 bar := p.AddBar(100, mpb.BarTrim())
28 preader := bar.ProxyReader(reader)
36 bar := p.AddBar(100, mpb.TrimSpace())
2937
30 if _, ok := preader.(io.Closer); !ok {
31 t.Error("type assertion to io.Closer is not ok")
32 }
33
34 written, err := io.Copy(ioutil.Discard, preader)
38 written, err := io.Copy(ioutil.Discard, bar.ProxyReader(reader))
3539 if err != nil {
3640 t.Errorf("Error copying from reader: %+v\n", err)
3741 }
3842
3943 p.Wait()
4044
45 if !reader.called {
46 t.Error("Read not called")
47 }
48
4149 if written != int64(total) {
4250 t.Errorf("Expected written: %d, got: %d\n", total, written)
4351 }
0 package mpb
1
2 import (
3 "io"
4 "strings"
5 "unicode/utf8"
6
7 "github.com/vbauerster/mpb/decor"
8 )
9
10 // SpinnerAlignment enum.
11 type SpinnerAlignment int
12
13 // SpinnerAlignment kinds.
14 const (
15 SpinnerOnLeft SpinnerAlignment = iota
16 SpinnerOnMiddle
17 SpinnerOnRight
18 )
19
20 var defaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
21
22 type spinnerFiller struct {
23 frames []string
24 count uint
25 alignment SpinnerAlignment
26 }
27
28 func (s *spinnerFiller) Fill(w io.Writer, width int, stat *decor.Statistics) {
29
30 frame := s.frames[s.count%uint(len(s.frames))]
31 frameWidth := utf8.RuneCountInString(frame)
32
33 if width < frameWidth {
34 return
35 }
36
37 switch rest := width - frameWidth; s.alignment {
38 case SpinnerOnLeft:
39 io.WriteString(w, frame+strings.Repeat(" ", rest))
40 case SpinnerOnMiddle:
41 str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2)
42 io.WriteString(w, str)
43 case SpinnerOnRight:
44 io.WriteString(w, strings.Repeat(" ", rest)+frame)
45 }
46 s.count++
47 }