| 9 | 9 |
|
| 10 | 10 |
// Bar represents a progress Bar
|
| 11 | 11 |
type Bar struct {
|
| 12 | |
width int
|
| 13 | |
termWidth int
|
| 14 | |
etaAlpha float64
|
| 15 | |
|
| 16 | |
fill byte
|
| 17 | |
empty byte
|
| 18 | |
tip byte
|
| 19 | |
leftEnd byte
|
| 20 | |
rightEnd byte
|
|
12 |
fillCh, emptyCh, tipCh, leftEndCh, rightEndCh chan byte
|
|
13 |
|
|
14 |
widthCh chan int
|
|
15 |
etaAlphaCh chan float64
|
| 21 | 16 |
|
| 22 | 17 |
incrCh chan int64
|
| 23 | 18 |
trimLeftCh chan bool
|
|
| 51 | 46 |
till int64
|
| 52 | 47 |
}
|
| 53 | 48 |
state struct {
|
| 54 | |
total, current int64
|
| 55 | |
timeElapsed, timePerItem time.Duration
|
| 56 | |
trimLeftSpace, trimRightSpace bool
|
| 57 | |
appendFuncs, prependFuncs []DecoratorFunc
|
| 58 | |
fill byte
|
| 59 | |
empty byte
|
| 60 | |
tip byte
|
| 61 | |
leftEnd byte
|
| 62 | |
rightEnd byte
|
| 63 | |
etaAlpha float64
|
| 64 | |
width int
|
| 65 | |
refill *refill
|
|
49 |
fill byte
|
|
50 |
empty byte
|
|
51 |
tip byte
|
|
52 |
leftEnd byte
|
|
53 |
rightEnd byte
|
|
54 |
etaAlpha float64
|
|
55 |
barWidth int
|
|
56 |
total int64
|
|
57 |
current int64
|
|
58 |
trimLeftSpace bool
|
|
59 |
trimRightSpace bool
|
|
60 |
timeElapsed time.Duration
|
|
61 |
timePerItem time.Duration
|
|
62 |
appendFuncs []DecoratorFunc
|
|
63 |
prependFuncs []DecoratorFunc
|
|
64 |
simpleSpinner func() byte
|
|
65 |
refill *refill
|
| 66 | 66 |
}
|
| 67 | 67 |
)
|
| 68 | 68 |
|
| 69 | |
func newBar(ctx context.Context, wg *sync.WaitGroup, total int64, width int) *Bar {
|
|
69 |
func newBar(ctx context.Context, wg *sync.WaitGroup, total int64, barWidth int) *Bar {
|
| 70 | 70 |
b := &Bar{
|
| 71 | |
fill: '=',
|
| 72 | |
empty: '-',
|
| 73 | |
tip: '>',
|
| 74 | |
leftEnd: '[',
|
| 75 | |
rightEnd: ']',
|
| 76 | |
etaAlpha: 0.25,
|
| 77 | |
width: width,
|
| 78 | |
|
|
71 |
fillCh: make(chan byte),
|
|
72 |
emptyCh: make(chan byte),
|
|
73 |
tipCh: make(chan byte),
|
|
74 |
leftEndCh: make(chan byte),
|
|
75 |
rightEndCh: make(chan byte),
|
|
76 |
etaAlphaCh: make(chan float64),
|
| 79 | 77 |
incrCh: make(chan int64, 1),
|
|
78 |
widthCh: make(chan int),
|
| 80 | 79 |
trimLeftCh: make(chan bool),
|
| 81 | 80 |
trimRightCh: make(chan bool),
|
| 82 | 81 |
refillCh: make(chan *refill),
|
|
| 87 | 86 |
completeReqCh: make(chan struct{}),
|
| 88 | 87 |
done: make(chan struct{}),
|
| 89 | 88 |
}
|
| 90 | |
go b.server(ctx, wg, total)
|
|
89 |
go b.server(ctx, wg, total, barWidth)
|
| 91 | 90 |
return b
|
| 92 | 91 |
}
|
| 93 | 92 |
|
| 94 | 93 |
// SetWidth sets width of the bar
|
| 95 | 94 |
func (b *Bar) SetWidth(n int) *Bar {
|
| 96 | |
if n < 2 {
|
| 97 | |
return b
|
| 98 | |
}
|
| 99 | |
b.width = n
|
|
95 |
if n < 2 || IsClosed(b.done) {
|
|
96 |
return b
|
|
97 |
}
|
|
98 |
b.widthCh <- n
|
| 100 | 99 |
return b
|
| 101 | 100 |
}
|
| 102 | 101 |
|
|
| 121 | 120 |
// SetFill sets character representing completed progress.
|
| 122 | 121 |
// Defaults to '='
|
| 123 | 122 |
func (b *Bar) SetFill(c byte) *Bar {
|
| 124 | |
b.fill = c
|
|
123 |
if IsClosed(b.done) {
|
|
124 |
return b
|
|
125 |
}
|
|
126 |
b.fillCh <- c
|
| 125 | 127 |
return b
|
| 126 | 128 |
}
|
| 127 | 129 |
|
| 128 | 130 |
// SetTip sets character representing tip of progress.
|
| 129 | 131 |
// Defaults to '>'
|
| 130 | 132 |
func (b *Bar) SetTip(c byte) *Bar {
|
| 131 | |
b.tip = c
|
|
133 |
if IsClosed(b.done) {
|
|
134 |
return b
|
|
135 |
}
|
|
136 |
b.tipCh <- c
|
| 132 | 137 |
return b
|
| 133 | 138 |
}
|
| 134 | 139 |
|
| 135 | 140 |
// SetEmpty sets character representing the empty progress
|
| 136 | 141 |
// Defaults to '-'
|
| 137 | 142 |
func (b *Bar) SetEmpty(c byte) *Bar {
|
| 138 | |
b.empty = c
|
|
143 |
if IsClosed(b.done) {
|
|
144 |
return b
|
|
145 |
}
|
|
146 |
b.emptyCh <- c
|
| 139 | 147 |
return b
|
| 140 | 148 |
}
|
| 141 | 149 |
|
| 142 | 150 |
// SetLeftEnd sets character representing the left most border
|
| 143 | 151 |
// Defaults to '['
|
| 144 | 152 |
func (b *Bar) SetLeftEnd(c byte) *Bar {
|
| 145 | |
b.leftEnd = c
|
|
153 |
if IsClosed(b.done) {
|
|
154 |
return b
|
|
155 |
}
|
|
156 |
b.leftEndCh <- c
|
| 146 | 157 |
return b
|
| 147 | 158 |
}
|
| 148 | 159 |
|
| 149 | 160 |
// SetRightEnd sets character representing the right most border
|
| 150 | 161 |
// Defaults to ']'
|
| 151 | 162 |
func (b *Bar) SetRightEnd(c byte) *Bar {
|
| 152 | |
b.rightEnd = c
|
|
163 |
if IsClosed(b.done) {
|
|
164 |
return b
|
|
165 |
}
|
|
166 |
b.rightEndCh <- c
|
| 153 | 167 |
return b
|
| 154 | 168 |
}
|
| 155 | 169 |
|
|
| 157 | 171 |
// Defaults to 0.25
|
| 158 | 172 |
// Normally you shouldn't touch this
|
| 159 | 173 |
func (b *Bar) SetEtaAlpha(a float64) *Bar {
|
| 160 | |
b.etaAlpha = a
|
|
174 |
if IsClosed(b.done) {
|
|
175 |
return b
|
|
176 |
}
|
|
177 |
b.etaAlphaCh <- a
|
| 161 | 178 |
return b
|
| 162 | 179 |
}
|
| 163 | 180 |
|
|
| 235 | 252 |
}
|
| 236 | 253 |
|
| 237 | 254 |
// Completed signals to the bar, that process has been completed.
|
| 238 | |
// You should call this method when total is unknown and you reached the point
|
|
255 |
// You should call this method when total is unknown and you've reached the point
|
| 239 | 256 |
// of process completion.
|
| 240 | 257 |
func (b *Bar) Completed() {
|
| 241 | 258 |
if IsClosed(b.done) {
|
|
| 244 | 261 |
b.completeReqCh <- struct{}{}
|
| 245 | 262 |
}
|
| 246 | 263 |
|
| 247 | |
func (b *Bar) bytes(width int) []byte {
|
| 248 | |
if width <= 0 {
|
| 249 | |
width = b.width
|
| 250 | |
}
|
| 251 | |
if IsClosed(b.done) {
|
| 252 | |
return b.lastState.draw(width)
|
|
264 |
func (b *Bar) bytes(termWidth int) []byte {
|
|
265 |
if IsClosed(b.done) {
|
|
266 |
return b.lastState.draw(termWidth)
|
| 253 | 267 |
}
|
| 254 | 268 |
ch := make(chan state, 1)
|
| 255 | 269 |
b.stateReqCh <- ch
|
| 256 | 270 |
s := <-ch
|
| 257 | |
return s.draw(width)
|
| 258 | |
}
|
| 259 | |
|
| 260 | |
func (b *Bar) server(ctx context.Context, wg *sync.WaitGroup, total int64) {
|
|
271 |
return s.draw(termWidth)
|
|
272 |
}
|
|
273 |
|
|
274 |
func (b *Bar) server(ctx context.Context, wg *sync.WaitGroup, total int64, barWidth int) {
|
| 261 | 275 |
var completed bool
|
| 262 | 276 |
timeStarted := time.Now()
|
| 263 | 277 |
blockStartTime := timeStarted
|
| 264 | |
state := state{total: total}
|
|
278 |
state := state{
|
|
279 |
fill: '=',
|
|
280 |
empty: '-',
|
|
281 |
tip: '>',
|
|
282 |
leftEnd: '[',
|
|
283 |
rightEnd: ']',
|
|
284 |
etaAlpha: 0.25,
|
|
285 |
barWidth: barWidth,
|
|
286 |
total: total,
|
|
287 |
}
|
|
288 |
if total <= 0 {
|
|
289 |
state.simpleSpinner = getSpinner()
|
|
290 |
}
|
| 265 | 291 |
defer func() {
|
| 266 | 292 |
b.stop(&state)
|
| 267 | 293 |
wg.Done()
|
|
| 277 | 303 |
break // break out of select
|
| 278 | 304 |
}
|
| 279 | 305 |
state.timeElapsed = time.Since(timeStarted)
|
| 280 | |
state.timePerItem = calcTimePerItemEstimate(state.timePerItem, blockStartTime, b.etaAlpha, i)
|
|
306 |
state.timePerItem = calcTimePerItemEstimate(state.timePerItem, blockStartTime, state.etaAlpha, i)
|
| 281 | 307 |
if n == total {
|
| 282 | 308 |
completed = true
|
| 283 | 309 |
}
|
|
| 295 | 321 |
state.prependFuncs = nil
|
| 296 | 322 |
}
|
| 297 | 323 |
case ch := <-b.stateReqCh:
|
| 298 | |
state.fill = b.fill
|
| 299 | |
state.empty = b.empty
|
| 300 | |
state.tip = b.tip
|
| 301 | |
state.leftEnd = b.leftEnd
|
| 302 | |
state.rightEnd = b.rightEnd
|
| 303 | |
state.etaAlpha = b.etaAlpha
|
| 304 | |
state.width = b.width
|
| 305 | 324 |
ch <- state
|
|
325 |
case state.fill = <-b.fillCh:
|
|
326 |
case state.empty = <-b.emptyCh:
|
|
327 |
case state.tip = <-b.tipCh:
|
|
328 |
case state.leftEnd = <-b.leftEndCh:
|
|
329 |
case state.rightEnd = <-b.rightEndCh:
|
|
330 |
case state.barWidth = <-b.widthCh:
|
| 306 | 331 |
case state.refill = <-b.refillCh:
|
| 307 | 332 |
case state.trimLeftSpace = <-b.trimLeftCh:
|
| 308 | 333 |
case state.trimRightSpace = <-b.trimRightCh:
|
|
| 339 | 364 |
b.removeReqCh <- struct{}{}
|
| 340 | 365 |
}
|
| 341 | 366 |
|
| 342 | |
func (s *state) draw(termWidth int) []byte {
|
|
367 |
func (s state) draw(termWidth int) []byte {
|
|
368 |
if termWidth <= 0 {
|
|
369 |
termWidth = s.barWidth
|
|
370 |
}
|
| 343 | 371 |
stat := &Statistics{
|
| 344 | 372 |
Total: s.total,
|
| 345 | 373 |
Current: s.current,
|
|
| 360 | 388 |
prependBlock = append(prependBlock, []byte(f(stat))...)
|
| 361 | 389 |
}
|
| 362 | 390 |
|
| 363 | |
barBlock := s.fillBar()
|
|
391 |
barBlock := s.fillBar(s.barWidth)
|
| 364 | 392 |
prependCount := utf8.RuneCount(prependBlock)
|
| 365 | 393 |
barCount := utf8.RuneCount(barBlock)
|
| 366 | 394 |
appendCount := utf8.RuneCount(appendBlock)
|
|
| 379 | 407 |
|
| 380 | 408 |
totalCount := prependCount + barCount + appendCount
|
| 381 | 409 |
if totalCount >= termWidth {
|
| 382 | |
s.width = termWidth - prependCount - appendCount - 1
|
| 383 | |
barBlock = s.fillBar()
|
|
410 |
newWidth := termWidth - prependCount - appendCount - 1
|
|
411 |
barBlock = s.fillBar(newWidth)
|
| 384 | 412 |
}
|
| 385 | 413 |
|
| 386 | 414 |
buf := make([]byte, 0, termWidth)
|
|
| 391 | 419 |
return buf
|
| 392 | 420 |
}
|
| 393 | 421 |
|
| 394 | |
func (s *state) fillBar() []byte {
|
| 395 | |
if s.width < 2 {
|
|
422 |
func (s state) fillBar(width int) []byte {
|
|
423 |
if width < 2 {
|
| 396 | 424 |
return []byte{}
|
| 397 | 425 |
}
|
| 398 | 426 |
|
| 399 | |
buf := make([]byte, s.width)
|
| 400 | |
completedWidth := percentage(s.total, s.current, s.width)
|
|
427 |
if s.simpleSpinner != nil {
|
|
428 |
return []byte{s.leftEnd, s.simpleSpinner(), s.rightEnd}
|
|
429 |
}
|
|
430 |
|
|
431 |
buf := make([]byte, width)
|
|
432 |
completedWidth := percentage(s.total, s.current, width)
|
| 401 | 433 |
|
| 402 | 434 |
if s.refill != nil {
|
| 403 | |
till := percentage(s.total, s.refill.till, s.width)
|
|
435 |
till := percentage(s.total, s.refill.till, width)
|
| 404 | 436 |
for i := 1; i < till; i++ {
|
| 405 | 437 |
buf[i] = s.refill.char
|
| 406 | 438 |
}
|
|
| 413 | 445 |
}
|
| 414 | 446 |
}
|
| 415 | 447 |
|
| 416 | |
for i := completedWidth; i < s.width-1; i++ {
|
|
448 |
for i := completedWidth; i < width-1; i++ {
|
| 417 | 449 |
buf[i] = s.empty
|
| 418 | 450 |
}
|
| 419 | 451 |
// set tip bit
|
| 420 | |
if completedWidth > 0 && completedWidth < s.width {
|
|
452 |
if completedWidth > 0 && completedWidth < s.barWidth {
|
| 421 | 453 |
buf[completedWidth-1] = s.tip
|
| 422 | 454 |
}
|
| 423 | 455 |
// set left and right ends bits
|
| 424 | |
buf[0], buf[s.width-1] = s.leftEnd, s.rightEnd
|
|
456 |
buf[0], buf[width-1] = s.leftEnd, s.rightEnd
|
| 425 | 457 |
|
| 426 | 458 |
return buf
|
| 427 | 459 |
}
|