| 7 | 7 |
"unicode/utf8"
|
| 8 | 8 |
)
|
| 9 | 9 |
|
|
10 |
type formatRunes [5]rune
|
|
11 |
|
| 10 | 12 |
// Bar represents a progress Bar
|
| 11 | 13 |
type Bar struct {
|
| 12 | |
fillCh, emptyCh, tipCh, leftEndCh, rightEndCh chan byte
|
|
14 |
formatElementCh chan runeFormatElement
|
| 13 | 15 |
|
| 14 | 16 |
widthCh chan int
|
| 15 | 17 |
etaAlphaCh chan float64
|
|
| 41 | 43 |
}
|
| 42 | 44 |
|
| 43 | 45 |
type (
|
|
46 |
runeFormatElement struct {
|
|
47 |
char rune
|
|
48 |
index uint8
|
|
49 |
}
|
| 44 | 50 |
refill struct {
|
| 45 | |
char byte
|
|
51 |
char rune
|
| 46 | 52 |
till int64
|
| 47 | 53 |
}
|
| 48 | 54 |
state struct {
|
| 49 | |
id int
|
| 50 | |
fill byte
|
| 51 | |
empty byte
|
| 52 | |
tip byte
|
| 53 | |
leftEnd byte
|
| 54 | |
rightEnd byte
|
|
55 |
id int64
|
|
56 |
format formatRunes
|
| 55 | 57 |
etaAlpha float64
|
| 56 | 58 |
barWidth int
|
| 57 | 59 |
total int64
|
|
| 67 | 69 |
}
|
| 68 | 70 |
)
|
| 69 | 71 |
|
| 70 | |
func newBar(ctx context.Context, wg *sync.WaitGroup, total int64, width, id int) *Bar {
|
|
72 |
func newBar(ctx context.Context, wg *sync.WaitGroup, id, total int64, width int, format string) *Bar {
|
| 71 | 73 |
b := &Bar{
|
| 72 | |
fillCh: make(chan byte),
|
| 73 | |
emptyCh: make(chan byte),
|
| 74 | |
tipCh: make(chan byte),
|
| 75 | |
leftEndCh: make(chan byte),
|
| 76 | |
rightEndCh: make(chan byte),
|
| 77 | |
etaAlphaCh: make(chan float64),
|
| 78 | |
incrCh: make(chan int64, 1),
|
| 79 | |
widthCh: make(chan int),
|
| 80 | |
trimLeftCh: make(chan bool),
|
| 81 | |
trimRightCh: make(chan bool),
|
| 82 | |
refillCh: make(chan *refill),
|
| 83 | |
stateReqCh: make(chan chan state, 1),
|
| 84 | |
decoratorCh: make(chan *decorator),
|
| 85 | |
flushedCh: make(chan struct{}, 1),
|
| 86 | |
removeReqCh: make(chan struct{}),
|
| 87 | |
completeReqCh: make(chan struct{}),
|
| 88 | |
done: make(chan struct{}),
|
| 89 | |
}
|
| 90 | |
go b.server(ctx, wg, total, width, id)
|
|
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{}),
|
|
87 |
}
|
|
88 |
go b.server(ctx, wg, id, total, width, format)
|
| 91 | 89 |
return b
|
| 92 | 90 |
}
|
| 93 | 91 |
|
|
| 120 | 118 |
|
| 121 | 119 |
// SetFill sets character representing completed progress.
|
| 122 | 120 |
// Defaults to '='
|
| 123 | |
func (b *Bar) SetFill(c byte) *Bar {
|
| 124 | |
if IsClosed(b.done) {
|
| 125 | |
return b
|
| 126 | |
}
|
| 127 | |
b.fillCh <- c
|
|
121 |
func (b *Bar) SetFill(r rune) *Bar {
|
|
122 |
if IsClosed(b.done) {
|
|
123 |
return b
|
|
124 |
}
|
|
125 |
b.formatElementCh <- runeFormatElement{r, 1}
|
| 128 | 126 |
return b
|
| 129 | 127 |
}
|
| 130 | 128 |
|
| 131 | 129 |
// SetTip sets character representing tip of progress.
|
| 132 | 130 |
// Defaults to '>'
|
| 133 | |
func (b *Bar) SetTip(c byte) *Bar {
|
| 134 | |
if IsClosed(b.done) {
|
| 135 | |
return b
|
| 136 | |
}
|
| 137 | |
b.tipCh <- c
|
|
131 |
func (b *Bar) SetTip(r rune) *Bar {
|
|
132 |
if IsClosed(b.done) {
|
|
133 |
return b
|
|
134 |
}
|
|
135 |
b.formatElementCh <- runeFormatElement{r, 2}
|
| 138 | 136 |
return b
|
| 139 | 137 |
}
|
| 140 | 138 |
|
| 141 | 139 |
// SetEmpty sets character representing the empty progress
|
| 142 | 140 |
// Defaults to '-'
|
| 143 | |
func (b *Bar) SetEmpty(c byte) *Bar {
|
| 144 | |
if IsClosed(b.done) {
|
| 145 | |
return b
|
| 146 | |
}
|
| 147 | |
b.emptyCh <- c
|
|
141 |
func (b *Bar) SetEmpty(r rune) *Bar {
|
|
142 |
if IsClosed(b.done) {
|
|
143 |
return b
|
|
144 |
}
|
|
145 |
b.formatElementCh <- runeFormatElement{r, 3}
|
| 148 | 146 |
return b
|
| 149 | 147 |
}
|
| 150 | 148 |
|
| 151 | 149 |
// SetLeftEnd sets character representing the left most border
|
| 152 | 150 |
// Defaults to '['
|
| 153 | |
func (b *Bar) SetLeftEnd(c byte) *Bar {
|
| 154 | |
if IsClosed(b.done) {
|
| 155 | |
return b
|
| 156 | |
}
|
| 157 | |
b.leftEndCh <- c
|
|
151 |
func (b *Bar) SetLeftEnd(r rune) *Bar {
|
|
152 |
if IsClosed(b.done) {
|
|
153 |
return b
|
|
154 |
}
|
|
155 |
b.formatElementCh <- runeFormatElement{r, 0}
|
| 158 | 156 |
return b
|
| 159 | 157 |
}
|
| 160 | 158 |
|
| 161 | 159 |
// SetRightEnd sets character representing the right most border
|
| 162 | 160 |
// Defaults to ']'
|
| 163 | |
func (b *Bar) SetRightEnd(c byte) *Bar {
|
| 164 | |
if IsClosed(b.done) {
|
| 165 | |
return b
|
| 166 | |
}
|
| 167 | |
b.rightEndCh <- c
|
|
161 |
func (b *Bar) SetRightEnd(r rune) *Bar {
|
|
162 |
if IsClosed(b.done) {
|
|
163 |
return b
|
|
164 |
}
|
|
165 |
b.formatElementCh <- runeFormatElement{r, 4}
|
| 168 | 166 |
return b
|
| 169 | 167 |
}
|
| 170 | 168 |
|
|
| 193 | 191 |
}
|
| 194 | 192 |
|
| 195 | 193 |
// IncrWithReFill increments pb with different fill character
|
| 196 | |
func (b *Bar) IncrWithReFill(n int, c byte) {
|
|
194 |
func (b *Bar) IncrWithReFill(n int, r rune) {
|
| 197 | 195 |
if IsClosed(b.done) {
|
| 198 | 196 |
return
|
| 199 | 197 |
}
|
| 200 | 198 |
b.Incr(n)
|
| 201 | |
b.refillCh <- &refill{c, int64(n)}
|
|
199 |
b.refillCh <- &refill{r, int64(n)}
|
| 202 | 200 |
}
|
| 203 | 201 |
|
| 204 | 202 |
// GetAppenders returns slice of appender DecoratorFunc
|
|
| 221 | 219 |
}
|
| 222 | 220 |
|
| 223 | 221 |
// GetID returs id of the bar
|
| 224 | |
func (b *Bar) GetID() int {
|
|
222 |
func (b *Bar) GetID() int64 {
|
| 225 | 223 |
state := b.getState()
|
| 226 | 224 |
return state.id
|
| 227 | 225 |
}
|
|
| 290 | 288 |
return state.draw(termWidth)
|
| 291 | 289 |
}
|
| 292 | 290 |
|
| 293 | |
func (b *Bar) server(ctx context.Context, wg *sync.WaitGroup, total int64, width, id int) {
|
|
291 |
func (b *Bar) server(ctx context.Context, wg *sync.WaitGroup, id, total int64, width int, format string) {
|
| 294 | 292 |
var completed bool
|
| 295 | 293 |
timeStarted := time.Now()
|
| 296 | 294 |
blockStartTime := timeStarted
|
| 297 | 295 |
state := state{
|
| 298 | 296 |
id: id,
|
| 299 | |
fill: '=',
|
| 300 | |
empty: '-',
|
| 301 | |
tip: '>',
|
| 302 | |
leftEnd: '[',
|
| 303 | |
rightEnd: ']',
|
|
297 |
format: formatRunes{'[', '=', '>', '-', ']'},
|
| 304 | 298 |
etaAlpha: 0.25,
|
| 305 | 299 |
barWidth: width,
|
| 306 | 300 |
total: total,
|
| 307 | 301 |
}
|
| 308 | 302 |
if total <= 0 {
|
| 309 | 303 |
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 |
}
|
| 310 | 309 |
}
|
| 311 | 310 |
defer func() {
|
| 312 | 311 |
b.stop(&state)
|
|
| 342 | 341 |
}
|
| 343 | 342 |
case ch := <-b.stateReqCh:
|
| 344 | 343 |
ch <- state
|
| 345 | |
case state.fill = <-b.fillCh:
|
| 346 | |
case state.empty = <-b.emptyCh:
|
| 347 | |
case state.tip = <-b.tipCh:
|
| 348 | |
case state.leftEnd = <-b.leftEndCh:
|
| 349 | |
case state.rightEnd = <-b.rightEndCh:
|
|
344 |
case e := <-b.formatElementCh:
|
|
345 |
state.format[e.index] = e.char
|
| 350 | 346 |
case state.barWidth = <-b.widthCh:
|
| 351 | 347 |
case state.refill = <-b.refillCh:
|
| 352 | 348 |
case state.trimLeftSpace = <-b.trimLeftCh:
|
|
| 448 | 444 |
return []byte{}
|
| 449 | 445 |
}
|
| 450 | 446 |
|
|
447 |
// bar width without leftEnd and rightEnd characters
|
|
448 |
barWidth := width - 2
|
|
449 |
formatBytes := s.convertFormatRunesToBytes()
|
|
450 |
|
| 451 | 451 |
if s.simpleSpinner != nil {
|
| 452 | |
return []byte{s.leftEnd, s.simpleSpinner(), s.rightEnd}
|
| 453 | |
}
|
| 454 | |
|
| 455 | |
buf := make([]byte, width)
|
| 456 | |
completedWidth := percentage(s.total, s.current, width)
|
|
452 |
var buf []byte
|
|
453 |
for _, block := range [...][]byte{formatBytes[0], []byte{s.simpleSpinner()}, formatBytes[4]} {
|
|
454 |
buf = append(buf, block...)
|
|
455 |
}
|
|
456 |
return buf
|
|
457 |
}
|
|
458 |
|
|
459 |
completedWidth := percentage(s.total, s.current, barWidth)
|
|
460 |
|
|
461 |
buf := make([]byte, 0, width)
|
|
462 |
// append leftEnd rune
|
|
463 |
buf = append(buf, formatBytes[0]...)
|
| 457 | 464 |
|
| 458 | 465 |
if s.refill != nil {
|
| 459 | |
till := percentage(s.total, s.refill.till, width)
|
| 460 | |
for i := 1; i < till; i++ {
|
| 461 | |
buf[i] = s.refill.char
|
| 462 | |
}
|
| 463 | |
for i := till; i < completedWidth; i++ {
|
| 464 | |
buf[i] = s.fill
|
|
466 |
till := percentage(s.total, s.refill.till, barWidth)
|
|
467 |
rbytes := make([]byte, utf8.RuneLen(s.refill.char))
|
|
468 |
utf8.EncodeRune(rbytes, s.refill.char)
|
|
469 |
for i := 0; i < till; i++ {
|
|
470 |
buf = append(buf, rbytes...)
|
|
471 |
}
|
|
472 |
for i := till; i < completedWidth-1; i++ {
|
|
473 |
// append fill rune
|
|
474 |
buf = append(buf, formatBytes[1]...)
|
| 465 | 475 |
}
|
| 466 | 476 |
} else {
|
| 467 | |
for i := 1; i < completedWidth; i++ {
|
| 468 | |
buf[i] = s.fill
|
| 469 | |
}
|
| 470 | |
}
|
| 471 | |
|
| 472 | |
for i := completedWidth; i < width-1; i++ {
|
| 473 | |
buf[i] = s.empty
|
|
477 |
for i := 0; i < completedWidth-1; i++ {
|
|
478 |
// append fill rune
|
|
479 |
buf = append(buf, formatBytes[1]...)
|
|
480 |
}
|
| 474 | 481 |
}
|
| 475 | 482 |
// set tip bit
|
| 476 | |
if completedWidth > 0 && completedWidth < s.barWidth {
|
| 477 | |
buf[completedWidth-1] = s.tip
|
| 478 | |
}
|
| 479 | |
// set left and right ends bits
|
| 480 | |
buf[0], buf[width-1] = s.leftEnd, s.rightEnd
|
| 481 | |
|
|
483 |
if completedWidth > 0 && completedWidth < barWidth {
|
|
484 |
// buf[completedWidth-1] = s.tip
|
|
485 |
buf = append(buf, formatBytes[2]...)
|
|
486 |
}
|
|
487 |
|
|
488 |
for i := completedWidth + 1; i < barWidth; i++ {
|
|
489 |
// append empty rune
|
|
490 |
buf = append(buf, formatBytes[3]...)
|
|
491 |
}
|
|
492 |
// append rightEnd rune
|
|
493 |
buf = append(buf, formatBytes[4]...)
|
| 482 | 494 |
return buf
|
|
495 |
}
|
|
496 |
|
|
497 |
func (s *state) convertFormatRunesToBytes() [5][]byte {
|
|
498 |
var formatBytes [5][]byte
|
|
499 |
for i, r := range s.format {
|
|
500 |
buf := make([]byte, utf8.RuneLen(r))
|
|
501 |
utf8.EncodeRune(buf, r)
|
|
502 |
formatBytes[i] = buf
|
|
503 |
}
|
|
504 |
return formatBytes
|
| 483 | 505 |
}
|
| 484 | 506 |
|
| 485 | 507 |
func calcTimePerItemEstimate(tpie time.Duration, blockStartTime time.Time, alpha float64, items int64) time.Duration {
|