state with draw behaviour
Vladimir Bauer
9 years ago
| 19 | 19 | leftEnd byte |
| 20 | 20 | rightEnd byte |
| 21 | 21 | |
| 22 | incrCh chan int64 | |
| 23 | trimLeftCh chan bool | |
| 24 | trimRightCh chan bool | |
| 25 | stateReqCh chan chan state | |
| 26 | decoratorCh chan *decorator | |
| 27 | flushedCh chan struct{} | |
| 28 | removeReqCh chan struct{} | |
| 29 | done chan struct{} | |
| 30 | ||
| 31 | refill *reFill | |
| 22 | incrCh chan int64 | |
| 23 | trimLeftCh chan bool | |
| 24 | trimRightCh chan bool | |
| 25 | refillCh chan *refill | |
| 26 | stateReqCh chan chan state | |
| 27 | decoratorCh chan *decorator | |
| 28 | flushedCh chan struct{} | |
| 29 | removeReqCh chan struct{} | |
| 30 | completeReqCh chan struct{} | |
| 31 | done chan struct{} | |
| 32 | ||
| 32 | 33 | lastState state |
| 33 | 34 | } |
| 34 | 35 | |
| 45 | 46 | } |
| 46 | 47 | |
| 47 | 48 | type ( |
| 49 | refill struct { | |
| 50 | char byte | |
| 51 | till int64 | |
| 52 | } | |
| 48 | 53 | state struct { |
| 49 | 54 | total, current int64 |
| 50 | 55 | timeElapsed, timePerItem time.Duration |
| 56 | trimLeftSpace, trimRightSpace bool | |
| 51 | 57 | appendFuncs, prependFuncs []DecoratorFunc |
| 52 | trimLeftSpace, trimRightSpace bool | |
| 53 | } | |
| 54 | reFill struct { | |
| 55 | c byte | |
| 56 | till int64 | |
| 58 | fill byte | |
| 59 | empty byte | |
| 60 | tip byte | |
| 61 | leftEnd byte | |
| 62 | rightEnd byte | |
| 63 | etaAlpha float64 | |
| 64 | width int | |
| 65 | refill *refill | |
| 57 | 66 | } |
| 58 | 67 | ) |
| 59 | 68 | |
| 67 | 76 | etaAlpha: 0.25, |
| 68 | 77 | width: width, |
| 69 | 78 | |
| 70 | incrCh: make(chan int64, 1), | |
| 71 | trimLeftCh: make(chan bool), | |
| 72 | trimRightCh: make(chan bool), | |
| 73 | stateReqCh: make(chan chan state, 1), | |
| 74 | decoratorCh: make(chan *decorator), | |
| 75 | flushedCh: make(chan struct{}, 1), | |
| 76 | removeReqCh: make(chan struct{}), | |
| 77 | done: make(chan struct{}), | |
| 79 | incrCh: make(chan int64, 1), | |
| 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{}), | |
| 78 | 89 | } |
| 79 | 90 | go b.server(ctx, wg, total) |
| 80 | 91 | return b |
| 157 | 168 | |
| 158 | 169 | // Incr increments progress bar |
| 159 | 170 | func (b *Bar) Incr(n int) { |
| 160 | if IsClosed(b.done) { | |
| 161 | return | |
| 162 | } | |
| 163 | if n > 0 { | |
| 164 | b.incrCh <- int64(n) | |
| 165 | } | |
| 171 | if n < 1 || IsClosed(b.done) { | |
| 172 | return | |
| 173 | } | |
| 174 | b.incrCh <- int64(n) | |
| 166 | 175 | } |
| 167 | 176 | |
| 168 | 177 | // IncrWithReFill increments pb with different fill character |
| 169 | 178 | func (b *Bar) IncrWithReFill(n int, c byte) { |
| 170 | if n > 0 { | |
| 171 | b.Incr(n) | |
| 172 | b.refill = &reFill{c, int64(n)} | |
| 173 | } | |
| 179 | if IsClosed(b.done) { | |
| 180 | return | |
| 181 | } | |
| 182 | b.Incr(n) | |
| 183 | b.refillCh <- &refill{c, int64(n)} | |
| 174 | 184 | } |
| 175 | 185 | |
| 176 | 186 | // Current returns the actual current. |
| 224 | 234 | b.decoratorCh <- &decorator{decAppendZero, nil} |
| 225 | 235 | } |
| 226 | 236 | |
| 237 | // 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 | |
| 239 | // of process completion. | |
| 240 | func (b *Bar) Completed() { | |
| 241 | if IsClosed(b.done) { | |
| 242 | return | |
| 243 | } | |
| 244 | b.completeReqCh <- struct{}{} | |
| 245 | } | |
| 246 | ||
| 227 | 247 | func (b *Bar) bytes(width int) []byte { |
| 228 | 248 | if width <= 0 { |
| 229 | 249 | width = b.width |
| 230 | 250 | } |
| 231 | 251 | if IsClosed(b.done) { |
| 232 | return b.draw(b.lastState, width) | |
| 252 | return b.lastState.draw(width) | |
| 233 | 253 | } |
| 234 | 254 | ch := make(chan state, 1) |
| 235 | 255 | b.stateReqCh <- ch |
| 236 | return b.draw(<-ch, width) | |
| 256 | s := <-ch | |
| 257 | return s.draw(width) | |
| 237 | 258 | } |
| 238 | 259 | |
| 239 | 260 | func (b *Bar) server(ctx context.Context, wg *sync.WaitGroup, total int64) { |
| 249 | 270 | select { |
| 250 | 271 | case i := <-b.incrCh: |
| 251 | 272 | n := state.current + i |
| 252 | if n > total { | |
| 273 | if total > 0 && n > total { | |
| 253 | 274 | state.current = total |
| 254 | 275 | completed = true |
| 255 | 276 | blockStartTime = time.Now() |
| 274 | 295 | state.prependFuncs = nil |
| 275 | 296 | } |
| 276 | 297 | 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 | |
| 277 | 305 | ch <- state |
| 306 | case state.refill = <-b.refillCh: | |
| 278 | 307 | case state.trimLeftSpace = <-b.trimLeftCh: |
| 279 | 308 | case state.trimRightSpace = <-b.trimRightCh: |
| 280 | 309 | case <-b.flushedCh: |
| 281 | 310 | if completed { |
| 282 | 311 | return |
| 283 | 312 | } |
| 313 | case <-b.completeReqCh: | |
| 314 | return | |
| 284 | 315 | case <-b.removeReqCh: |
| 285 | 316 | return |
| 286 | 317 | case <-ctx.Done(): |
| 308 | 339 | b.removeReqCh <- struct{}{} |
| 309 | 340 | } |
| 310 | 341 | |
| 311 | func (b *Bar) draw(s state, termWidth int) []byte { | |
| 312 | buf := make([]byte, 0, termWidth) | |
| 313 | ||
| 342 | func (s *state) draw(termWidth int) []byte { | |
| 314 | 343 | stat := &Statistics{ |
| 315 | 344 | Total: s.total, |
| 316 | 345 | Current: s.current, |
| 319 | 348 | TimePerItemEstimate: s.timePerItem, |
| 320 | 349 | } |
| 321 | 350 | |
| 322 | barBlock := b.fillBar(s.total, s.current, b.width) | |
| 323 | ||
| 324 | 351 | // render append functions to the right of the bar |
| 325 | 352 | var appendBlock []byte |
| 326 | 353 | for _, f := range s.appendFuncs { |
| 333 | 360 | prependBlock = append(prependBlock, []byte(f(stat))...) |
| 334 | 361 | } |
| 335 | 362 | |
| 363 | barBlock := s.fillBar() | |
| 336 | 364 | prependCount := utf8.RuneCount(prependBlock) |
| 337 | 365 | barCount := utf8.RuneCount(barBlock) |
| 338 | 366 | appendCount := utf8.RuneCount(appendBlock) |
| 351 | 379 | |
| 352 | 380 | totalCount := prependCount + barCount + appendCount |
| 353 | 381 | if totalCount >= termWidth { |
| 354 | newWidth := termWidth - prependCount - appendCount | |
| 355 | barBlock = b.fillBar(s.total, s.current, newWidth-1) | |
| 356 | } | |
| 357 | ||
| 382 | s.width = termWidth - prependCount - appendCount - 1 | |
| 383 | barBlock = s.fillBar() | |
| 384 | } | |
| 385 | ||
| 386 | buf := make([]byte, 0, termWidth) | |
| 358 | 387 | for _, block := range [...][]byte{prependBlock, leftSpace, barBlock, rightSpace, appendBlock} { |
| 359 | 388 | buf = append(buf, block...) |
| 360 | 389 | } |
| 362 | 391 | return buf |
| 363 | 392 | } |
| 364 | 393 | |
| 365 | func (b *Bar) fillBar(total, current int64, width int) []byte { | |
| 366 | if width < 2 { | |
| 394 | func (s *state) fillBar() []byte { | |
| 395 | if s.width < 2 { | |
| 367 | 396 | return []byte{} |
| 368 | 397 | } |
| 369 | 398 | |
| 370 | buf := make([]byte, width) | |
| 371 | completedWidth := percentage(total, current, width) | |
| 372 | ||
| 373 | if b.refill != nil { | |
| 374 | till := percentage(total, b.refill.till, width) | |
| 399 | buf := make([]byte, s.width) | |
| 400 | completedWidth := percentage(s.total, s.current, s.width) | |
| 401 | ||
| 402 | if s.refill != nil { | |
| 403 | till := percentage(s.total, s.refill.till, s.width) | |
| 375 | 404 | for i := 1; i < till; i++ { |
| 376 | buf[i] = b.refill.c | |
| 405 | buf[i] = s.refill.char | |
| 377 | 406 | } |
| 378 | 407 | for i := till; i < completedWidth; i++ { |
| 379 | buf[i] = b.fill | |
| 408 | buf[i] = s.fill | |
| 380 | 409 | } |
| 381 | 410 | } else { |
| 382 | 411 | for i := 1; i < completedWidth; i++ { |
| 383 | buf[i] = b.fill | |
| 412 | buf[i] = s.fill | |
| 384 | 413 | } |
| 385 | 414 | } |
| 386 | 415 | |
| 387 | for i := completedWidth; i < width-1; i++ { | |
| 388 | buf[i] = b.empty | |
| 416 | for i := completedWidth; i < s.width-1; i++ { | |
| 417 | buf[i] = s.empty | |
| 389 | 418 | } |
| 390 | 419 | // set tip bit |
| 391 | if completedWidth > 0 && completedWidth < width { | |
| 392 | buf[completedWidth-1] = b.tip | |
| 420 | if completedWidth > 0 && completedWidth < s.width { | |
| 421 | buf[completedWidth-1] = s.tip | |
| 393 | 422 | } |
| 394 | 423 | // set left and right ends bits |
| 395 | buf[0], buf[width-1] = b.leftEnd, b.rightEnd | |
| 424 | buf[0], buf[s.width-1] = s.leftEnd, s.rightEnd | |
| 396 | 425 | |
| 397 | 426 | return buf |
| 398 | 427 | } |
| 428 | 457 | } |
| 429 | 458 | |
| 430 | 459 | func percentage(total, current int64, ratio int) int { |
| 431 | if total == 0 { | |
| 460 | if total <= 0 { | |
| 432 | 461 | return 0 |
| 433 | 462 | } |
| 434 | 463 | return int(float64(ratio) * float64(current) / float64(total)) |
| 435 | 464 | } |
| 465 | ||
| 466 | func getSpinner() func() byte { | |
| 467 | chars := []byte(`-\|/`) | |
| 468 | repeat := len(chars) - 1 | |
| 469 | index := repeat | |
| 470 | return func() byte { | |
| 471 | if index == repeat { | |
| 472 | index = -1 | |
| 473 | } | |
| 474 | index++ | |
| 475 | return chars[index] | |
| 476 | } | |
| 477 | } | |