Codebase list golang-github-vbauerster-mpb / 9f047b9
mpb.AddSpinner Vladimir Bauer 7 years ago
7 changed file(s) with 349 addition(s) and 208 deletion(s). Raw diff Collapse all Expand all
+116
-135
bar.go less more
1010 "unicode/utf8"
1111
1212 "github.com/vbauerster/mpb/decor"
13 "github.com/vbauerster/mpb/internal"
1413 )
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
2714
2815 // Bar represents a progress Bar
2916 type Bar struct {
4431 shutdown chan struct{}
4532 }
4633
34 type filler interface {
35 fill(w io.Writer, width int, s *decor.Statistics)
36 }
37
4738 type (
4839 bState struct {
40 filler filler
4941 id int
5042 width int
43 alignment int
5144 total int64
5245 current int64
53 runes barRunes
54 spinner []rune
55 trimLeftSpace bool
56 trimRightSpace bool
46 trimSpace bool
5747 toComplete bool
5848 removeOnComplete bool
5949 barClearOnComplete bool
7363 runningBar *Bar
7464 }
7565 refill struct {
76 char rune
77 till int64
66 r rune
67 limit int64
7868 }
7969 frameReader struct {
8070 io.Reader
8474 }
8575 )
8676
87 func newBar(wg *sync.WaitGroup, id int, total int64, cancel <-chan struct{}, options ...BarOption) *Bar {
77 func newBar(wg *sync.WaitGroup, id, width int, total int64, cancel <-chan struct{}, options ...BarOption) *Bar {
8878 if total <= 0 {
8979 total = time.Now().Unix()
9080 }
9282 s := &bState{
9383 id: id,
9484 priority: id,
85 width: width,
9586 total: total,
9687 }
9788
10192 }
10293 }
10394
104 s.bufP = bytes.NewBuffer(make([]byte, 0, s.width))
105 s.bufB = bytes.NewBuffer(make([]byte, 0, s.width))
106 s.bufA = bytes.NewBuffer(make([]byte, 0, s.width))
95 s.bufP = bytes.NewBuffer(make([]byte, 0, width))
96 s.bufB = bytes.NewBuffer(make([]byte, 0, width))
97 s.bufA = bytes.NewBuffer(make([]byte, 0, width))
98 if s.newLineExtendFn != nil {
99 s.bufNL = bytes.NewBuffer(make([]byte, 0, width))
100 }
107101
108102 b := &Bar{
109103 priority: s.priority,
121115 b.priority = b.runningBar.priority
122116 }
123117
124 if s.newLineExtendFn != nil {
125 s.bufNL = bytes.NewBuffer(make([]byte, 0, s.width))
126 }
127
128118 go b.serve(wg, s, cancel)
129119 return b
130120 }
202192 return
203193 }
204194 b.operateState <- func(s *bState) {
205 s.refill = &refill{r, int64(n)}
206 }
207 }
208
209 // RefillBy is deprecated, use SetRefill
210 func (b *Bar) RefillBy(n int, r rune) {
211 b.SetRefill(n, r)
195 if bf, ok := s.filler.(*barFiller); ok {
196 bf.refill = &refill{r, int64(n)}
197 }
198 }
212199 }
213200
214201 // Increment is a shorthand for b.IncrBy(1).
322309 }
323310
324311 func (s *bState) draw(termWidth int) io.Reader {
325 defer s.bufA.WriteByte('\n')
326
327312 if s.panicMsg != "" {
328313 return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", termWidth), s.panicMsg))
329314 }
338323 s.bufA.WriteString(d.Decor(stat))
339324 }
340325
326 if s.barClearOnComplete && s.completeFlushed {
327 s.bufA.WriteByte('\n')
328 return io.MultiReader(s.bufP, s.bufA)
329 }
330
341331 prependCount := utf8.RuneCount(s.bufP.Bytes())
342332 appendCount := utf8.RuneCount(s.bufA.Bytes())
343333
344 if s.barClearOnComplete && s.completeFlushed {
345 return io.MultiReader(s.bufP, s.bufA)
346 }
347
348 s.fill(s.width)
349 barCount := utf8.RuneCount(s.bufB.Bytes())
350 totalCount := prependCount + barCount + appendCount
351 if spaceCount := 0; totalCount > termWidth {
352 if !s.trimLeftSpace {
353 spaceCount++
354 }
355 if !s.trimRightSpace {
356 spaceCount++
357 }
358 s.fill(termWidth - prependCount - appendCount - spaceCount)
359 }
360
334 // s.bufB.Reset()
335 if !s.trimSpace {
336 // reserve space for edge spaces
337 termWidth -= 2
338 s.bufB.WriteByte(' ')
339 }
340
341 if prependCount+s.width+appendCount > termWidth {
342 s.filler.fill(s.bufB, termWidth-prependCount-appendCount, stat)
343 } else {
344 s.filler.fill(s.bufB, s.width, stat)
345 }
346
347 if !s.trimSpace {
348 s.bufB.WriteByte(' ')
349 }
350
351 s.bufA.WriteByte('\n')
361352 return io.MultiReader(s.bufP, s.bufB, s.bufA)
362353 }
363354
364 func (s *bState) fill(width int) {
365 if len(s.spinner) != 0 {
366 s.fillSpinner(width)
367 } else {
368 s.fillBar(width)
369 }
370 }
371
372 func (s *bState) fillSpinner(width int) {
373 s.bufB.Reset()
374
375 if !s.trimLeftSpace {
376 s.bufB.WriteByte(' ')
377 }
378
379 spin := []byte(string(s.spinner[s.current%int64(len(s.spinner))]))
380 for _, b := range spin {
381 s.bufB.WriteByte(b)
382 }
383
384 for i := len(spin); i < width; i++ {
385 s.bufB.WriteRune(' ')
386 }
387 }
388
389 func (s *bState) fillBar(width int) {
390 defer func() {
391 s.bufB.WriteRune(s.runes[rRight])
392 if !s.trimRightSpace {
393 s.bufB.WriteByte(' ')
394 }
395 }()
396
397 s.bufB.Reset()
398 if !s.trimLeftSpace {
399 s.bufB.WriteByte(' ')
400 }
401 s.bufB.WriteRune(s.runes[rLeft])
402 if width <= 2 {
403 return
404 }
405
406 // bar s.width without leftEnd and rightEnd runes
407 barWidth := width - 2
408
409 completedWidth := internal.Percentage(s.total, s.current, int64(barWidth))
410
411 if s.refill != nil {
412 till := internal.Percentage(s.total, s.refill.till, int64(barWidth))
413 // append refill rune
414 var i int64
415 for i = 0; i < till; i++ {
416 s.bufB.WriteRune(s.refill.char)
417 }
418 for i = till; i < completedWidth; i++ {
419 s.bufB.WriteRune(s.runes[rFill])
420 }
421 } else {
422 var i int64
423 for i = 0; i < completedWidth; i++ {
424 s.bufB.WriteRune(s.runes[rFill])
425 }
426 }
427
428 if completedWidth < int64(barWidth) && completedWidth > 0 {
429 _, size := utf8.DecodeLastRune(s.bufB.Bytes())
430 s.bufB.Truncate(s.bufB.Len() - size)
431 s.bufB.WriteRune(s.runes[rTip])
432 }
433
434 for i := completedWidth; i < int64(barWidth); i++ {
435 s.bufB.WriteRune(s.runes[rEmpty])
436 }
437 }
355 // func (s *bState) fillSpinner(width int) {
356 // s.bufB.Reset()
357 // s.bufB.WriteByte(' ')
358
359 // if width <= 2 {
360 // s.bufB.WriteByte(' ')
361 // return
362 // }
363
364 // r := s.bType.format[s.current%int64(len(s.bType.format))]
365
366 // switch s.alignment {
367 // case alignLeft:
368 // s.bufB.WriteRune(r)
369 // s.bufB.Write(bytes.Repeat([]byte(" "), width-1))
370 // case alignMiddle:
371 // mid := width / 2
372 // mod := width % 2
373 // s.bufB.Write(bytes.Repeat([]byte(" "), mid-1+mod))
374 // s.bufB.WriteRune(r)
375 // s.bufB.Write(bytes.Repeat([]byte(" "), mid))
376 // case alignRight:
377 // s.bufB.Write(bytes.Repeat([]byte(" "), width-1))
378 // s.bufB.WriteRune(r)
379 // }
380
381 // s.bufB.WriteByte(' ')
382 // }
383
384 // func (s *bState) fillBar(width int) {
385 // s.bufB.Reset()
386 // s.bufB.WriteByte(' ')
387
388 // // don't count rLeft and rRight [brackets] with trailing spaces
389 // width -= 4
390
391 // if width <= 2 {
392 // s.bufB.WriteByte(' ')
393 // return
394 // }
395
396 // s.bufB.WriteRune(s.bType.format[rLeft])
397 // completedWidth := internal.Percentage(s.total, s.current, int64(width))
398
399 // if s.refill != nil {
400 // till := internal.Percentage(s.total, s.refill.till, int64(width))
401 // // append refill rune
402 // for i := int64(0); i < till; i++ {
403 // s.bufB.WriteRune(s.refill.char)
404 // }
405 // for i := till; i < completedWidth; i++ {
406 // s.bufB.WriteRune(s.bType.format[rFill])
407 // }
408 // } else {
409 // for i := int64(0); i < completedWidth; i++ {
410 // s.bufB.WriteRune(s.bType.format[rFill])
411 // }
412 // }
413
414 // if completedWidth < int64(width) && completedWidth > 0 {
415 // _, size := utf8.DecodeLastRune(s.bufB.Bytes())
416 // s.bufB.Truncate(s.bufB.Len() - size)
417 // s.bufB.WriteRune(s.bType.format[rTip])
418 // }
419
420 // for i := completedWidth; i < int64(width); i++ {
421 // s.bufB.WriteRune(s.bType.format[rEmpty])
422 // }
423
424 // s.bufB.WriteRune(s.bType.format[rRight])
425 // s.bufB.WriteByte(' ')
426 // }
438427
439428 func (s *bState) wSyncTable() [][]chan int {
440429 columns := make([]chan int, 0, len(s.pDecorators)+len(s.aDecorators))
467456 }
468457 }
469458
470 func strToBarRunes(format string) (array barRunes) {
471 for i, n := 0, 0; len(format) > 0; i++ {
472 array[i], n = utf8.DecodeRuneInString(format)
473 format = format[n:]
474 }
475 return
476 }
477
478459 func countLines(b []byte) int {
479460 return bytes.Count(b, []byte("\n"))
480461 }
0 package mpb
1
2 import (
3 "bytes"
4 "io"
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 )
17
18 var defaultBarStyle = []rune("[=>-]")
19
20 type barFiller struct {
21 format []rune
22 refill *refill
23 }
24
25 func (s *barFiller) fill(w io.Writer, width int, stat *decor.Statistics) {
26
27 w.Write([]byte(string(s.format[rLeft])))
28
29 // don't count rLeft and rRight [brackets]
30 width -= 2
31
32 if width <= 2 {
33 w.Write([]byte(string(s.format[rRight])))
34 return
35 }
36
37 progressWidth := internal.Percentage(stat.Total, stat.Current, int64(width))
38 needTip := progressWidth < int64(width) && progressWidth > 0
39
40 if needTip {
41 progressWidth--
42 }
43
44 if s.refill != nil {
45 // append refill rune
46 times := internal.Percentage(stat.Total, s.refill.limit, int64(width))
47 w.Write(s.repeat(s.refill.r, int(times)))
48 rest := progressWidth - times
49 w.Write(s.repeat(s.format[rFill], int(rest)))
50 } else {
51 w.Write(s.repeat(s.format[rFill], int(progressWidth)))
52 }
53
54 if needTip {
55 w.Write([]byte(string(s.format[rTip])))
56 progressWidth++
57 }
58
59 rest := int64(width) - progressWidth
60 w.Write(s.repeat(s.format[rEmpty], int(rest)))
61 w.Write([]byte(string(s.format[rRight])))
62 }
63
64 func (s *barFiller) repeat(r rune, count int) []byte {
65 return bytes.Repeat([]byte(string(r)), count)
66 }
11
22 import (
33 "io"
4 "unicode/utf8"
45
56 "github.com/vbauerster/mpb/decor"
67 )
78
8 // BarOption is a function option which changes the default behavior of a bar,
9 // if passed to p.AddBar(int64, ...BarOption)
9 // BarOption is a function option which changes the default behavior of a bar.
1010 type BarOption func(*bState)
1111
12 // AppendDecorators let you inject decorators to the bar's right side
12 // AppendDecorators let you inject decorators to the bar's right side.
1313 func AppendDecorators(appenders ...decor.Decorator) BarOption {
1414 return func(s *bState) {
1515 for _, decorator := range appenders {
2424 }
2525 }
2626
27 // PrependDecorators let you inject decorators to the bar's left side
27 // PrependDecorators let you inject decorators to the bar's left side.
2828 func PrependDecorators(prependers ...decor.Decorator) BarOption {
2929 return func(s *bState) {
3030 for _, decorator := range prependers {
3939 }
4040 }
4141
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
42 // BarID sets bar id.
6543 func BarID(id int) BarOption {
6644 return func(s *bState) {
6745 s.id = id
11088 }
11189 }
11290
113 func barSpinner(spinner string) BarOption {
91 // BarStyle sets custom bar style.
92 func BarStyle(style string) BarOption {
11493 return func(s *bState) {
115 s.spinner = []rune(spinner)
94 if style == "" {
95 return
96 }
97 if bf, ok := s.filler.(*barFiller); ok {
98 if !utf8.ValidString(style) {
99 panic("invalid style string")
100 }
101 defaultFormat := bf.format
102 bf.format = []rune(style)
103 if len(bf.format) < 5 {
104 bf.format = defaultFormat
105 }
106 }
116107 }
117108 }
118109
119 func barWidth(w int) BarOption {
110 // SpinnerStyle sets custom Spinner style.
111 func SpinnerStyle(frames []string) BarOption {
120112 return func(s *bState) {
121 s.width = w
113 if len(frames) == 0 {
114 return
115 }
116 if bf, ok := s.filler.(*spinnerFiller); ok {
117 bf.frames = frames
118 }
122119 }
123120 }
124121
125 func barFormat(format string) BarOption {
122 // AlignLeft align spinner on left, default.
123 // Applicable fo spinner bar type only.
124 // func AlignLeft() BarOption {
125 // return func(s *bState) {
126 // if bf, ok := s.filler.(*spinnerFiller); ok {
127 // bf.alignment = alignLeft
128 // }
129 // }
130 // }
131
132 // AlignMiddle align spinner on the middle.
133 // Applicable fo spinner bar type only.
134 // func AlignMiddle() BarOption {
135 // return func(s *bState) {
136 // if bf, ok := s.filler.(*spinnerFiller); ok {
137 // bf.alignment = alignMiddle
138 // }
139 // }
140 // }
141
142 // AlignRight align spinner on right.
143 // Applicable fo spinner bar type only.
144 // func AlignRight() BarOption {
145 // return func(s *bState) {
146 // if bf, ok := s.filler.(*spinnerFiller); ok {
147 // bf.alignment = alignRight
148 // }
149 // }
150 // }
151
152 func TrimSpace() BarOption {
126153 return func(s *bState) {
127 s.runes = strToBarRunes(format)
154 s.trimSpace = true
128155 }
129156 }
1717 var wg sync.WaitGroup
1818 p := mpb.New(
1919 mpb.WithWaitGroup(&wg),
20 mpb.WithSpinner("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"),
20 mpb.WithWidth(13),
2121 )
22 total, numBars := 100, 3
22 total, numBars := 101, 3
2323 wg.Add(numBars)
2424
2525 for i := 0; i < numBars; i++ {
2626 name := fmt.Sprintf("Bar#%d:", i)
27 bar := p.AddBar(int64(total),
28 mpb.PrependDecorators(
29 // simple name decorator
30 decor.Name(name),
31 ),
32 mpb.AppendDecorators(
33 // replace ETA decorator with "done" message, OnComplete event
34 decor.OnComplete(
35 // ETA decorator with ewma age of 60
36 decor.EwmaETA(decor.ET_STYLE_GO, 60), "done",
27 var bar *mpb.Bar
28 if i == 0 {
29 bar = p.AddBar(int64(total),
30 mpb.BarStyle("╢▌▌░╟"),
31 mpb.PrependDecorators(
32 // simple name decorator
33 decor.Name(name),
3734 ),
38 ),
39 )
35 mpb.AppendDecorators(
36 // replace ETA decorator with "done" message, OnComplete event
37 decor.OnComplete(
38 // ETA decorator with ewma age of 60
39 decor.EwmaETA(decor.ET_STYLE_GO, 60), "done",
40 ),
41 ),
42 )
43 } else {
44 bar = p.AddSpinner(int64(total), mpb.SpinnerOnMiddle,
45 // mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}),
46 mpb.PrependDecorators(
47 // simple name decorator
48 decor.Name(name),
49 ),
50 mpb.AppendDecorators(
51 // replace ETA decorator with "done" message, OnComplete event
52 decor.OnComplete(
53 // ETA decorator with ewma age of 60
54 decor.EwmaETA(decor.ET_STYLE_GO, 60), "done",
55 ),
56 ),
57 )
58 }
59
4060 // simulating some work
4161 go func() {
4262 defer wg.Done()
33 "io"
44 "sync"
55 "time"
6 "unicode/utf8"
76
87 "github.com/vbauerster/mpb/cwriter"
98 )
2726 return func(s *pState) {
2827 if w >= 0 {
2928 s.width = w
30 }
31 }
32 }
33
34 // WithSpinner overrides default bar format to use a spinner
35 func WithSpinner(spinner string) ProgressOption {
36 return func(s *pState) {
37 s.spinner = spinner
38 }
39 }
40
41 // WithFormat overrides default bar format "[=>-]"
42 func WithFormat(format string) ProgressOption {
43 return func(s *pState) {
44 if utf8.RuneCountInString(format) == formatLen {
45 s.format = format
4629 }
4730 }
4831 }
1616 prr = 120 * time.Millisecond
1717 // default width
1818 pwidth = 80
19 // default format
20 pformat = "[=>-]"
2119 )
2220
2321 // Progress represents the container that renders Progress bars
3634 idCounter int
3735 width int
3836 format string
39 spinner string
4037 rr time.Duration
4138 cw *cwriter.Writer
4239 pMatrix map[int][]chan int
5956 s := &pState{
6057 bHeap: &pq,
6158 width: pwidth,
62 format: pformat,
6359 cw: cwriter.New(os.Stdout),
6460 rr: prr,
6561 waitBars: make(map[*Bar]*Bar),
8480
8581 // AddBar creates a new progress bar and adds to the container.
8682 func (p *Progress) AddBar(total int64, options ...BarOption) *Bar {
83 // make sure filler is initialized first
84 args := []BarOption{
85 func(s *bState) {
86 s.filler = &barFiller{
87 format: defaultBarStyle,
88 }
89 },
90 }
91 args = append(args, options...)
92 return p.add(total, args...)
93 }
94
95 func (p *Progress) AddSpinner(total int64, alignment SpinnerAlignment, options ...BarOption) *Bar {
96 // make sure filler is initialized first
97 args := []BarOption{
98 func(s *bState) {
99 s.filler = &spinnerFiller{
100 frames: defaultSpinnerStyle,
101 alignment: alignment,
102 }
103 },
104 }
105 args = append(args, options...)
106 return p.add(total, args...)
107 }
108
109 func (p *Progress) add(total int64, options ...BarOption) *Bar {
87110 p.wg.Add(1)
88111 result := make(chan *Bar)
89112 select {
90113 case p.operateState <- func(s *pState) {
91 options = append(options, barWidth(s.width), barFormat(s.format))
92 if s.spinner != "" {
93 options = append(options, barSpinner(s.spinner))
94 }
95 b := newBar(p.wg, s.idCounter, total, s.cancel, options...)
114 b := newBar(p.wg, s.idCounter, s.width, total, s.cancel, options...)
96115 if b.runningBar != nil {
97116 s.waitBars[b.runningBar] = b
98117 } else {
0 package mpb
1
2 import (
3 "io"
4 "strings"
5 "unicode/utf8"
6
7 "github.com/vbauerster/mpb/decor"
8 )
9
10 type SpinnerAlignment int
11
12 const (
13 SpinnerOnLeft SpinnerAlignment = iota
14 SpinnerOnMiddle
15 SpinnerOnRight
16 )
17
18 var defaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
19
20 type spinnerFiller struct {
21 frames []string
22 alignment SpinnerAlignment
23 }
24
25 func (s *spinnerFiller) fill(w io.Writer, width int, stat *decor.Statistics) {
26
27 frame := s.frames[stat.Current%int64(len(s.frames))]
28 frameWidth := utf8.RuneCountInString(frame)
29
30 if width < frameWidth {
31 return
32 }
33
34 switch rest := width - frameWidth; s.alignment {
35 case SpinnerOnLeft:
36 io.WriteString(w, frame+strings.Repeat(" ", rest))
37 case SpinnerOnMiddle:
38 str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2)
39 io.WriteString(w, str)
40 case SpinnerOnRight:
41 io.WriteString(w, strings.Repeat(" ", rest)+frame)
42 }
43 }