| 7 | 7 |
"unicode/utf8"
|
| 8 | 8 |
)
|
| 9 | 9 |
|
| 10 | |
type formatRunes [numFmtRunes]rune
|
|
10 |
const (
|
|
11 |
rLeft = iota
|
|
12 |
rFill
|
|
13 |
rTip
|
|
14 |
rEmpty
|
|
15 |
rRight
|
|
16 |
)
|
|
17 |
|
|
18 |
type barFmtRunes [numFmtRunes]rune
|
|
19 |
type barFmtBytes [numFmtRunes][]byte
|
| 11 | 20 |
|
| 12 | 21 |
// Bar represents a progress Bar
|
| 13 | 22 |
type Bar struct {
|
| 14 | |
widthSetCh chan int
|
| 15 | |
widthReqCh chan chan int
|
| 16 | 23 |
stateReqCh chan chan state
|
|
24 |
widthCh chan int
|
| 17 | 25 |
formatCh chan string
|
| 18 | 26 |
etaAlphaCh chan float64
|
| 19 | 27 |
incrCh chan int64
|
|
| 54 | 62 |
}
|
| 55 | 63 |
state struct {
|
| 56 | 64 |
id int
|
| 57 | |
format formatRunes
|
|
65 |
width int
|
|
66 |
format barFmtRunes
|
| 58 | 67 |
etaAlpha float64
|
| 59 | 68 |
total int64
|
| 60 | 69 |
current int64
|
|
| 71 | 80 |
|
| 72 | 81 |
func newBar(ctx context.Context, wg *sync.WaitGroup, id int, total int64, width int, format string) *Bar {
|
| 73 | 82 |
b := &Bar{
|
|
83 |
stateReqCh: make(chan chan state, 1),
|
|
84 |
widthCh: make(chan int),
|
| 74 | 85 |
formatCh: make(chan string),
|
| 75 | 86 |
etaAlphaCh: make(chan float64),
|
| 76 | 87 |
incrCh: make(chan int64, 1),
|
| 77 | |
widthSetCh: make(chan int),
|
| 78 | |
widthReqCh: make(chan chan int),
|
| 79 | 88 |
trimLeftCh: make(chan bool),
|
| 80 | 89 |
trimRightCh: make(chan bool),
|
| 81 | 90 |
refillCh: make(chan *refill),
|
| 82 | |
stateReqCh: make(chan chan state, 1),
|
| 83 | 91 |
decoratorCh: make(chan *decorator),
|
| 84 | 92 |
flushedCh: make(chan struct{}, 1),
|
| 85 | 93 |
removeReqCh: make(chan struct{}),
|
|
| 95 | 103 |
if n < 2 || IsClosed(b.done) {
|
| 96 | 104 |
return b
|
| 97 | 105 |
}
|
| 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
|
|
106 |
b.widthCh <- n
|
|
107 |
return b
|
| 109 | 108 |
}
|
| 110 | 109 |
|
| 111 | 110 |
// TrimLeftSpace removes space befor LeftEnd charater
|
|
| 254 | 253 |
|
| 255 | 254 |
func (b *Bar) bytes(termWidth int) []byte {
|
| 256 | 255 |
s := b.getState()
|
| 257 | |
return draw(&s, b.GetWidth(), termWidth)
|
|
256 |
return draw(&s, termWidth)
|
| 258 | 257 |
}
|
| 259 | 258 |
|
| 260 | 259 |
func (b *Bar) server(ctx context.Context, wg *sync.WaitGroup, id int, total int64, width int, format string) {
|
| 261 | 260 |
var completed bool
|
| 262 | 261 |
timeStarted := time.Now()
|
| 263 | 262 |
blockStartTime := timeStarted
|
| 264 | |
state := state{
|
|
263 |
barState := state{
|
| 265 | 264 |
id: id,
|
| 266 | |
format: formatRunes{'[', '=', '>', '-', ']'},
|
|
265 |
width: width,
|
|
266 |
format: barFmtRunes{'[', '=', '>', '-', ']'},
|
| 267 | 267 |
etaAlpha: 0.25,
|
| 268 | 268 |
total: total,
|
| 269 | 269 |
}
|
| 270 | 270 |
if total <= 0 {
|
| 271 | |
state.simpleSpinner = getSpinner()
|
|
271 |
barState.simpleSpinner = getSpinner()
|
| 272 | 272 |
} else {
|
| 273 | |
state.updateFormat(format)
|
|
273 |
barState.updateFormat(format)
|
| 274 | 274 |
}
|
| 275 | 275 |
defer func() {
|
| 276 | |
b.stop(&state, width)
|
|
276 |
b.stop(&barState, width)
|
| 277 | 277 |
wg.Done()
|
| 278 | 278 |
}()
|
| 279 | 279 |
for {
|
| 280 | 280 |
select {
|
| 281 | 281 |
case i := <-b.incrCh:
|
| 282 | |
n := state.current + i
|
|
282 |
n := barState.current + i
|
| 283 | 283 |
if total > 0 && n > total {
|
| 284 | |
state.current = total
|
|
284 |
barState.current = total
|
| 285 | 285 |
completed = true
|
| 286 | 286 |
blockStartTime = time.Now()
|
| 287 | 287 |
break // break out of select
|
| 288 | 288 |
}
|
| 289 | |
state.timeElapsed = time.Since(timeStarted)
|
| 290 | |
state.timePerItem = calcTimePerItemEstimate(state.timePerItem, blockStartTime, state.etaAlpha, i)
|
|
289 |
barState.timeElapsed = time.Since(timeStarted)
|
|
290 |
barState.timePerItem = calcTimePerItemEstimate(barState.timePerItem, blockStartTime, barState.etaAlpha, i)
|
| 291 | 291 |
if n == total {
|
| 292 | 292 |
completed = true
|
| 293 | 293 |
}
|
| 294 | |
state.current = n
|
|
294 |
barState.current = n
|
| 295 | 295 |
blockStartTime = time.Now()
|
| 296 | 296 |
case d := <-b.decoratorCh:
|
| 297 | 297 |
switch d.kind {
|
| 298 | 298 |
case decAppend:
|
| 299 | |
state.appendFuncs = append(state.appendFuncs, d.f)
|
|
299 |
barState.appendFuncs = append(barState.appendFuncs, d.f)
|
| 300 | 300 |
case decAppendZero:
|
| 301 | |
state.appendFuncs = nil
|
|
301 |
barState.appendFuncs = nil
|
| 302 | 302 |
case decPrepend:
|
| 303 | |
state.prependFuncs = append(state.prependFuncs, d.f)
|
|
303 |
barState.prependFuncs = append(barState.prependFuncs, d.f)
|
| 304 | 304 |
case decPrependZero:
|
| 305 | |
state.prependFuncs = nil
|
|
305 |
barState.prependFuncs = nil
|
| 306 | 306 |
}
|
| 307 | 307 |
case ch := <-b.stateReqCh:
|
| 308 | |
ch <- state
|
| 309 | |
case ch := <-b.widthReqCh:
|
| 310 | |
ch <- width
|
| 311 | |
case width = <-b.widthSetCh:
|
|
308 |
ch <- barState
|
| 312 | 309 |
case format := <-b.formatCh:
|
| 313 | |
state.updateFormat(format)
|
| 314 | |
case state.refill = <-b.refillCh:
|
| 315 | |
case state.trimLeftSpace = <-b.trimLeftCh:
|
| 316 | |
case state.trimRightSpace = <-b.trimRightCh:
|
|
310 |
barState.updateFormat(format)
|
|
311 |
case barState.width = <-b.widthCh:
|
|
312 |
case barState.refill = <-b.refillCh:
|
|
313 |
case barState.trimLeftSpace = <-b.trimLeftCh:
|
|
314 |
case barState.trimRightSpace = <-b.trimRightCh:
|
| 317 | 315 |
case <-b.flushedCh:
|
| 318 | 316 |
if completed {
|
| 319 | 317 |
return
|
|
| 358 | 356 |
}
|
| 359 | 357 |
}
|
| 360 | 358 |
|
| 361 | |
func draw(s *state, barWidth, termWidth int) []byte {
|
|
359 |
func draw(s *state, termWidth int) []byte {
|
| 362 | 360 |
if termWidth <= 0 {
|
| 363 | |
termWidth = barWidth
|
|
361 |
termWidth = s.width
|
| 364 | 362 |
}
|
| 365 | 363 |
|
| 366 | 364 |
stat := newStatistics(s)
|
|
| 377 | 375 |
prependBlock = append(prependBlock, []byte(f(stat))...)
|
| 378 | 376 |
}
|
| 379 | 377 |
|
| 380 | |
barBlock := fillBar(s, barWidth)
|
| 381 | 378 |
prependCount := utf8.RuneCount(prependBlock)
|
| 382 | |
barCount := utf8.RuneCount(barBlock)
|
| 383 | 379 |
appendCount := utf8.RuneCount(appendBlock)
|
| 384 | 380 |
|
| 385 | 381 |
var leftSpace, rightSpace []byte
|
|
| 394 | 390 |
rightSpace = space
|
| 395 | 391 |
}
|
| 396 | 392 |
|
|
393 |
var barBlock []byte
|
|
394 |
buf := make([]byte, 0, termWidth)
|
|
395 |
fmtBytes := convertFmtRunesToBytes(s.format)
|
|
396 |
|
|
397 |
if s.simpleSpinner != nil {
|
|
398 |
for _, block := range [...][]byte{fmtBytes[rLeft], []byte{s.simpleSpinner()}, fmtBytes[rRight]} {
|
|
399 |
barBlock = append(barBlock, block...)
|
|
400 |
}
|
|
401 |
return concatenateBlocks(buf, prependBlock, leftSpace, barBlock, rightSpace, appendBlock)
|
|
402 |
}
|
|
403 |
|
|
404 |
barBlock = fillBar(s.total, s.current, s.width, fmtBytes, s.refill)
|
|
405 |
barCount := utf8.RuneCount(barBlock)
|
| 397 | 406 |
totalCount := prependCount + barCount + appendCount
|
| 398 | 407 |
if totalCount > termWidth {
|
| 399 | 408 |
newWidth := termWidth - prependCount - appendCount
|
| 400 | |
barBlock = fillBar(s, newWidth)
|
| 401 | |
}
|
| 402 | |
|
| 403 | |
buf := make([]byte, 0, termWidth)
|
| 404 | |
for _, block := range [...][]byte{prependBlock, leftSpace, barBlock, rightSpace, appendBlock} {
|
|
409 |
barBlock = fillBar(s.total, s.current, newWidth, fmtBytes, s.refill)
|
|
410 |
}
|
|
411 |
|
|
412 |
return concatenateBlocks(buf, prependBlock, leftSpace, barBlock, rightSpace, appendBlock)
|
|
413 |
}
|
|
414 |
|
|
415 |
func concatenateBlocks(buf []byte, blocks ...[]byte) []byte {
|
|
416 |
for _, block := range blocks {
|
| 405 | 417 |
buf = append(buf, block...)
|
| 406 | 418 |
}
|
| 407 | |
|
| 408 | 419 |
return buf
|
| 409 | 420 |
}
|
| 410 | 421 |
|
| 411 | |
func fillBar(s *state, width int) []byte {
|
|
422 |
func fillBar(total, current int64, width int, fmtBytes barFmtBytes, rf *refill) []byte {
|
| 412 | 423 |
if width < 2 {
|
| 413 | 424 |
return []byte{}
|
| 414 | 425 |
}
|
| 415 | 426 |
|
| 416 | 427 |
// bar width without leftEnd and rightEnd runes
|
| 417 | 428 |
barWidth := width - 1
|
| 418 | |
formatBytes := convertFmtRunesToBytes(s.format)
|
| 419 | |
|
| 420 | |
if s.simpleSpinner != nil {
|
| 421 | |
var buf []byte
|
| 422 | |
for _, block := range [...][]byte{formatBytes[0], []byte{s.simpleSpinner()}, formatBytes[4]} {
|
| 423 | |
buf = append(buf, block...)
|
| 424 | |
}
|
| 425 | |
return buf
|
| 426 | |
}
|
| 427 | |
|
| 428 | |
completedWidth := percentage(s.total, s.current, barWidth)
|
|
429 |
|
|
430 |
completedWidth := percentage(total, current, barWidth)
|
| 429 | 431 |
|
| 430 | 432 |
buf := make([]byte, 0, width)
|
| 431 | |
// append leftEnd rune
|
| 432 | |
buf = append(buf, formatBytes[0]...)
|
| 433 | |
|
| 434 | |
if s.refill != nil {
|
| 435 | |
till := percentage(s.total, s.refill.till, barWidth)
|
| 436 | |
rbytes := make([]byte, utf8.RuneLen(s.refill.char))
|
| 437 | |
utf8.EncodeRune(rbytes, s.refill.char)
|
|
433 |
buf = append(buf, fmtBytes[rLeft]...)
|
|
434 |
|
|
435 |
if rf != nil {
|
|
436 |
till := percentage(total, rf.till, barWidth)
|
|
437 |
rbytes := make([]byte, utf8.RuneLen(rf.char))
|
|
438 |
utf8.EncodeRune(rbytes, rf.char)
|
| 438 | 439 |
// append refill rune
|
| 439 | 440 |
for i := 0; i < till; i++ {
|
| 440 | 441 |
buf = append(buf, rbytes...)
|
| 441 | 442 |
}
|
| 442 | |
// append fill rune
|
| 443 | 443 |
for i := till; i < completedWidth-1; i++ {
|
| 444 | |
buf = append(buf, formatBytes[1]...)
|
|
444 |
buf = append(buf, fmtBytes[rFill]...)
|
| 445 | 445 |
}
|
| 446 | 446 |
} else {
|
| 447 | |
// append fill rune
|
| 448 | 447 |
for i := 0; i < completedWidth-1; i++ {
|
| 449 | |
buf = append(buf, formatBytes[1]...)
|
|
448 |
buf = append(buf, fmtBytes[rFill]...)
|
| 450 | 449 |
}
|
| 451 | 450 |
}
|
| 452 | 451 |
|
| 453 | |
// set tip bit
|
| 454 | 452 |
if completedWidth > 0 && completedWidth < barWidth {
|
| 455 | |
buf = append(buf, formatBytes[2]...)
|
| 456 | |
}
|
| 457 | |
|
| 458 | |
// append empty rune
|
|
453 |
buf = append(buf, fmtBytes[rTip]...)
|
|
454 |
}
|
|
455 |
|
| 459 | 456 |
for i := completedWidth + 1; i < barWidth; i++ {
|
| 460 | |
buf = append(buf, formatBytes[3]...)
|
| 461 | |
}
|
| 462 | |
|
| 463 | |
// append rightEnd rune
|
| 464 | |
buf = append(buf, formatBytes[4]...)
|
|
457 |
buf = append(buf, fmtBytes[rEmpty]...)
|
|
458 |
}
|
|
459 |
|
|
460 |
buf = append(buf, fmtBytes[rRight]...)
|
| 465 | 461 |
|
| 466 | 462 |
return buf
|
| 467 | 463 |
}
|
|
| 475 | 471 |
}
|
| 476 | 472 |
}
|
| 477 | 473 |
|
| 478 | |
func convertFmtRunesToBytes(format formatRunes) [numFmtRunes][]byte {
|
| 479 | |
var formatBytes [numFmtRunes][]byte
|
|
474 |
func convertFmtRunesToBytes(format barFmtRunes) barFmtBytes {
|
|
475 |
var fmtBytes barFmtBytes
|
| 480 | 476 |
for i, r := range format {
|
| 481 | 477 |
buf := make([]byte, utf8.RuneLen(r))
|
| 482 | 478 |
utf8.EncodeRune(buf, r)
|
| 483 | |
formatBytes[i] = buf
|
| 484 | |
}
|
| 485 | |
return formatBytes
|
|
479 |
fmtBytes[i] = buf
|
|
480 |
}
|
|
481 |
return fmtBytes
|
| 486 | 482 |
}
|
| 487 | 483 |
|
| 488 | 484 |
func calcTimePerItemEstimate(tpie time.Duration, blockStartTime time.Time, alpha float64, items int64) time.Duration {
|