mpb.AddSpinner
Vladimir Bauer
7 years ago
| 10 | 10 | "unicode/utf8" |
| 11 | 11 | |
| 12 | 12 | "github.com/vbauerster/mpb/decor" |
| 13 | "github.com/vbauerster/mpb/internal" | |
| 14 | 13 | ) |
| 15 | ||
| 16 | const ( | |
| 17 | rLeft = iota | |
| 18 | rFill | |
| 19 | rTip | |
| 20 | rEmpty | |
| 21 | rRight | |
| 22 | ) | |
| 23 | ||
| 24 | const formatLen = 5 | |
| 25 | ||
| 26 | type barRunes [formatLen]rune | |
| 27 | 14 | |
| 28 | 15 | // Bar represents a progress Bar |
| 29 | 16 | type Bar struct { |
| 44 | 31 | shutdown chan struct{} |
| 45 | 32 | } |
| 46 | 33 | |
| 34 | type filler interface { | |
| 35 | fill(w io.Writer, width int, s *decor.Statistics) | |
| 36 | } | |
| 37 | ||
| 47 | 38 | type ( |
| 48 | 39 | bState struct { |
| 40 | filler filler | |
| 49 | 41 | id int |
| 50 | 42 | width int |
| 43 | alignment int | |
| 51 | 44 | total int64 |
| 52 | 45 | current int64 |
| 53 | runes barRunes | |
| 54 | spinner []rune | |
| 55 | trimLeftSpace bool | |
| 56 | trimRightSpace bool | |
| 46 | trimSpace bool | |
| 57 | 47 | toComplete bool |
| 58 | 48 | removeOnComplete bool |
| 59 | 49 | barClearOnComplete bool |
| 73 | 63 | runningBar *Bar |
| 74 | 64 | } |
| 75 | 65 | refill struct { |
| 76 | char rune | |
| 77 | till int64 | |
| 66 | r rune | |
| 67 | limit int64 | |
| 78 | 68 | } |
| 79 | 69 | frameReader struct { |
| 80 | 70 | io.Reader |
| 84 | 74 | } |
| 85 | 75 | ) |
| 86 | 76 | |
| 87 | func newBar(wg *sync.WaitGroup, id int, total int64, cancel <-chan struct{}, options ...BarOption) *Bar { | |
| 77 | func newBar(wg *sync.WaitGroup, id, width int, total int64, cancel <-chan struct{}, options ...BarOption) *Bar { | |
| 88 | 78 | if total <= 0 { |
| 89 | 79 | total = time.Now().Unix() |
| 90 | 80 | } |
| 92 | 82 | s := &bState{ |
| 93 | 83 | id: id, |
| 94 | 84 | priority: id, |
| 85 | width: width, | |
| 95 | 86 | total: total, |
| 96 | 87 | } |
| 97 | 88 | |
| 101 | 92 | } |
| 102 | 93 | } |
| 103 | 94 | |
| 104 | s.bufP = bytes.NewBuffer(make([]byte, 0, s.width)) | |
| 105 | s.bufB = bytes.NewBuffer(make([]byte, 0, s.width)) | |
| 106 | s.bufA = bytes.NewBuffer(make([]byte, 0, s.width)) | |
| 95 | s.bufP = bytes.NewBuffer(make([]byte, 0, width)) | |
| 96 | s.bufB = bytes.NewBuffer(make([]byte, 0, width)) | |
| 97 | s.bufA = bytes.NewBuffer(make([]byte, 0, width)) | |
| 98 | if s.newLineExtendFn != nil { | |
| 99 | s.bufNL = bytes.NewBuffer(make([]byte, 0, width)) | |
| 100 | } | |
| 107 | 101 | |
| 108 | 102 | b := &Bar{ |
| 109 | 103 | priority: s.priority, |
| 121 | 115 | b.priority = b.runningBar.priority |
| 122 | 116 | } |
| 123 | 117 | |
| 124 | if s.newLineExtendFn != nil { | |
| 125 | s.bufNL = bytes.NewBuffer(make([]byte, 0, s.width)) | |
| 126 | } | |
| 127 | ||
| 128 | 118 | go b.serve(wg, s, cancel) |
| 129 | 119 | return b |
| 130 | 120 | } |
| 202 | 192 | return |
| 203 | 193 | } |
| 204 | 194 | b.operateState <- func(s *bState) { |
| 205 | s.refill = &refill{r, int64(n)} | |
| 206 | } | |
| 207 | } | |
| 208 | ||
| 209 | // RefillBy is deprecated, use SetRefill | |
| 210 | func (b *Bar) RefillBy(n int, r rune) { | |
| 211 | b.SetRefill(n, r) | |
| 195 | if bf, ok := s.filler.(*barFiller); ok { | |
| 196 | bf.refill = &refill{r, int64(n)} | |
| 197 | } | |
| 198 | } | |
| 212 | 199 | } |
| 213 | 200 | |
| 214 | 201 | // Increment is a shorthand for b.IncrBy(1). |
| 322 | 309 | } |
| 323 | 310 | |
| 324 | 311 | func (s *bState) draw(termWidth int) io.Reader { |
| 325 | defer s.bufA.WriteByte('\n') | |
| 326 | ||
| 327 | 312 | if s.panicMsg != "" { |
| 328 | 313 | return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", termWidth), s.panicMsg)) |
| 329 | 314 | } |
| 338 | 323 | s.bufA.WriteString(d.Decor(stat)) |
| 339 | 324 | } |
| 340 | 325 | |
| 326 | if s.barClearOnComplete && s.completeFlushed { | |
| 327 | s.bufA.WriteByte('\n') | |
| 328 | return io.MultiReader(s.bufP, s.bufA) | |
| 329 | } | |
| 330 | ||
| 341 | 331 | prependCount := utf8.RuneCount(s.bufP.Bytes()) |
| 342 | 332 | appendCount := utf8.RuneCount(s.bufA.Bytes()) |
| 343 | 333 | |
| 344 | if s.barClearOnComplete && s.completeFlushed { | |
| 345 | return io.MultiReader(s.bufP, s.bufA) | |
| 346 | } | |
| 347 | ||
| 348 | s.fill(s.width) | |
| 349 | barCount := utf8.RuneCount(s.bufB.Bytes()) | |
| 350 | totalCount := prependCount + barCount + appendCount | |
| 351 | if spaceCount := 0; totalCount > termWidth { | |
| 352 | if !s.trimLeftSpace { | |
| 353 | spaceCount++ | |
| 354 | } | |
| 355 | if !s.trimRightSpace { | |
| 356 | spaceCount++ | |
| 357 | } | |
| 358 | s.fill(termWidth - prependCount - appendCount - spaceCount) | |
| 359 | } | |
| 360 | ||
| 334 | // s.bufB.Reset() | |
| 335 | if !s.trimSpace { | |
| 336 | // reserve space for edge spaces | |
| 337 | termWidth -= 2 | |
| 338 | s.bufB.WriteByte(' ') | |
| 339 | } | |
| 340 | ||
| 341 | if prependCount+s.width+appendCount > termWidth { | |
| 342 | s.filler.fill(s.bufB, termWidth-prependCount-appendCount, stat) | |
| 343 | } else { | |
| 344 | s.filler.fill(s.bufB, s.width, stat) | |
| 345 | } | |
| 346 | ||
| 347 | if !s.trimSpace { | |
| 348 | s.bufB.WriteByte(' ') | |
| 349 | } | |
| 350 | ||
| 351 | s.bufA.WriteByte('\n') | |
| 361 | 352 | return io.MultiReader(s.bufP, s.bufB, s.bufA) |
| 362 | 353 | } |
| 363 | 354 | |
| 364 | func (s *bState) fill(width int) { | |
| 365 | if len(s.spinner) != 0 { | |
| 366 | s.fillSpinner(width) | |
| 367 | } else { | |
| 368 | s.fillBar(width) | |
| 369 | } | |
| 370 | } | |
| 371 | ||
| 372 | func (s *bState) fillSpinner(width int) { | |
| 373 | s.bufB.Reset() | |
| 374 | ||
| 375 | if !s.trimLeftSpace { | |
| 376 | s.bufB.WriteByte(' ') | |
| 377 | } | |
| 378 | ||
| 379 | spin := []byte(string(s.spinner[s.current%int64(len(s.spinner))])) | |
| 380 | for _, b := range spin { | |
| 381 | s.bufB.WriteByte(b) | |
| 382 | } | |
| 383 | ||
| 384 | for i := len(spin); i < width; i++ { | |
| 385 | s.bufB.WriteRune(' ') | |
| 386 | } | |
| 387 | } | |
| 388 | ||
| 389 | func (s *bState) fillBar(width int) { | |
| 390 | defer func() { | |
| 391 | s.bufB.WriteRune(s.runes[rRight]) | |
| 392 | if !s.trimRightSpace { | |
| 393 | s.bufB.WriteByte(' ') | |
| 394 | } | |
| 395 | }() | |
| 396 | ||
| 397 | s.bufB.Reset() | |
| 398 | if !s.trimLeftSpace { | |
| 399 | s.bufB.WriteByte(' ') | |
| 400 | } | |
| 401 | s.bufB.WriteRune(s.runes[rLeft]) | |
| 402 | if width <= 2 { | |
| 403 | return | |
| 404 | } | |
| 405 | ||
| 406 | // bar s.width without leftEnd and rightEnd runes | |
| 407 | barWidth := width - 2 | |
| 408 | ||
| 409 | completedWidth := internal.Percentage(s.total, s.current, int64(barWidth)) | |
| 410 | ||
| 411 | if s.refill != nil { | |
| 412 | till := internal.Percentage(s.total, s.refill.till, int64(barWidth)) | |
| 413 | // append refill rune | |
| 414 | var i int64 | |
| 415 | for i = 0; i < till; i++ { | |
| 416 | s.bufB.WriteRune(s.refill.char) | |
| 417 | } | |
| 418 | for i = till; i < completedWidth; i++ { | |
| 419 | s.bufB.WriteRune(s.runes[rFill]) | |
| 420 | } | |
| 421 | } else { | |
| 422 | var i int64 | |
| 423 | for i = 0; i < completedWidth; i++ { | |
| 424 | s.bufB.WriteRune(s.runes[rFill]) | |
| 425 | } | |
| 426 | } | |
| 427 | ||
| 428 | if completedWidth < int64(barWidth) && completedWidth > 0 { | |
| 429 | _, size := utf8.DecodeLastRune(s.bufB.Bytes()) | |
| 430 | s.bufB.Truncate(s.bufB.Len() - size) | |
| 431 | s.bufB.WriteRune(s.runes[rTip]) | |
| 432 | } | |
| 433 | ||
| 434 | for i := completedWidth; i < int64(barWidth); i++ { | |
| 435 | s.bufB.WriteRune(s.runes[rEmpty]) | |
| 436 | } | |
| 437 | } | |
| 355 | // func (s *bState) fillSpinner(width int) { | |
| 356 | // s.bufB.Reset() | |
| 357 | // s.bufB.WriteByte(' ') | |
| 358 | ||
| 359 | // if width <= 2 { | |
| 360 | // s.bufB.WriteByte(' ') | |
| 361 | // return | |
| 362 | // } | |
| 363 | ||
| 364 | // r := s.bType.format[s.current%int64(len(s.bType.format))] | |
| 365 | ||
| 366 | // switch s.alignment { | |
| 367 | // case alignLeft: | |
| 368 | // s.bufB.WriteRune(r) | |
| 369 | // s.bufB.Write(bytes.Repeat([]byte(" "), width-1)) | |
| 370 | // case alignMiddle: | |
| 371 | // mid := width / 2 | |
| 372 | // mod := width % 2 | |
| 373 | // s.bufB.Write(bytes.Repeat([]byte(" "), mid-1+mod)) | |
| 374 | // s.bufB.WriteRune(r) | |
| 375 | // s.bufB.Write(bytes.Repeat([]byte(" "), mid)) | |
| 376 | // case alignRight: | |
| 377 | // s.bufB.Write(bytes.Repeat([]byte(" "), width-1)) | |
| 378 | // s.bufB.WriteRune(r) | |
| 379 | // } | |
| 380 | ||
| 381 | // s.bufB.WriteByte(' ') | |
| 382 | // } | |
| 383 | ||
| 384 | // func (s *bState) fillBar(width int) { | |
| 385 | // s.bufB.Reset() | |
| 386 | // s.bufB.WriteByte(' ') | |
| 387 | ||
| 388 | // // don't count rLeft and rRight [brackets] with trailing spaces | |
| 389 | // width -= 4 | |
| 390 | ||
| 391 | // if width <= 2 { | |
| 392 | // s.bufB.WriteByte(' ') | |
| 393 | // return | |
| 394 | // } | |
| 395 | ||
| 396 | // s.bufB.WriteRune(s.bType.format[rLeft]) | |
| 397 | // completedWidth := internal.Percentage(s.total, s.current, int64(width)) | |
| 398 | ||
| 399 | // if s.refill != nil { | |
| 400 | // till := internal.Percentage(s.total, s.refill.till, int64(width)) | |
| 401 | // // append refill rune | |
| 402 | // for i := int64(0); i < till; i++ { | |
| 403 | // s.bufB.WriteRune(s.refill.char) | |
| 404 | // } | |
| 405 | // for i := till; i < completedWidth; i++ { | |
| 406 | // s.bufB.WriteRune(s.bType.format[rFill]) | |
| 407 | // } | |
| 408 | // } else { | |
| 409 | // for i := int64(0); i < completedWidth; i++ { | |
| 410 | // s.bufB.WriteRune(s.bType.format[rFill]) | |
| 411 | // } | |
| 412 | // } | |
| 413 | ||
| 414 | // if completedWidth < int64(width) && completedWidth > 0 { | |
| 415 | // _, size := utf8.DecodeLastRune(s.bufB.Bytes()) | |
| 416 | // s.bufB.Truncate(s.bufB.Len() - size) | |
| 417 | // s.bufB.WriteRune(s.bType.format[rTip]) | |
| 418 | // } | |
| 419 | ||
| 420 | // for i := completedWidth; i < int64(width); i++ { | |
| 421 | // s.bufB.WriteRune(s.bType.format[rEmpty]) | |
| 422 | // } | |
| 423 | ||
| 424 | // s.bufB.WriteRune(s.bType.format[rRight]) | |
| 425 | // s.bufB.WriteByte(' ') | |
| 426 | // } | |
| 438 | 427 | |
| 439 | 428 | func (s *bState) wSyncTable() [][]chan int { |
| 440 | 429 | columns := make([]chan int, 0, len(s.pDecorators)+len(s.aDecorators)) |
| 467 | 456 | } |
| 468 | 457 | } |
| 469 | 458 | |
| 470 | func strToBarRunes(format string) (array barRunes) { | |
| 471 | for i, n := 0, 0; len(format) > 0; i++ { | |
| 472 | array[i], n = utf8.DecodeRuneInString(format) | |
| 473 | format = format[n:] | |
| 474 | } | |
| 475 | return | |
| 476 | } | |
| 477 | ||
| 478 | 459 | func countLines(b []byte) int { |
| 479 | 460 | return bytes.Count(b, []byte("\n")) |
| 480 | 461 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "bytes" | |
| 4 | "io" | |
| 5 | ||
| 6 | "github.com/vbauerster/mpb/decor" | |
| 7 | "github.com/vbauerster/mpb/internal" | |
| 8 | ) | |
| 9 | ||
| 10 | const ( | |
| 11 | rLeft = iota | |
| 12 | rFill | |
| 13 | rTip | |
| 14 | rEmpty | |
| 15 | rRight | |
| 16 | ) | |
| 17 | ||
| 18 | var defaultBarStyle = []rune("[=>-]") | |
| 19 | ||
| 20 | type barFiller struct { | |
| 21 | format []rune | |
| 22 | refill *refill | |
| 23 | } | |
| 24 | ||
| 25 | func (s *barFiller) fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 26 | ||
| 27 | w.Write([]byte(string(s.format[rLeft]))) | |
| 28 | ||
| 29 | // don't count rLeft and rRight [brackets] | |
| 30 | width -= 2 | |
| 31 | ||
| 32 | if width <= 2 { | |
| 33 | w.Write([]byte(string(s.format[rRight]))) | |
| 34 | return | |
| 35 | } | |
| 36 | ||
| 37 | progressWidth := internal.Percentage(stat.Total, stat.Current, int64(width)) | |
| 38 | needTip := progressWidth < int64(width) && progressWidth > 0 | |
| 39 | ||
| 40 | if needTip { | |
| 41 | progressWidth-- | |
| 42 | } | |
| 43 | ||
| 44 | if s.refill != nil { | |
| 45 | // append refill rune | |
| 46 | times := internal.Percentage(stat.Total, s.refill.limit, int64(width)) | |
| 47 | w.Write(s.repeat(s.refill.r, int(times))) | |
| 48 | rest := progressWidth - times | |
| 49 | w.Write(s.repeat(s.format[rFill], int(rest))) | |
| 50 | } else { | |
| 51 | w.Write(s.repeat(s.format[rFill], int(progressWidth))) | |
| 52 | } | |
| 53 | ||
| 54 | if needTip { | |
| 55 | w.Write([]byte(string(s.format[rTip]))) | |
| 56 | progressWidth++ | |
| 57 | } | |
| 58 | ||
| 59 | rest := int64(width) - progressWidth | |
| 60 | w.Write(s.repeat(s.format[rEmpty], int(rest))) | |
| 61 | w.Write([]byte(string(s.format[rRight]))) | |
| 62 | } | |
| 63 | ||
| 64 | func (s *barFiller) repeat(r rune, count int) []byte { | |
| 65 | return bytes.Repeat([]byte(string(r)), count) | |
| 66 | } |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "io" |
| 4 | "unicode/utf8" | |
| 4 | 5 | |
| 5 | 6 | "github.com/vbauerster/mpb/decor" |
| 6 | 7 | ) |
| 7 | 8 | |
| 8 | // BarOption is a function option which changes the default behavior of a bar, | |
| 9 | // if passed to p.AddBar(int64, ...BarOption) | |
| 9 | // BarOption is a function option which changes the default behavior of a bar. | |
| 10 | 10 | type BarOption func(*bState) |
| 11 | 11 | |
| 12 | // AppendDecorators let you inject decorators to the bar's right side | |
| 12 | // AppendDecorators let you inject decorators to the bar's right side. | |
| 13 | 13 | func AppendDecorators(appenders ...decor.Decorator) BarOption { |
| 14 | 14 | return func(s *bState) { |
| 15 | 15 | for _, decorator := range appenders { |
| 24 | 24 | } |
| 25 | 25 | } |
| 26 | 26 | |
| 27 | // PrependDecorators let you inject decorators to the bar's left side | |
| 27 | // PrependDecorators let you inject decorators to the bar's left side. | |
| 28 | 28 | func PrependDecorators(prependers ...decor.Decorator) BarOption { |
| 29 | 29 | return func(s *bState) { |
| 30 | 30 | for _, decorator := range prependers { |
| 39 | 39 | } |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | // BarTrimLeft trims left side space of the bar | |
| 43 | func BarTrimLeft() BarOption { | |
| 44 | return func(s *bState) { | |
| 45 | s.trimLeftSpace = true | |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | // BarTrimRight trims right space of the bar | |
| 50 | func BarTrimRight() BarOption { | |
| 51 | return func(s *bState) { | |
| 52 | s.trimRightSpace = true | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | // BarTrim trims both left and right spaces of the bar | |
| 57 | func BarTrim() BarOption { | |
| 58 | return func(s *bState) { | |
| 59 | s.trimLeftSpace = true | |
| 60 | s.trimRightSpace = true | |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | // BarID overwrites internal bar id | |
| 42 | // BarID sets bar id. | |
| 65 | 43 | func BarID(id int) BarOption { |
| 66 | 44 | return func(s *bState) { |
| 67 | 45 | s.id = id |
| 110 | 88 | } |
| 111 | 89 | } |
| 112 | 90 | |
| 113 | func barSpinner(spinner string) BarOption { | |
| 91 | // BarStyle sets custom bar style. | |
| 92 | func BarStyle(style string) BarOption { | |
| 114 | 93 | return func(s *bState) { |
| 115 | s.spinner = []rune(spinner) | |
| 94 | if style == "" { | |
| 95 | return | |
| 96 | } | |
| 97 | if bf, ok := s.filler.(*barFiller); ok { | |
| 98 | if !utf8.ValidString(style) { | |
| 99 | panic("invalid style string") | |
| 100 | } | |
| 101 | defaultFormat := bf.format | |
| 102 | bf.format = []rune(style) | |
| 103 | if len(bf.format) < 5 { | |
| 104 | bf.format = defaultFormat | |
| 105 | } | |
| 106 | } | |
| 116 | 107 | } |
| 117 | 108 | } |
| 118 | 109 | |
| 119 | func barWidth(w int) BarOption { | |
| 110 | // SpinnerStyle sets custom Spinner style. | |
| 111 | func SpinnerStyle(frames []string) BarOption { | |
| 120 | 112 | return func(s *bState) { |
| 121 | s.width = w | |
| 113 | if len(frames) == 0 { | |
| 114 | return | |
| 115 | } | |
| 116 | if bf, ok := s.filler.(*spinnerFiller); ok { | |
| 117 | bf.frames = frames | |
| 118 | } | |
| 122 | 119 | } |
| 123 | 120 | } |
| 124 | 121 | |
| 125 | func barFormat(format string) BarOption { | |
| 122 | // AlignLeft align spinner on left, default. | |
| 123 | // Applicable fo spinner bar type only. | |
| 124 | // func AlignLeft() BarOption { | |
| 125 | // return func(s *bState) { | |
| 126 | // if bf, ok := s.filler.(*spinnerFiller); ok { | |
| 127 | // bf.alignment = alignLeft | |
| 128 | // } | |
| 129 | // } | |
| 130 | // } | |
| 131 | ||
| 132 | // AlignMiddle align spinner on the middle. | |
| 133 | // Applicable fo spinner bar type only. | |
| 134 | // func AlignMiddle() BarOption { | |
| 135 | // return func(s *bState) { | |
| 136 | // if bf, ok := s.filler.(*spinnerFiller); ok { | |
| 137 | // bf.alignment = alignMiddle | |
| 138 | // } | |
| 139 | // } | |
| 140 | // } | |
| 141 | ||
| 142 | // AlignRight align spinner on right. | |
| 143 | // Applicable fo spinner bar type only. | |
| 144 | // func AlignRight() BarOption { | |
| 145 | // return func(s *bState) { | |
| 146 | // if bf, ok := s.filler.(*spinnerFiller); ok { | |
| 147 | // bf.alignment = alignRight | |
| 148 | // } | |
| 149 | // } | |
| 150 | // } | |
| 151 | ||
| 152 | func TrimSpace() BarOption { | |
| 126 | 153 | return func(s *bState) { |
| 127 | s.runes = strToBarRunes(format) | |
| 154 | s.trimSpace = true | |
| 128 | 155 | } |
| 129 | 156 | } |
| 17 | 17 | var wg sync.WaitGroup |
| 18 | 18 | p := mpb.New( |
| 19 | 19 | mpb.WithWaitGroup(&wg), |
| 20 | mpb.WithSpinner("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"), | |
| 20 | mpb.WithWidth(13), | |
| 21 | 21 | ) |
| 22 | total, numBars := 100, 3 | |
| 22 | total, numBars := 101, 3 | |
| 23 | 23 | wg.Add(numBars) |
| 24 | 24 | |
| 25 | 25 | for i := 0; i < numBars; i++ { |
| 26 | 26 | name := fmt.Sprintf("Bar#%d:", i) |
| 27 | bar := p.AddBar(int64(total), | |
| 28 | mpb.PrependDecorators( | |
| 29 | // simple name decorator | |
| 30 | decor.Name(name), | |
| 31 | ), | |
| 32 | mpb.AppendDecorators( | |
| 33 | // replace ETA decorator with "done" message, OnComplete event | |
| 34 | decor.OnComplete( | |
| 35 | // ETA decorator with ewma age of 60 | |
| 36 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 27 | var bar *mpb.Bar | |
| 28 | if i == 0 { | |
| 29 | bar = p.AddBar(int64(total), | |
| 30 | mpb.BarStyle("╢▌▌░╟"), | |
| 31 | mpb.PrependDecorators( | |
| 32 | // simple name decorator | |
| 33 | decor.Name(name), | |
| 37 | 34 | ), |
| 38 | ), | |
| 39 | ) | |
| 35 | mpb.AppendDecorators( | |
| 36 | // replace ETA decorator with "done" message, OnComplete event | |
| 37 | decor.OnComplete( | |
| 38 | // ETA decorator with ewma age of 60 | |
| 39 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 40 | ), | |
| 41 | ), | |
| 42 | ) | |
| 43 | } else { | |
| 44 | bar = p.AddSpinner(int64(total), mpb.SpinnerOnMiddle, | |
| 45 | // mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}), | |
| 46 | mpb.PrependDecorators( | |
| 47 | // simple name decorator | |
| 48 | decor.Name(name), | |
| 49 | ), | |
| 50 | mpb.AppendDecorators( | |
| 51 | // replace ETA decorator with "done" message, OnComplete event | |
| 52 | decor.OnComplete( | |
| 53 | // ETA decorator with ewma age of 60 | |
| 54 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 55 | ), | |
| 56 | ), | |
| 57 | ) | |
| 58 | } | |
| 59 | ||
| 40 | 60 | // simulating some work |
| 41 | 61 | go func() { |
| 42 | 62 | defer wg.Done() |
| 3 | 3 | "io" |
| 4 | 4 | "sync" |
| 5 | 5 | "time" |
| 6 | "unicode/utf8" | |
| 7 | 6 | |
| 8 | 7 | "github.com/vbauerster/mpb/cwriter" |
| 9 | 8 | ) |
| 27 | 26 | return func(s *pState) { |
| 28 | 27 | if w >= 0 { |
| 29 | 28 | s.width = w |
| 30 | } | |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | // WithSpinner overrides default bar format to use a spinner | |
| 35 | func WithSpinner(spinner string) ProgressOption { | |
| 36 | return func(s *pState) { | |
| 37 | s.spinner = spinner | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | // WithFormat overrides default bar format "[=>-]" | |
| 42 | func WithFormat(format string) ProgressOption { | |
| 43 | return func(s *pState) { | |
| 44 | if utf8.RuneCountInString(format) == formatLen { | |
| 45 | s.format = format | |
| 46 | 29 | } |
| 47 | 30 | } |
| 48 | 31 | } |
| 16 | 16 | prr = 120 * time.Millisecond |
| 17 | 17 | // default width |
| 18 | 18 | pwidth = 80 |
| 19 | // default format | |
| 20 | pformat = "[=>-]" | |
| 21 | 19 | ) |
| 22 | 20 | |
| 23 | 21 | // Progress represents the container that renders Progress bars |
| 36 | 34 | idCounter int |
| 37 | 35 | width int |
| 38 | 36 | format string |
| 39 | spinner string | |
| 40 | 37 | rr time.Duration |
| 41 | 38 | cw *cwriter.Writer |
| 42 | 39 | pMatrix map[int][]chan int |
| 59 | 56 | s := &pState{ |
| 60 | 57 | bHeap: &pq, |
| 61 | 58 | width: pwidth, |
| 62 | format: pformat, | |
| 63 | 59 | cw: cwriter.New(os.Stdout), |
| 64 | 60 | rr: prr, |
| 65 | 61 | waitBars: make(map[*Bar]*Bar), |
| 84 | 80 | |
| 85 | 81 | // AddBar creates a new progress bar and adds to the container. |
| 86 | 82 | func (p *Progress) AddBar(total int64, options ...BarOption) *Bar { |
| 83 | // make sure filler is initialized first | |
| 84 | args := []BarOption{ | |
| 85 | func(s *bState) { | |
| 86 | s.filler = &barFiller{ | |
| 87 | format: defaultBarStyle, | |
| 88 | } | |
| 89 | }, | |
| 90 | } | |
| 91 | args = append(args, options...) | |
| 92 | return p.add(total, args...) | |
| 93 | } | |
| 94 | ||
| 95 | func (p *Progress) AddSpinner(total int64, alignment SpinnerAlignment, options ...BarOption) *Bar { | |
| 96 | // make sure filler is initialized first | |
| 97 | args := []BarOption{ | |
| 98 | func(s *bState) { | |
| 99 | s.filler = &spinnerFiller{ | |
| 100 | frames: defaultSpinnerStyle, | |
| 101 | alignment: alignment, | |
| 102 | } | |
| 103 | }, | |
| 104 | } | |
| 105 | args = append(args, options...) | |
| 106 | return p.add(total, args...) | |
| 107 | } | |
| 108 | ||
| 109 | func (p *Progress) add(total int64, options ...BarOption) *Bar { | |
| 87 | 110 | p.wg.Add(1) |
| 88 | 111 | result := make(chan *Bar) |
| 89 | 112 | select { |
| 90 | 113 | case p.operateState <- func(s *pState) { |
| 91 | options = append(options, barWidth(s.width), barFormat(s.format)) | |
| 92 | if s.spinner != "" { | |
| 93 | options = append(options, barSpinner(s.spinner)) | |
| 94 | } | |
| 95 | b := newBar(p.wg, s.idCounter, total, s.cancel, options...) | |
| 114 | b := newBar(p.wg, s.idCounter, s.width, total, s.cancel, options...) | |
| 96 | 115 | if b.runningBar != nil { |
| 97 | 116 | s.waitBars[b.runningBar] = b |
| 98 | 117 | } else { |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "strings" | |
| 5 | "unicode/utf8" | |
| 6 | ||
| 7 | "github.com/vbauerster/mpb/decor" | |
| 8 | ) | |
| 9 | ||
| 10 | type SpinnerAlignment int | |
| 11 | ||
| 12 | const ( | |
| 13 | SpinnerOnLeft SpinnerAlignment = iota | |
| 14 | SpinnerOnMiddle | |
| 15 | SpinnerOnRight | |
| 16 | ) | |
| 17 | ||
| 18 | var defaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 19 | ||
| 20 | type spinnerFiller struct { | |
| 21 | frames []string | |
| 22 | alignment SpinnerAlignment | |
| 23 | } | |
| 24 | ||
| 25 | func (s *spinnerFiller) fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 26 | ||
| 27 | frame := s.frames[stat.Current%int64(len(s.frames))] | |
| 28 | frameWidth := utf8.RuneCountInString(frame) | |
| 29 | ||
| 30 | if width < frameWidth { | |
| 31 | return | |
| 32 | } | |
| 33 | ||
| 34 | switch rest := width - frameWidth; s.alignment { | |
| 35 | case SpinnerOnLeft: | |
| 36 | io.WriteString(w, frame+strings.Repeat(" ", rest)) | |
| 37 | case SpinnerOnMiddle: | |
| 38 | str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) | |
| 39 | io.WriteString(w, str) | |
| 40 | case SpinnerOnRight: | |
| 41 | io.WriteString(w, strings.Repeat(" ", rest)+frame) | |
| 42 | } | |
| 43 | } |