Codebase list golang-github-vbauerster-mpb / dbd699f
refactoring Vladimir Bauer 9 years ago
3 changed file(s) with 106 addition(s) and 121 deletion(s). Raw diff Collapse all Expand all
+96
-114
bar.go less more
77 "unicode/utf8"
88 )
99
10 type formatRunes [5]rune
10 type formatRunes [numFmtRunes]rune
1111
1212 // Bar represents a progress Bar
1313 type Bar struct {
14 formatElementCh chan runeFormatElement
15
16 widthCh chan int
17 etaAlphaCh chan float64
18
14 widthSetCh chan int
15 widthReqCh chan chan int
16 stateReqCh chan chan state
17 formatCh chan string
18 etaAlphaCh chan float64
1919 incrCh chan int64
2020 trimLeftCh chan bool
2121 trimRightCh chan bool
2222 refillCh chan *refill
23 stateReqCh chan chan state
2423 decoratorCh chan *decorator
2524 flushedCh chan struct{}
2625 removeReqCh chan struct{}
2726 completeReqCh chan struct{}
2827 done chan struct{}
2928
30 lastState state
29 // follawing are used after (*Bar.done) is closed
30 width int
31 state state
3132 }
3233
3334 // Statistics represents statistics of the progress bar.
5556 id int
5657 format formatRunes
5758 etaAlpha float64
58 barWidth int
5959 total int64
6060 current int64
6161 trimLeftSpace bool
7171
7272 func newBar(ctx context.Context, wg *sync.WaitGroup, id int, total int64, width int, format string) *Bar {
7373 b := &Bar{
74 formatElementCh: make(chan runeFormatElement),
75 etaAlphaCh: make(chan float64),
76 incrCh: make(chan int64, 1),
77 widthCh: make(chan int),
78 trimLeftCh: make(chan bool),
79 trimRightCh: make(chan bool),
80 refillCh: make(chan *refill),
81 stateReqCh: make(chan chan state, 1),
82 decoratorCh: make(chan *decorator),
83 flushedCh: make(chan struct{}, 1),
84 removeReqCh: make(chan struct{}),
85 completeReqCh: make(chan struct{}),
86 done: make(chan struct{}),
74 formatCh: make(chan string),
75 etaAlphaCh: make(chan float64),
76 incrCh: make(chan int64, 1),
77 widthSetCh: make(chan int),
78 widthReqCh: make(chan chan int),
79 trimLeftCh: make(chan bool),
80 trimRightCh: make(chan bool),
81 refillCh: make(chan *refill),
82 stateReqCh: make(chan chan state, 1),
83 decoratorCh: make(chan *decorator),
84 flushedCh: make(chan struct{}, 1),
85 removeReqCh: make(chan struct{}),
86 completeReqCh: make(chan struct{}),
87 done: make(chan struct{}),
8788 }
8889 go b.server(ctx, wg, id, total, width, format)
8990 return b
9091 }
9192
92 // SetWidth sets width of the bar
93 // SetWidth overrides width of individual bar
9394 func (b *Bar) SetWidth(n int) *Bar {
9495 if n < 2 || IsClosed(b.done) {
9596 return b
9697 }
97 b.widthCh <- n
98 return b
98 b.widthSetCh <- n
99 return b
100 }
101
102 func (b *Bar) GetWidth() int {
103 if IsClosed(b.done) {
104 return b.width
105 }
106 ch := make(chan int, 1)
107 b.widthReqCh <- ch
108 return <-ch
99109 }
100110
101111 // TrimLeftSpace removes space befor LeftEnd charater
116126 return b
117127 }
118128
119 // SetFill sets character representing completed progress.
120 // Defaults to '='
121 func (b *Bar) SetFill(r rune) *Bar {
122 if IsClosed(b.done) {
123 return b
124 }
125 b.formatElementCh <- runeFormatElement{r, 1}
126 return b
127 }
128
129 // SetTip sets character representing tip of progress.
130 // Defaults to '>'
131 func (b *Bar) SetTip(r rune) *Bar {
132 if IsClosed(b.done) {
133 return b
134 }
135 b.formatElementCh <- runeFormatElement{r, 2}
136 return b
137 }
138
139 // SetEmpty sets character representing the empty progress
140 // Defaults to '-'
141 func (b *Bar) SetEmpty(r rune) *Bar {
142 if IsClosed(b.done) {
143 return b
144 }
145 b.formatElementCh <- runeFormatElement{r, 3}
146 return b
147 }
148
149 // SetLeftEnd sets character representing the left most border
150 // Defaults to '['
151 func (b *Bar) SetLeftEnd(r rune) *Bar {
152 if IsClosed(b.done) {
153 return b
154 }
155 b.formatElementCh <- runeFormatElement{r, 0}
156 return b
157 }
158
159 // SetRightEnd sets character representing the right most border
160 // Defaults to ']'
161 func (b *Bar) SetRightEnd(r rune) *Bar {
162 if IsClosed(b.done) {
163 return b
164 }
165 b.formatElementCh <- runeFormatElement{r, 4}
129 // Format overrides format of individual bar
130 func (b *Bar) Format(format string) *Bar {
131 if utf8.RuneCountInString(format) != numFmtRunes || IsClosed(b.done) {
132 return b
133 }
134 b.formatCh <- format
166135 return b
167136 }
168137
214183 // GetStatistics returs *Statistics, which contains information like Tottal,
215184 // Current, TimeElapsed and TimePerItemEstimate
216185 func (b *Bar) GetStatistics() *Statistics {
217 state := b.getState()
218 return state.newStat()
186 s := b.getState()
187 return newStatistics(&s)
219188 }
220189
221190 // GetID returs id of the bar
276245
277246 func (b *Bar) getState() state {
278247 if IsClosed(b.done) {
279 return b.lastState
248 return b.state
280249 }
281250 ch := make(chan state, 1)
282251 b.stateReqCh <- ch
284253 }
285254
286255 func (b *Bar) bytes(termWidth int) []byte {
287 state := b.getState()
288 return state.draw(termWidth)
256 s := b.getState()
257 return draw(&s, b.GetWidth(), termWidth)
289258 }
290259
291260 func (b *Bar) server(ctx context.Context, wg *sync.WaitGroup, id int, total int64, width int, format string) {
296265 id: id,
297266 format: formatRunes{'[', '=', '>', '-', ']'},
298267 etaAlpha: 0.25,
299 barWidth: width,
300268 total: total,
301269 }
302270 if total <= 0 {
303271 state.simpleSpinner = getSpinner()
304 } else if format != "" {
305 for i, n := 0, 0; len(format) > 0; i++ {
306 state.format[i], n = utf8.DecodeRuneInString(format)
307 format = format[n:]
308 }
272 } else {
273 state.updateFormat(format)
309274 }
310275 defer func() {
311 b.stop(&state)
276 b.stop(&state, width)
312277 wg.Done()
313278 }()
314279 for {
341306 }
342307 case ch := <-b.stateReqCh:
343308 ch <- state
344 case e := <-b.formatElementCh:
345 state.format[e.index] = e.char
346 case state.barWidth = <-b.widthCh:
309 case ch := <-b.widthReqCh:
310 ch <- width
311 case width = <-b.widthSetCh:
312 case format := <-b.formatCh:
313 state.updateFormat(format)
347314 case state.refill = <-b.refillCh:
348315 case state.trimLeftSpace = <-b.trimLeftCh:
349316 case state.trimRightSpace = <-b.trimRightCh:
361328 }
362329 }
363330
364 func (b *Bar) stop(s *state) {
365 b.lastState = *s
331 func (b *Bar) stop(s *state, width int) {
332 b.state = *s
333 b.width = width
366334 close(b.done)
367335 }
368336
380348 b.removeReqCh <- struct{}{}
381349 }
382350
383 func (s *state) newStat() *Statistics {
384 return &Statistics{
385 Total: s.total,
386 Current: s.current,
387 TimeElapsed: s.timeElapsed,
388 TimePerItemEstimate: s.timePerItem,
389 }
390 }
391
392 func (s *state) draw(termWidth int) []byte {
351 func (s *state) updateFormat(format string) {
352 if format == "" {
353 return
354 }
355 for i, n := 0, 0; len(format) > 0; i++ {
356 s.format[i], n = utf8.DecodeRuneInString(format)
357 format = format[n:]
358 }
359 }
360
361 func draw(s *state, barWidth, termWidth int) []byte {
393362 if termWidth <= 0 {
394 termWidth = s.barWidth
395 }
396
397 stat := s.newStat()
363 termWidth = barWidth
364 }
365
366 stat := newStatistics(s)
398367
399368 // render append functions to the right of the bar
400369 var appendBlock []byte
408377 prependBlock = append(prependBlock, []byte(f(stat))...)
409378 }
410379
411 barBlock := s.fillBar(s.barWidth)
380 barBlock := fillBar(s, barWidth)
412381 prependCount := utf8.RuneCount(prependBlock)
413382 barCount := utf8.RuneCount(barBlock)
414383 appendCount := utf8.RuneCount(appendBlock)
428397 totalCount := prependCount + barCount + appendCount
429398 if totalCount > termWidth {
430399 newWidth := termWidth - prependCount - appendCount
431 barBlock = s.fillBar(newWidth)
400 barBlock = fillBar(s, newWidth)
432401 }
433402
434403 buf := make([]byte, 0, termWidth)
439408 return buf
440409 }
441410
442 func (s *state) fillBar(width int) []byte {
411 func fillBar(s *state, width int) []byte {
443412 if width < 2 {
444413 return []byte{}
445414 }
446415
447 // bar width without leftEnd and rightEnd characters
416 // bar width without leftEnd and rightEnd runes
448417 barWidth := width - 1
449 formatBytes := s.convertFormatRunesToBytes()
418 formatBytes := convertFmtRunesToBytes(s.format)
450419
451420 if s.simpleSpinner != nil {
452421 var buf []byte
466435 till := percentage(s.total, s.refill.till, barWidth)
467436 rbytes := make([]byte, utf8.RuneLen(s.refill.char))
468437 utf8.EncodeRune(rbytes, s.refill.char)
438 // append refill rune
469439 for i := 0; i < till; i++ {
470440 buf = append(buf, rbytes...)
471441 }
442 // append fill rune
472443 for i := till; i < completedWidth-1; i++ {
473 // append fill rune
474444 buf = append(buf, formatBytes[1]...)
475445 }
476446 } else {
447 // append fill rune
477448 for i := 0; i < completedWidth-1; i++ {
478 // append fill rune
479449 buf = append(buf, formatBytes[1]...)
480450 }
481451 }
452
482453 // set tip bit
483454 if completedWidth > 0 && completedWidth < barWidth {
484455 buf = append(buf, formatBytes[2]...)
485456 }
486457
458 // append empty rune
487459 for i := completedWidth + 1; i < barWidth; i++ {
488 // append empty rune
489460 buf = append(buf, formatBytes[3]...)
490461 }
462
491463 // append rightEnd rune
492464 buf = append(buf, formatBytes[4]...)
465
493466 return buf
494467 }
495468
496 func (s *state) convertFormatRunesToBytes() [5][]byte {
497 var formatBytes [5][]byte
498 for i, r := range s.format {
469 func newStatistics(s *state) *Statistics {
470 return &Statistics{
471 Total: s.total,
472 Current: s.current,
473 TimeElapsed: s.timeElapsed,
474 TimePerItemEstimate: s.timePerItem,
475 }
476 }
477
478 func convertFmtRunesToBytes(format formatRunes) [numFmtRunes][]byte {
479 var formatBytes [numFmtRunes][]byte
480 for i, r := range format {
499481 buf := make([]byte, utf8.RuneLen(r))
500482 utf8.EncodeRune(buf, r)
501483 formatBytes[i] = buf
4949 }
5050
5151 for _, test := range tests {
52 s.barWidth = test.barWidth
53 got := s.draw(test.termWidth)
52 got := draw(s, test.barWidth, test.termWidth)
5453 if !reflect.DeepEqual(test.want, got) {
5554 t.Errorf("Want: %q, Got: %q\n", test.want, got)
5655 }
5756 }
5857 }
5958
60 func newTestState(total, current int64) *state {
61 return &state{
59 func newTestState(total, current int64) state {
60 return state{
6261 format: formatRunes{'[', '=', '>', '-', ']'},
6362 total: total,
6463 current: current,
4646 barRemove
4747 )
4848
49 // default RefreshRate
50 const rr = 100
49 const (
50 // default RefreshRate
51 rr = 100
52 // number of format runes for bar
53 numFmtRunes = 5
54 )
5155
5256 // Progress represents the container that renders Progress bars
5357 type Progress struct {
176180 // Format sets custom format for underlying bar(s).
177181 // The default one is "[=>-]"
178182 func (p *Progress) Format(format string) *Progress {
179 if utf8.RuneCountInString(format) != 5 {
183 if utf8.RuneCountInString(format) != numFmtRunes {
180184 return p
181185 }
182186 p.format = format