| 7 | 7 |
"unicode/utf8"
|
| 8 | 8 |
)
|
| 9 | 9 |
|
| 10 | |
type formatRunes [5]rune
|
|
10 |
type formatRunes [numFmtRunes]rune
|
| 11 | 11 |
|
| 12 | 12 |
// Bar represents a progress Bar
|
| 13 | 13 |
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
|
| 19 | 19 |
incrCh chan int64
|
| 20 | 20 |
trimLeftCh chan bool
|
| 21 | 21 |
trimRightCh chan bool
|
| 22 | 22 |
refillCh chan *refill
|
| 23 | |
stateReqCh chan chan state
|
| 24 | 23 |
decoratorCh chan *decorator
|
| 25 | 24 |
flushedCh chan struct{}
|
| 26 | 25 |
removeReqCh chan struct{}
|
| 27 | 26 |
completeReqCh chan struct{}
|
| 28 | 27 |
done chan struct{}
|
| 29 | 28 |
|
| 30 | |
lastState state
|
|
29 |
// follawing are used after (*Bar.done) is closed
|
|
30 |
width int
|
|
31 |
state state
|
| 31 | 32 |
}
|
| 32 | 33 |
|
| 33 | 34 |
// Statistics represents statistics of the progress bar.
|
|
| 55 | 56 |
id int
|
| 56 | 57 |
format formatRunes
|
| 57 | 58 |
etaAlpha float64
|
| 58 | |
barWidth int
|
| 59 | 59 |
total int64
|
| 60 | 60 |
current int64
|
| 61 | 61 |
trimLeftSpace bool
|
|
| 71 | 71 |
|
| 72 | 72 |
func newBar(ctx context.Context, wg *sync.WaitGroup, id int, total int64, width int, format string) *Bar {
|
| 73 | 73 |
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{}),
|
| 87 | 88 |
}
|
| 88 | 89 |
go b.server(ctx, wg, id, total, width, format)
|
| 89 | 90 |
return b
|
| 90 | 91 |
}
|
| 91 | 92 |
|
| 92 | |
// SetWidth sets width of the bar
|
|
93 |
// SetWidth overrides width of individual bar
|
| 93 | 94 |
func (b *Bar) SetWidth(n int) *Bar {
|
| 94 | 95 |
if n < 2 || IsClosed(b.done) {
|
| 95 | 96 |
return b
|
| 96 | 97 |
}
|
| 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
|
| 99 | 109 |
}
|
| 100 | 110 |
|
| 101 | 111 |
// TrimLeftSpace removes space befor LeftEnd charater
|
|
| 116 | 126 |
return b
|
| 117 | 127 |
}
|
| 118 | 128 |
|
| 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
|
| 166 | 135 |
return b
|
| 167 | 136 |
}
|
| 168 | 137 |
|
|
| 214 | 183 |
// GetStatistics returs *Statistics, which contains information like Tottal,
|
| 215 | 184 |
// Current, TimeElapsed and TimePerItemEstimate
|
| 216 | 185 |
func (b *Bar) GetStatistics() *Statistics {
|
| 217 | |
state := b.getState()
|
| 218 | |
return state.newStat()
|
|
186 |
s := b.getState()
|
|
187 |
return newStatistics(&s)
|
| 219 | 188 |
}
|
| 220 | 189 |
|
| 221 | 190 |
// GetID returs id of the bar
|
|
| 276 | 245 |
|
| 277 | 246 |
func (b *Bar) getState() state {
|
| 278 | 247 |
if IsClosed(b.done) {
|
| 279 | |
return b.lastState
|
|
248 |
return b.state
|
| 280 | 249 |
}
|
| 281 | 250 |
ch := make(chan state, 1)
|
| 282 | 251 |
b.stateReqCh <- ch
|
|
| 284 | 253 |
}
|
| 285 | 254 |
|
| 286 | 255 |
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)
|
| 289 | 258 |
}
|
| 290 | 259 |
|
| 291 | 260 |
func (b *Bar) server(ctx context.Context, wg *sync.WaitGroup, id int, total int64, width int, format string) {
|
|
| 296 | 265 |
id: id,
|
| 297 | 266 |
format: formatRunes{'[', '=', '>', '-', ']'},
|
| 298 | 267 |
etaAlpha: 0.25,
|
| 299 | |
barWidth: width,
|
| 300 | 268 |
total: total,
|
| 301 | 269 |
}
|
| 302 | 270 |
if total <= 0 {
|
| 303 | 271 |
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)
|
| 309 | 274 |
}
|
| 310 | 275 |
defer func() {
|
| 311 | |
b.stop(&state)
|
|
276 |
b.stop(&state, width)
|
| 312 | 277 |
wg.Done()
|
| 313 | 278 |
}()
|
| 314 | 279 |
for {
|
|
| 341 | 306 |
}
|
| 342 | 307 |
case ch := <-b.stateReqCh:
|
| 343 | 308 |
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)
|
| 347 | 314 |
case state.refill = <-b.refillCh:
|
| 348 | 315 |
case state.trimLeftSpace = <-b.trimLeftCh:
|
| 349 | 316 |
case state.trimRightSpace = <-b.trimRightCh:
|
|
| 361 | 328 |
}
|
| 362 | 329 |
}
|
| 363 | 330 |
|
| 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
|
| 366 | 334 |
close(b.done)
|
| 367 | 335 |
}
|
| 368 | 336 |
|
|
| 380 | 348 |
b.removeReqCh <- struct{}{}
|
| 381 | 349 |
}
|
| 382 | 350 |
|
| 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 {
|
| 393 | 362 |
if termWidth <= 0 {
|
| 394 | |
termWidth = s.barWidth
|
| 395 | |
}
|
| 396 | |
|
| 397 | |
stat := s.newStat()
|
|
363 |
termWidth = barWidth
|
|
364 |
}
|
|
365 |
|
|
366 |
stat := newStatistics(s)
|
| 398 | 367 |
|
| 399 | 368 |
// render append functions to the right of the bar
|
| 400 | 369 |
var appendBlock []byte
|
|
| 408 | 377 |
prependBlock = append(prependBlock, []byte(f(stat))...)
|
| 409 | 378 |
}
|
| 410 | 379 |
|
| 411 | |
barBlock := s.fillBar(s.barWidth)
|
|
380 |
barBlock := fillBar(s, barWidth)
|
| 412 | 381 |
prependCount := utf8.RuneCount(prependBlock)
|
| 413 | 382 |
barCount := utf8.RuneCount(barBlock)
|
| 414 | 383 |
appendCount := utf8.RuneCount(appendBlock)
|
|
| 428 | 397 |
totalCount := prependCount + barCount + appendCount
|
| 429 | 398 |
if totalCount > termWidth {
|
| 430 | 399 |
newWidth := termWidth - prependCount - appendCount
|
| 431 | |
barBlock = s.fillBar(newWidth)
|
|
400 |
barBlock = fillBar(s, newWidth)
|
| 432 | 401 |
}
|
| 433 | 402 |
|
| 434 | 403 |
buf := make([]byte, 0, termWidth)
|
|
| 439 | 408 |
return buf
|
| 440 | 409 |
}
|
| 441 | 410 |
|
| 442 | |
func (s *state) fillBar(width int) []byte {
|
|
411 |
func fillBar(s *state, width int) []byte {
|
| 443 | 412 |
if width < 2 {
|
| 444 | 413 |
return []byte{}
|
| 445 | 414 |
}
|
| 446 | 415 |
|
| 447 | |
// bar width without leftEnd and rightEnd characters
|
|
416 |
// bar width without leftEnd and rightEnd runes
|
| 448 | 417 |
barWidth := width - 1
|
| 449 | |
formatBytes := s.convertFormatRunesToBytes()
|
|
418 |
formatBytes := convertFmtRunesToBytes(s.format)
|
| 450 | 419 |
|
| 451 | 420 |
if s.simpleSpinner != nil {
|
| 452 | 421 |
var buf []byte
|
|
| 466 | 435 |
till := percentage(s.total, s.refill.till, barWidth)
|
| 467 | 436 |
rbytes := make([]byte, utf8.RuneLen(s.refill.char))
|
| 468 | 437 |
utf8.EncodeRune(rbytes, s.refill.char)
|
|
438 |
// append refill rune
|
| 469 | 439 |
for i := 0; i < till; i++ {
|
| 470 | 440 |
buf = append(buf, rbytes...)
|
| 471 | 441 |
}
|
|
442 |
// append fill rune
|
| 472 | 443 |
for i := till; i < completedWidth-1; i++ {
|
| 473 | |
// append fill rune
|
| 474 | 444 |
buf = append(buf, formatBytes[1]...)
|
| 475 | 445 |
}
|
| 476 | 446 |
} else {
|
|
447 |
// append fill rune
|
| 477 | 448 |
for i := 0; i < completedWidth-1; i++ {
|
| 478 | |
// append fill rune
|
| 479 | 449 |
buf = append(buf, formatBytes[1]...)
|
| 480 | 450 |
}
|
| 481 | 451 |
}
|
|
452 |
|
| 482 | 453 |
// set tip bit
|
| 483 | 454 |
if completedWidth > 0 && completedWidth < barWidth {
|
| 484 | 455 |
buf = append(buf, formatBytes[2]...)
|
| 485 | 456 |
}
|
| 486 | 457 |
|
|
458 |
// append empty rune
|
| 487 | 459 |
for i := completedWidth + 1; i < barWidth; i++ {
|
| 488 | |
// append empty rune
|
| 489 | 460 |
buf = append(buf, formatBytes[3]...)
|
| 490 | 461 |
}
|
|
462 |
|
| 491 | 463 |
// append rightEnd rune
|
| 492 | 464 |
buf = append(buf, formatBytes[4]...)
|
|
465 |
|
| 493 | 466 |
return buf
|
| 494 | 467 |
}
|
| 495 | 468 |
|
| 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 {
|
| 499 | 481 |
buf := make([]byte, utf8.RuneLen(r))
|
| 500 | 482 |
utf8.EncodeRune(buf, r)
|
| 501 | 483 |
formatBytes[i] = buf
|