New upstream version 3.4.0
Dmitry Smirnov
6 years ago
| 0 | 0 | language: go |
| 1 | 1 | sudo: false |
| 2 | 2 | go: |
| 3 | - 1.8.x | |
| 4 | - 1.9.x | |
| 3 | - 1.10.x | |
| 4 | - tip | |
| 5 | 5 | |
| 6 | 6 | before_install: |
| 7 | 7 | - go get -t -v ./... |
| 30 | 30 | p := mpb.New( |
| 31 | 31 | // override default (80) width |
| 32 | 32 | mpb.WithWidth(64), |
| 33 | // override default "[=>-]" format | |
| 34 | mpb.WithFormat("╢▌▌░╟"), | |
| 35 | 33 | // override default 120ms refresh rate |
| 36 | 34 | mpb.WithRefreshRate(180*time.Millisecond), |
| 37 | 35 | ) |
| 40 | 38 | name := "Single Bar:" |
| 41 | 39 | // adding a single bar |
| 42 | 40 | bar := p.AddBar(int64(total), |
| 41 | // override default "[=>-]" style | |
| 42 | mpb.BarStyle("╢▌▌░╟"), | |
| 43 | 43 | mpb.PrependDecorators( |
| 44 | 44 | // display our name with one space on the right |
| 45 | 45 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "bytes" |
| 4 | "context" | |
| 4 | 5 | "fmt" |
| 5 | 6 | "io" |
| 6 | 7 | "io/ioutil" |
| 10 | 11 | "unicode/utf8" |
| 11 | 12 | |
| 12 | 13 | "github.com/vbauerster/mpb/decor" |
| 13 | "github.com/vbauerster/mpb/internal" | |
| 14 | 14 | ) |
| 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 | 15 | |
| 28 | 16 | // Bar represents a progress Bar |
| 29 | 17 | type Bar struct { |
| 44 | 32 | shutdown chan struct{} |
| 45 | 33 | } |
| 46 | 34 | |
| 35 | // Filler interface. | |
| 36 | // Bar renders by calling Filler's Fill method. You can literally have | |
| 37 | // any bar kind, by implementing this interface and passing it to the | |
| 38 | // Add method. | |
| 39 | type Filler interface { | |
| 40 | Fill(w io.Writer, width int, s *decor.Statistics) | |
| 41 | } | |
| 42 | ||
| 43 | // FillerFunc is function type adapter to convert function into Filler. | |
| 44 | type FillerFunc func(w io.Writer, width int, stat *decor.Statistics) | |
| 45 | ||
| 46 | func (f FillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 47 | f(w, width, stat) | |
| 48 | } | |
| 49 | ||
| 47 | 50 | type ( |
| 48 | 51 | bState struct { |
| 52 | filler Filler | |
| 49 | 53 | id int |
| 50 | 54 | width int |
| 55 | alignment int | |
| 51 | 56 | total int64 |
| 52 | 57 | current int64 |
| 53 | runes barRunes | |
| 54 | trimLeftSpace bool | |
| 55 | trimRightSpace bool | |
| 58 | trimSpace bool | |
| 56 | 59 | toComplete bool |
| 57 | 60 | removeOnComplete bool |
| 58 | 61 | barClearOnComplete bool |
| 72 | 75 | runningBar *Bar |
| 73 | 76 | } |
| 74 | 77 | refill struct { |
| 75 | char rune | |
| 76 | till int64 | |
| 78 | r rune | |
| 79 | limit int64 | |
| 77 | 80 | } |
| 78 | 81 | frameReader struct { |
| 79 | 82 | io.Reader |
| 83 | 86 | } |
| 84 | 87 | ) |
| 85 | 88 | |
| 86 | func newBar(wg *sync.WaitGroup, id int, total int64, cancel <-chan struct{}, options ...BarOption) *Bar { | |
| 87 | if total <= 0 { | |
| 88 | total = time.Now().Unix() | |
| 89 | } | |
| 89 | func newBar( | |
| 90 | ctx context.Context, | |
| 91 | wg *sync.WaitGroup, | |
| 92 | filler Filler, | |
| 93 | id, width int, | |
| 94 | total int64, | |
| 95 | options ...BarOption, | |
| 96 | ) *Bar { | |
| 90 | 97 | |
| 91 | 98 | s := &bState{ |
| 99 | filler: filler, | |
| 92 | 100 | id: id, |
| 93 | 101 | priority: id, |
| 102 | width: width, | |
| 94 | 103 | total: total, |
| 95 | 104 | } |
| 96 | 105 | |
| 103 | 112 | s.bufP = bytes.NewBuffer(make([]byte, 0, s.width)) |
| 104 | 113 | s.bufB = bytes.NewBuffer(make([]byte, 0, s.width)) |
| 105 | 114 | s.bufA = bytes.NewBuffer(make([]byte, 0, s.width)) |
| 115 | if s.newLineExtendFn != nil { | |
| 116 | s.bufNL = bytes.NewBuffer(make([]byte, 0, s.width)) | |
| 117 | } | |
| 106 | 118 | |
| 107 | 119 | b := &Bar{ |
| 108 | 120 | priority: s.priority, |
| 120 | 132 | b.priority = b.runningBar.priority |
| 121 | 133 | } |
| 122 | 134 | |
| 123 | if s.newLineExtendFn != nil { | |
| 124 | s.bufNL = bytes.NewBuffer(make([]byte, 0, s.width)) | |
| 125 | } | |
| 126 | ||
| 127 | go b.serve(wg, s, cancel) | |
| 135 | go b.serve(ctx, wg, s) | |
| 128 | 136 | return b |
| 129 | 137 | } |
| 130 | 138 | |
| 177 | 185 | } |
| 178 | 186 | |
| 179 | 187 | // SetTotal sets total dynamically. |
| 180 | // Set final to true, when total is known, it will trigger bar complete event. | |
| 181 | func (b *Bar) SetTotal(total int64, final bool) bool { | |
| 188 | // Set complete to true, to trigger bar complete event now. | |
| 189 | func (b *Bar) SetTotal(total int64, complete bool) { | |
| 182 | 190 | select { |
| 183 | 191 | case b.operateState <- func(s *bState) { |
| 184 | if total > 0 { | |
| 185 | s.total = total | |
| 186 | } | |
| 187 | if final { | |
| 192 | s.total = total | |
| 193 | if complete && !s.toComplete { | |
| 188 | 194 | s.current = s.total |
| 189 | 195 | s.toComplete = true |
| 190 | 196 | } |
| 191 | 197 | }: |
| 192 | return true | |
| 193 | case <-b.done: | |
| 194 | return false | |
| 195 | } | |
| 196 | } | |
| 197 | ||
| 198 | // SetRefill sets fill rune to r, up until n. | |
| 199 | func (b *Bar) SetRefill(n int, r rune) { | |
| 200 | if n <= 0 { | |
| 201 | return | |
| 202 | } | |
| 198 | case <-b.done: | |
| 199 | } | |
| 200 | } | |
| 201 | ||
| 202 | // SetRefill sets refill, if supported by underlying Filler. | |
| 203 | func (b *Bar) SetRefill(amount int64) { | |
| 203 | 204 | b.operateState <- func(s *bState) { |
| 204 | s.refill = &refill{r, int64(n)} | |
| 205 | } | |
| 206 | } | |
| 207 | ||
| 208 | // RefillBy is deprecated, use SetRefill | |
| 209 | func (b *Bar) RefillBy(n int, r rune) { | |
| 210 | b.SetRefill(n, r) | |
| 205 | if f, ok := s.filler.(interface{ SetRefill(int64) }); ok { | |
| 206 | f.SetRefill(amount) | |
| 207 | } | |
| 208 | } | |
| 211 | 209 | } |
| 212 | 210 | |
| 213 | 211 | // Increment is a shorthand for b.IncrBy(1). |
| 216 | 214 | } |
| 217 | 215 | |
| 218 | 216 | // IncrBy increments progress bar by amount of n. |
| 219 | // wdd is optional work duration i.e. time.Since(start), | |
| 220 | // which expected to be provided, if any ewma based decorator is used. | |
| 217 | // wdd is optional work duration i.e. time.Since(start), which expected | |
| 218 | // to be provided, if any ewma based decorator is used. | |
| 221 | 219 | func (b *Bar) IncrBy(n int, wdd ...time.Duration) { |
| 222 | 220 | select { |
| 223 | 221 | case b.operateState <- func(s *bState) { |
| 224 | 222 | s.current += int64(n) |
| 225 | if s.current >= s.total { | |
| 223 | if s.total > 0 && s.current >= s.total { | |
| 226 | 224 | s.current = s.total |
| 227 | 225 | s.toComplete = true |
| 228 | 226 | } |
| 237 | 235 | // Completed reports whether the bar is in completed state. |
| 238 | 236 | func (b *Bar) Completed() bool { |
| 239 | 237 | // omit select here, because primary usage of the method is for loop |
| 240 | // condition, like for !bar.Completed() {...} | |
| 241 | // so when toComplete=true it is called once (at which time, the bar is still alive), | |
| 242 | // then quits the loop and never suppose to be called afterwards. | |
| 238 | // condition, like for !bar.Completed() {...} so when toComplete=true | |
| 239 | // it is called once (at which time, the bar is still alive), then | |
| 240 | // quits the loop and never suppose to be called afterwards. | |
| 243 | 241 | return <-b.boolCh |
| 244 | 242 | } |
| 245 | 243 | |
| 252 | 250 | } |
| 253 | 251 | } |
| 254 | 252 | |
| 255 | func (b *Bar) serve(wg *sync.WaitGroup, s *bState, cancel <-chan struct{}) { | |
| 253 | func (b *Bar) serve(ctx context.Context, wg *sync.WaitGroup, s *bState) { | |
| 256 | 254 | defer wg.Done() |
| 255 | cancel := ctx.Done() | |
| 257 | 256 | for { |
| 258 | 257 | select { |
| 259 | 258 | case op := <-b.operateState: |
| 321 | 320 | } |
| 322 | 321 | |
| 323 | 322 | func (s *bState) draw(termWidth int) io.Reader { |
| 324 | defer s.bufA.WriteByte('\n') | |
| 325 | ||
| 326 | 323 | if s.panicMsg != "" { |
| 327 | 324 | return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", termWidth), s.panicMsg)) |
| 328 | 325 | } |
| 337 | 334 | s.bufA.WriteString(d.Decor(stat)) |
| 338 | 335 | } |
| 339 | 336 | |
| 337 | if s.barClearOnComplete && s.completeFlushed { | |
| 338 | s.bufA.WriteByte('\n') | |
| 339 | return io.MultiReader(s.bufP, s.bufA) | |
| 340 | } | |
| 341 | ||
| 340 | 342 | prependCount := utf8.RuneCount(s.bufP.Bytes()) |
| 341 | 343 | appendCount := utf8.RuneCount(s.bufA.Bytes()) |
| 342 | 344 | |
| 343 | if s.barClearOnComplete && s.completeFlushed { | |
| 344 | return io.MultiReader(s.bufP, s.bufA) | |
| 345 | } | |
| 346 | ||
| 347 | s.fillBar(s.width) | |
| 348 | barCount := utf8.RuneCount(s.bufB.Bytes()) | |
| 349 | totalCount := prependCount + barCount + appendCount | |
| 350 | if spaceCount := 0; totalCount > termWidth { | |
| 351 | if !s.trimLeftSpace { | |
| 352 | spaceCount++ | |
| 353 | } | |
| 354 | if !s.trimRightSpace { | |
| 355 | spaceCount++ | |
| 356 | } | |
| 357 | s.fillBar(termWidth - prependCount - appendCount - spaceCount) | |
| 358 | } | |
| 359 | ||
| 345 | if !s.trimSpace { | |
| 346 | // reserve space for edge spaces | |
| 347 | termWidth -= 2 | |
| 348 | s.bufB.WriteByte(' ') | |
| 349 | } | |
| 350 | ||
| 351 | if prependCount+s.width+appendCount > termWidth { | |
| 352 | s.filler.Fill(s.bufB, termWidth-prependCount-appendCount, stat) | |
| 353 | } else { | |
| 354 | s.filler.Fill(s.bufB, s.width, stat) | |
| 355 | } | |
| 356 | ||
| 357 | if !s.trimSpace { | |
| 358 | s.bufB.WriteByte(' ') | |
| 359 | } | |
| 360 | ||
| 361 | s.bufA.WriteByte('\n') | |
| 360 | 362 | return io.MultiReader(s.bufP, s.bufB, s.bufA) |
| 361 | } | |
| 362 | ||
| 363 | func (s *bState) fillBar(width int) { | |
| 364 | defer func() { | |
| 365 | s.bufB.WriteRune(s.runes[rRight]) | |
| 366 | if !s.trimRightSpace { | |
| 367 | s.bufB.WriteByte(' ') | |
| 368 | } | |
| 369 | }() | |
| 370 | ||
| 371 | s.bufB.Reset() | |
| 372 | if !s.trimLeftSpace { | |
| 373 | s.bufB.WriteByte(' ') | |
| 374 | } | |
| 375 | s.bufB.WriteRune(s.runes[rLeft]) | |
| 376 | if width <= 2 { | |
| 377 | return | |
| 378 | } | |
| 379 | ||
| 380 | // bar s.width without leftEnd and rightEnd runes | |
| 381 | barWidth := width - 2 | |
| 382 | ||
| 383 | completedWidth := internal.Percentage(s.total, s.current, int64(barWidth)) | |
| 384 | ||
| 385 | if s.refill != nil { | |
| 386 | till := internal.Percentage(s.total, s.refill.till, int64(barWidth)) | |
| 387 | // append refill rune | |
| 388 | var i int64 | |
| 389 | for i = 0; i < till; i++ { | |
| 390 | s.bufB.WriteRune(s.refill.char) | |
| 391 | } | |
| 392 | for i = till; i < completedWidth; i++ { | |
| 393 | s.bufB.WriteRune(s.runes[rFill]) | |
| 394 | } | |
| 395 | } else { | |
| 396 | var i int64 | |
| 397 | for i = 0; i < completedWidth; i++ { | |
| 398 | s.bufB.WriteRune(s.runes[rFill]) | |
| 399 | } | |
| 400 | } | |
| 401 | ||
| 402 | if completedWidth < int64(barWidth) && completedWidth > 0 { | |
| 403 | _, size := utf8.DecodeLastRune(s.bufB.Bytes()) | |
| 404 | s.bufB.Truncate(s.bufB.Len() - size) | |
| 405 | s.bufB.WriteRune(s.runes[rTip]) | |
| 406 | } | |
| 407 | ||
| 408 | for i := completedWidth; i < int64(barWidth); i++ { | |
| 409 | s.bufB.WriteRune(s.runes[rEmpty]) | |
| 410 | } | |
| 411 | 363 | } |
| 412 | 364 | |
| 413 | 365 | func (s *bState) wSyncTable() [][]chan int { |
| 441 | 393 | } |
| 442 | 394 | } |
| 443 | 395 | |
| 444 | func strToBarRunes(format string) (array barRunes) { | |
| 445 | for i, n := 0, 0; len(format) > 0; i++ { | |
| 446 | array[i], n = utf8.DecodeRuneInString(format) | |
| 447 | format = format[n:] | |
| 448 | } | |
| 449 | return | |
| 450 | } | |
| 451 | ||
| 452 | 396 | func countLines(b []byte) int { |
| 453 | 397 | return bytes.Count(b, []byte("\n")) |
| 454 | 398 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "unicode/utf8" | |
| 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 | rRevTip | |
| 17 | rRefill | |
| 18 | ) | |
| 19 | ||
| 20 | var defaultBarStyle = "[=>-]<+" | |
| 21 | ||
| 22 | type barFiller struct { | |
| 23 | format [][]byte | |
| 24 | refillAmount int64 | |
| 25 | reverse bool | |
| 26 | } | |
| 27 | ||
| 28 | func newDefaultBarFiller() Filler { | |
| 29 | bf := &barFiller{ | |
| 30 | format: make([][]byte, utf8.RuneCountInString(defaultBarStyle)), | |
| 31 | } | |
| 32 | bf.setStyle(defaultBarStyle) | |
| 33 | return bf | |
| 34 | } | |
| 35 | ||
| 36 | func (s *barFiller) setStyle(style string) { | |
| 37 | if !utf8.ValidString(style) { | |
| 38 | return | |
| 39 | } | |
| 40 | src := make([][]byte, 0, utf8.RuneCountInString(style)) | |
| 41 | for _, r := range style { | |
| 42 | src = append(src, []byte(string(r))) | |
| 43 | } | |
| 44 | copy(s.format, src) | |
| 45 | } | |
| 46 | ||
| 47 | func (s *barFiller) setReverse() { | |
| 48 | s.reverse = true | |
| 49 | } | |
| 50 | ||
| 51 | func (s *barFiller) SetRefill(amount int64) { | |
| 52 | s.refillAmount = amount | |
| 53 | } | |
| 54 | ||
| 55 | func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 56 | ||
| 57 | // don't count rLeft and rRight [brackets] | |
| 58 | width -= 2 | |
| 59 | if width < 2 { | |
| 60 | return | |
| 61 | } | |
| 62 | ||
| 63 | w.Write(s.format[rLeft]) | |
| 64 | if width == 2 { | |
| 65 | w.Write(s.format[rRight]) | |
| 66 | return | |
| 67 | } | |
| 68 | ||
| 69 | bb := make([][]byte, width) | |
| 70 | ||
| 71 | cwidth := int(internal.Percentage(stat.Total, stat.Current, int64(width))) | |
| 72 | ||
| 73 | for i := 0; i < cwidth; i++ { | |
| 74 | bb[i] = s.format[rFill] | |
| 75 | } | |
| 76 | ||
| 77 | if s.refillAmount > 0 { | |
| 78 | var rwidth int | |
| 79 | if s.refillAmount > stat.Current { | |
| 80 | rwidth = cwidth | |
| 81 | } else { | |
| 82 | rwidth = int(internal.Percentage(stat.Total, int64(s.refillAmount), int64(width))) | |
| 83 | } | |
| 84 | for i := 0; i < rwidth; i++ { | |
| 85 | bb[i] = s.format[rRefill] | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | if cwidth > 0 && cwidth < width { | |
| 90 | bb[cwidth-1] = s.format[rTip] | |
| 91 | } | |
| 92 | ||
| 93 | for i := cwidth; i < width; i++ { | |
| 94 | bb[i] = s.format[rEmpty] | |
| 95 | } | |
| 96 | ||
| 97 | if s.reverse { | |
| 98 | if cwidth > 0 && cwidth < width { | |
| 99 | bb[cwidth-1] = s.format[rRevTip] | |
| 100 | } | |
| 101 | for i := len(bb) - 1; i >= 0; i-- { | |
| 102 | w.Write(bb[i]) | |
| 103 | } | |
| 104 | } else { | |
| 105 | for i := 0; i < len(bb); i++ { | |
| 106 | w.Write(bb[i]) | |
| 107 | } | |
| 108 | } | |
| 109 | w.Write(s.format[rRight]) | |
| 110 | } |
| 5 | 5 | "github.com/vbauerster/mpb/decor" |
| 6 | 6 | ) |
| 7 | 7 | |
| 8 | // BarOption is a function option which changes the default behavior of a bar, | |
| 9 | // if passed to p.AddBar(int64, ...BarOption) | |
| 8 | // BarOption is a function option which changes the default behavior of a bar. | |
| 10 | 9 | type BarOption func(*bState) |
| 11 | 10 | |
| 12 | // AppendDecorators let you inject decorators to the bar's right side | |
| 11 | // AppendDecorators let you inject decorators to the bar's right side. | |
| 13 | 12 | func AppendDecorators(appenders ...decor.Decorator) BarOption { |
| 14 | 13 | return func(s *bState) { |
| 15 | 14 | for _, decorator := range appenders { |
| 24 | 23 | } |
| 25 | 24 | } |
| 26 | 25 | |
| 27 | // PrependDecorators let you inject decorators to the bar's left side | |
| 26 | // PrependDecorators let you inject decorators to the bar's left side. | |
| 28 | 27 | func PrependDecorators(prependers ...decor.Decorator) BarOption { |
| 29 | 28 | return func(s *bState) { |
| 30 | 29 | for _, decorator := range prependers { |
| 39 | 38 | } |
| 40 | 39 | } |
| 41 | 40 | |
| 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 | |
| 41 | // BarID sets bar id. | |
| 65 | 42 | func BarID(id int) BarOption { |
| 66 | 43 | return func(s *bState) { |
| 67 | 44 | s.id = id |
| 68 | 45 | } |
| 69 | 46 | } |
| 70 | 47 | |
| 71 | // BarRemoveOnComplete is a flag, if set whole bar line will be removed on complete event. | |
| 72 | // If both BarRemoveOnComplete and BarClearOnComplete are set, first bar section gets cleared | |
| 73 | // and then whole bar line gets removed completely. | |
| 48 | // BarWidth sets bar width independent of the container. | |
| 49 | func BarWidth(width int) BarOption { | |
| 50 | return func(s *bState) { | |
| 51 | s.width = width | |
| 52 | } | |
| 53 | } | |
| 54 | ||
| 55 | // BarRemoveOnComplete is a flag, if set whole bar line will be removed | |
| 56 | // on complete event. If both BarRemoveOnComplete and BarClearOnComplete | |
| 57 | // are set, first bar section gets cleared and then whole bar line | |
| 58 | // gets removed completely. | |
| 74 | 59 | func BarRemoveOnComplete() BarOption { |
| 75 | 60 | return func(s *bState) { |
| 76 | 61 | s.removeOnComplete = true |
| 77 | 62 | } |
| 78 | 63 | } |
| 79 | 64 | |
| 80 | // BarReplaceOnComplete is indicator for delayed bar start, after the `runningBar` is complete. | |
| 81 | // To achieve bar replacement effect, `runningBar` should has its `BarRemoveOnComplete` option set. | |
| 65 | // BarReplaceOnComplete is indicator for delayed bar start, after the | |
| 66 | // `runningBar` is complete. To achieve bar replacement effect, | |
| 67 | // `runningBar` should has its `BarRemoveOnComplete` option set. | |
| 82 | 68 | func BarReplaceOnComplete(runningBar *Bar) BarOption { |
| 69 | return BarParkTo(runningBar) | |
| 70 | } | |
| 71 | ||
| 72 | // BarParkTo same as BarReplaceOnComplete | |
| 73 | func BarParkTo(runningBar *Bar) BarOption { | |
| 83 | 74 | return func(s *bState) { |
| 84 | 75 | s.runningBar = runningBar |
| 85 | 76 | } |
| 86 | 77 | } |
| 87 | 78 | |
| 88 | // BarClearOnComplete is a flag, if set will clear bar section on complete event. | |
| 89 | // If you need to remove a whole bar line, refer to BarRemoveOnComplete. | |
| 79 | // BarClearOnComplete is a flag, if set will clear bar section on | |
| 80 | // complete event. If you need to remove a whole bar line, refer to | |
| 81 | // BarRemoveOnComplete. | |
| 90 | 82 | func BarClearOnComplete() BarOption { |
| 91 | 83 | return func(s *bState) { |
| 92 | 84 | s.barClearOnComplete = true |
| 93 | 85 | } |
| 94 | 86 | } |
| 95 | 87 | |
| 96 | // BarPriority sets bar's priority. | |
| 97 | // Zero is highest priority, i.e. bar will be on top. | |
| 98 | // If `BarReplaceOnComplete` option is supplied, this option is ignored. | |
| 88 | // BarPriority sets bar's priority. Zero is highest priority, i.e. bar | |
| 89 | // will be on top. If `BarReplaceOnComplete` option is supplied, this | |
| 90 | // option is ignored. | |
| 99 | 91 | func BarPriority(priority int) BarOption { |
| 100 | 92 | return func(s *bState) { |
| 101 | 93 | s.priority = priority |
| 102 | 94 | } |
| 103 | 95 | } |
| 104 | 96 | |
| 105 | // BarNewLineExtend takes user defined efn, which gets called each render cycle. | |
| 106 | // Any write to provided writer of efn, will appear on new line of respective bar. | |
| 97 | // BarNewLineExtend takes user defined efn, which gets called each | |
| 98 | // render cycle. Any write to provided writer of efn, will appear on | |
| 99 | // new line of respective bar. | |
| 107 | 100 | func BarNewLineExtend(efn func(io.Writer, *decor.Statistics)) BarOption { |
| 108 | 101 | return func(s *bState) { |
| 109 | 102 | s.newLineExtendFn = efn |
| 110 | 103 | } |
| 111 | 104 | } |
| 112 | 105 | |
| 113 | func barWidth(w int) BarOption { | |
| 106 | // TrimSpace trims bar's edge spaces. | |
| 107 | func TrimSpace() BarOption { | |
| 114 | 108 | return func(s *bState) { |
| 115 | s.width = w | |
| 109 | s.trimSpace = true | |
| 116 | 110 | } |
| 117 | 111 | } |
| 118 | 112 | |
| 119 | func barFormat(format string) BarOption { | |
| 113 | // BarStyle sets custom bar style, default one is "[=>-]<+". | |
| 114 | // | |
| 115 | // '[' left bracket rune | |
| 116 | // | |
| 117 | // '=' fill rune | |
| 118 | // | |
| 119 | // '>' tip rune | |
| 120 | // | |
| 121 | // '-' empty rune | |
| 122 | // | |
| 123 | // ']' right bracket rune | |
| 124 | // | |
| 125 | // '<' reverse tip rune, used when BarReverse option is set | |
| 126 | // | |
| 127 | // '+' refill rune, used when *Bar.SetRefill(int64) is called | |
| 128 | // | |
| 129 | // It's ok to provide first five runes only, for example mpb.BarStyle("╢▌▌░╟") | |
| 130 | func BarStyle(style string) BarOption { | |
| 131 | chk := func(filler Filler) (interface{}, bool) { | |
| 132 | if style == "" { | |
| 133 | return nil, false | |
| 134 | } | |
| 135 | t, ok := filler.(*barFiller) | |
| 136 | return t, ok | |
| 137 | } | |
| 138 | cb := func(t interface{}) { | |
| 139 | t.(*barFiller).setStyle(style) | |
| 140 | } | |
| 141 | return MakeFillerTypeSpecificBarOption(chk, cb) | |
| 142 | } | |
| 143 | ||
| 144 | // BarReverse reverse mode, bar will progress from right to left. | |
| 145 | func BarReverse() BarOption { | |
| 146 | chk := func(filler Filler) (interface{}, bool) { | |
| 147 | t, ok := filler.(*barFiller) | |
| 148 | return t, ok | |
| 149 | } | |
| 150 | cb := func(t interface{}) { | |
| 151 | t.(*barFiller).setReverse() | |
| 152 | } | |
| 153 | return MakeFillerTypeSpecificBarOption(chk, cb) | |
| 154 | } | |
| 155 | ||
| 156 | // SpinnerStyle sets custom spinner style. | |
| 157 | // Effective when Filler type is spinner. | |
| 158 | func SpinnerStyle(frames []string) BarOption { | |
| 159 | chk := func(filler Filler) (interface{}, bool) { | |
| 160 | if len(frames) == 0 { | |
| 161 | return nil, false | |
| 162 | } | |
| 163 | t, ok := filler.(*spinnerFiller) | |
| 164 | return t, ok | |
| 165 | } | |
| 166 | cb := func(t interface{}) { | |
| 167 | t.(*spinnerFiller).frames = frames | |
| 168 | } | |
| 169 | return MakeFillerTypeSpecificBarOption(chk, cb) | |
| 170 | } | |
| 171 | ||
| 172 | // MakeFillerTypeSpecificBarOption makes BarOption specific to Filler's | |
| 173 | // actual type. If you implement your own Filler, so most probably | |
| 174 | // you'll need this. See BarStyle or SpinnerStyle for example. | |
| 175 | func MakeFillerTypeSpecificBarOption( | |
| 176 | typeChecker func(Filler) (interface{}, bool), | |
| 177 | cb func(interface{}), | |
| 178 | ) BarOption { | |
| 120 | 179 | return func(s *bState) { |
| 121 | s.runes = strToBarRunes(format) | |
| 180 | if t, ok := typeChecker(s.filler); ok { | |
| 181 | cb(t) | |
| 182 | } | |
| 122 | 183 | } |
| 123 | 184 | } |
| 185 | ||
| 186 | // OptionOnCondition returns option when condition evaluates to true. | |
| 187 | func OptionOnCondition(option BarOption, condition func() bool) BarOption { | |
| 188 | if condition() { | |
| 189 | return option | |
| 190 | } | |
| 191 | return nil | |
| 192 | } | |
| 6 | 6 | "strings" |
| 7 | 7 | "testing" |
| 8 | 8 | "time" |
| 9 | "unicode/utf8" | |
| 9 | 10 | |
| 10 | 11 | . "github.com/vbauerster/mpb" |
| 11 | 12 | "github.com/vbauerster/mpb/decor" |
| 59 | 60 | |
| 60 | 61 | total := 100 |
| 61 | 62 | till := 30 |
| 62 | refillRune := '+' | |
| 63 | ||
| 64 | bar := p.AddBar(int64(total), BarTrim()) | |
| 65 | ||
| 66 | bar.SetRefill(till, refillRune) | |
| 63 | refillRune, _ := utf8.DecodeLastRuneInString(DefaultBarStyle) | |
| 64 | ||
| 65 | bar := p.AddBar(int64(total), TrimSpace()) | |
| 66 | ||
| 67 | bar.SetRefill(int64(till)) | |
| 67 | 68 | bar.IncrBy(till) |
| 68 | 69 | |
| 69 | 70 | for i := 0; i < total-till; i++ { |
| 75 | 76 | |
| 76 | 77 | wantBar := fmt.Sprintf("[%s%s]", |
| 77 | 78 | strings.Repeat(string(refillRune), till-1), |
| 78 | strings.Repeat("=", total-till-1)) | |
| 79 | ||
| 80 | if !strings.Contains(buf.String(), wantBar) { | |
| 81 | t.Errorf("Want bar: %s, got bar: %s\n", wantBar, buf.String()) | |
| 79 | strings.Repeat("=", total-till-1), | |
| 80 | ) | |
| 81 | ||
| 82 | got := string(getLastLine(buf.Bytes())) | |
| 83 | ||
| 84 | if !strings.Contains(got, wantBar) { | |
| 85 | t.Errorf("Want bar: %q, got bar: %q\n", wantBar, got) | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | func TestBarHas100PercentWithOnCompleteDecorator(t *testing.T) { | |
| 90 | var buf bytes.Buffer | |
| 91 | ||
| 92 | p := New(WithOutput(&buf)) | |
| 93 | ||
| 94 | total := 50 | |
| 95 | ||
| 96 | bar := p.AddBar(int64(total), | |
| 97 | AppendDecorators( | |
| 98 | decor.OnComplete( | |
| 99 | decor.Percentage(), "done", | |
| 100 | ), | |
| 101 | ), | |
| 102 | ) | |
| 103 | ||
| 104 | for i := 0; i < total; i++ { | |
| 105 | bar.Increment() | |
| 106 | time.Sleep(10 * time.Millisecond) | |
| 107 | } | |
| 108 | ||
| 109 | p.Wait() | |
| 110 | ||
| 111 | hundred := "100 %" | |
| 112 | if !bytes.Contains(buf.Bytes(), []byte(hundred)) { | |
| 113 | t.Errorf("Bar's buffer does not contain: %q\n", hundred) | |
| 114 | } | |
| 115 | } | |
| 116 | ||
| 117 | func TestBarHas100PercentWithBarRemoveOnComplete(t *testing.T) { | |
| 118 | var buf bytes.Buffer | |
| 119 | ||
| 120 | p := New(WithOutput(&buf)) | |
| 121 | ||
| 122 | total := 50 | |
| 123 | ||
| 124 | bar := p.AddBar(int64(total), | |
| 125 | BarRemoveOnComplete(), | |
| 126 | AppendDecorators(decor.Percentage()), | |
| 127 | ) | |
| 128 | ||
| 129 | for i := 0; i < total; i++ { | |
| 130 | bar.Increment() | |
| 131 | time.Sleep(10 * time.Millisecond) | |
| 132 | } | |
| 133 | ||
| 134 | p.Wait() | |
| 135 | ||
| 136 | hundred := "100 %" | |
| 137 | if !bytes.Contains(buf.Bytes(), []byte(hundred)) { | |
| 138 | t.Errorf("Bar's buffer does not contain: %q\n", hundred) | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | func TestBarStyle(t *testing.T) { | |
| 143 | var buf bytes.Buffer | |
| 144 | customFormat := "╢▌▌░╟" | |
| 145 | p := New(WithOutput(&buf)) | |
| 146 | total := 80 | |
| 147 | bar := p.AddBar(int64(total), BarStyle(customFormat), TrimSpace()) | |
| 148 | ||
| 149 | for i := 0; i < total; i++ { | |
| 150 | bar.Increment() | |
| 151 | time.Sleep(10 * time.Millisecond) | |
| 152 | } | |
| 153 | ||
| 154 | p.Wait() | |
| 155 | ||
| 156 | runes := []rune(customFormat) | |
| 157 | wantBar := fmt.Sprintf("%s%s%s", | |
| 158 | string(runes[0]), | |
| 159 | strings.Repeat(string(runes[1]), total-2), | |
| 160 | string(runes[len(runes)-1]), | |
| 161 | ) | |
| 162 | got := string(getLastLine(buf.Bytes())) | |
| 163 | ||
| 164 | if got != wantBar { | |
| 165 | t.Errorf("Want bar: %q:%d, got bar: %q:%d\n", wantBar, utf8.RuneCountInString(wantBar), got, utf8.RuneCountInString(got)) | |
| 82 | 166 | } |
| 83 | 167 | } |
| 84 | 168 | |
| 21 | 21 | clearCursorAndLine = cursorUp + clearLine |
| 22 | 22 | ) |
| 23 | 23 | |
| 24 | // Writer is a buffered the writer that updates the terminal. | |
| 25 | // The contents of writer will be flushed when Flush is called. | |
| 24 | // Writer is a buffered the writer that updates the terminal. The | |
| 25 | // contents of writer will be flushed when Flush is called. | |
| 26 | 26 | type Writer struct { |
| 27 | 27 | out io.Writer |
| 28 | 28 | buf bytes.Buffer |
| 63 | 63 | return w.buf.WriteString(s) |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | // ReadFrom reads from the provided io.Reader and writes to the underlying buffer. | |
| 66 | // ReadFrom reads from the provided io.Reader and writes to the | |
| 67 | // underlying buffer. | |
| 67 | 68 | func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) { |
| 68 | 69 | return w.buf.ReadFrom(r) |
| 69 | 70 | } |
| 70 | 71 | |
| 72 | // GetWidth returns width of underlying terminal. | |
| 71 | 73 | func (w *Writer) GetWidth() (int, error) { |
| 72 | 74 | if w.isTerminal { |
| 73 | 75 | tw, _, err := terminal.GetSize(w.fd) |
| 7 | 7 | "syscall" |
| 8 | 8 | "unsafe" |
| 9 | 9 | |
| 10 | "github.com/mattn/go-isatty" | |
| 10 | isatty "github.com/mattn/go-isatty" | |
| 11 | 11 | ) |
| 12 | 12 | |
| 13 | 13 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") |
| 140 | 140 | return Counters(0, pairFormat, wcc...) |
| 141 | 141 | } |
| 142 | 142 | |
| 143 | // CountersKibiByte is a wrapper around Counters with predefined unit UnitKiB (bytes/1024). | |
| 143 | // CountersKibiByte is a wrapper around Counters with predefined unit | |
| 144 | // UnitKiB (bytes/1024). | |
| 144 | 145 | func CountersKibiByte(pairFormat string, wcc ...WC) Decorator { |
| 145 | 146 | return Counters(UnitKiB, pairFormat, wcc...) |
| 146 | 147 | } |
| 147 | 148 | |
| 148 | // CountersKiloByte is a wrapper around Counters with predefined unit UnitKB (bytes/1000). | |
| 149 | // CountersKiloByte is a wrapper around Counters with predefined unit | |
| 150 | // UnitKB (bytes/1000). | |
| 149 | 151 | func CountersKiloByte(pairFormat string, wcc ...WC) Decorator { |
| 150 | 152 | return Counters(UnitKB, pairFormat, wcc...) |
| 151 | 153 | } |
| 30 | 30 | DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight |
| 31 | 31 | ) |
| 32 | 32 | |
| 33 | // TimeStyle enum. | |
| 34 | type TimeStyle int | |
| 35 | ||
| 36 | // TimeStyle kinds. | |
| 33 | 37 | const ( |
| 34 | ET_STYLE_GO = iota | |
| 38 | ET_STYLE_GO TimeStyle = iota | |
| 35 | 39 | ET_STYLE_HHMMSS |
| 36 | 40 | ET_STYLE_HHMM |
| 37 | 41 | ET_STYLE_MMSS |
| 46 | 50 | } |
| 47 | 51 | |
| 48 | 52 | // Decorator interface. |
| 49 | // A decorator must implement this interface, in order to be used with mpb library. | |
| 53 | // A decorator must implement this interface, in order to be used with | |
| 54 | // mpb library. | |
| 50 | 55 | type Decorator interface { |
| 51 | 56 | Decor(*Statistics) string |
| 52 | 57 | Syncable |
| 53 | 58 | } |
| 54 | 59 | |
| 55 | 60 | // Syncable interface. |
| 56 | // All decorators implement this interface implicitly. | |
| 57 | // Its Syncable method exposes width sync channel, if sync is enabled. | |
| 61 | // All decorators implement this interface implicitly. Its Syncable | |
| 62 | // method exposes width sync channel, if sync is enabled. | |
| 58 | 63 | type Syncable interface { |
| 59 | 64 | Syncable() (bool, chan int) |
| 60 | 65 | } |
| 61 | 66 | |
| 62 | 67 | // OnCompleteMessenger interface. |
| 63 | // Decorators implementing this interface suppose to return provided string on complete event. | |
| 68 | // Decorators implementing this interface suppose to return provided | |
| 69 | // string on complete event. | |
| 64 | 70 | type OnCompleteMessenger interface { |
| 65 | 71 | OnCompleteMessage(string) |
| 66 | 72 | } |
| 67 | 73 | |
| 68 | 74 | // AmountReceiver interface. |
| 69 | // If decorator needs to receive increment amount, | |
| 70 | // so this is the right interface to implement. | |
| 75 | // If decorator needs to receive increment amount, so this is the right | |
| 76 | // interface to implement. | |
| 71 | 77 | type AmountReceiver interface { |
| 72 | 78 | NextAmount(int, ...time.Duration) |
| 73 | 79 | } |
| 74 | 80 | |
| 75 | 81 | // ShutdownListener interface. |
| 76 | // If decorator needs to be notified once upon bar shutdown event, | |
| 77 | // so this is the right interface to implement. | |
| 82 | // If decorator needs to be notified once upon bar shutdown event, so | |
| 83 | // this is the right interface to implement. | |
| 78 | 84 | type ShutdownListener interface { |
| 79 | 85 | Shutdown() |
| 80 | 86 | } |
| 89 | 95 | |
| 90 | 96 | // WC is a struct with two public fields W and C, both of int type. |
| 91 | 97 | // W represents width and C represents bit set of width related config. |
| 98 | // A decorator should embed WC, in order to become Syncable. | |
| 92 | 99 | type WC struct { |
| 93 | 100 | W int |
| 94 | 101 | C int |
| 125 | 132 | } |
| 126 | 133 | } |
| 127 | 134 | |
| 135 | // Syncable is implementation of Syncable interface. | |
| 128 | 136 | func (wc *WC) Syncable() (bool, chan int) { |
| 129 | 137 | return (wc.C & DSyncWidth) != 0, wc.wsync |
| 130 | 138 | } |
| 131 | 139 | |
| 132 | // OnComplete returns decorator, which wraps provided decorator, with sole | |
| 133 | // purpose to display provided message on complete event. | |
| 140 | // OnComplete returns decorator, which wraps provided decorator, with | |
| 141 | // sole purpose to display provided message on complete event. | |
| 134 | 142 | // |
| 135 | 143 | // `decorator` Decorator to wrap |
| 136 | 144 | // |
| 9 | 9 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] |
| 10 | 10 | // |
| 11 | 11 | // `wcc` optional WC config |
| 12 | func Elapsed(style int, wcc ...WC) Decorator { | |
| 12 | func Elapsed(style TimeStyle, wcc ...WC) Decorator { | |
| 13 | 13 | var wc WC |
| 14 | 14 | for _, widthConf := range wcc { |
| 15 | 15 | wc = widthConf |
| 25 | 25 | |
| 26 | 26 | type elapsedDecorator struct { |
| 27 | 27 | WC |
| 28 | style int | |
| 28 | style TimeStyle | |
| 29 | 29 | startTime time.Time |
| 30 | 30 | msg string |
| 31 | 31 | completeMsg *string |
| 5 | 5 | "time" |
| 6 | 6 | |
| 7 | 7 | "github.com/VividCortex/ewma" |
| 8 | "github.com/vbauerster/mpb/internal" | |
| 9 | 8 | ) |
| 10 | 9 | |
| 11 | 10 | type TimeNormalizer func(time.Duration) time.Duration |
| 17 | 16 | // `age` is the previous N samples to average over. |
| 18 | 17 | // |
| 19 | 18 | // `wcc` optional WC config |
| 20 | func EwmaETA(style int, age float64, wcc ...WC) Decorator { | |
| 19 | func EwmaETA(style TimeStyle, age float64, wcc ...WC) Decorator { | |
| 21 | 20 | return MovingAverageETA(style, ewma.NewMovingAverage(age), NopNormalizer(), wcc...) |
| 22 | 21 | } |
| 23 | 22 | |
| 30 | 29 | // `normalizer` available implementations are [NopNormalizer|FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer] |
| 31 | 30 | // |
| 32 | 31 | // `wcc` optional WC config |
| 33 | func MovingAverageETA(style int, average MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator { | |
| 32 | func MovingAverageETA(style TimeStyle, average MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator { | |
| 34 | 33 | var wc WC |
| 35 | 34 | for _, widthConf := range wcc { |
| 36 | 35 | wc = widthConf |
| 47 | 46 | |
| 48 | 47 | type movingAverageETA struct { |
| 49 | 48 | WC |
| 50 | style int | |
| 49 | style TimeStyle | |
| 51 | 50 | average ewma.MovingAverage |
| 52 | 51 | completeMsg *string |
| 53 | 52 | normalizer TimeNormalizer |
| 58 | 57 | return d.FormatMsg(*d.completeMsg) |
| 59 | 58 | } |
| 60 | 59 | |
| 61 | v := internal.Round(d.average.Value()) | |
| 60 | v := math.Round(d.average.Value()) | |
| 62 | 61 | remaining := d.normalizer(time.Duration((st.Total - st.Current) * int64(v))) |
| 63 | 62 | hours := int64((remaining / time.Hour) % 60) |
| 64 | 63 | minutes := int64((remaining / time.Minute) % 60) |
| 104 | 103 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] |
| 105 | 104 | // |
| 106 | 105 | // `wcc` optional WC config |
| 107 | func AverageETA(style int, wcc ...WC) Decorator { | |
| 106 | func AverageETA(style TimeStyle, wcc ...WC) Decorator { | |
| 108 | 107 | var wc WC |
| 109 | 108 | for _, widthConf := range wcc { |
| 110 | 109 | wc = widthConf |
| 120 | 119 | |
| 121 | 120 | type averageETA struct { |
| 122 | 121 | WC |
| 123 | style int | |
| 122 | style TimeStyle | |
| 124 | 123 | startTime time.Time |
| 125 | 124 | completeMsg *string |
| 126 | 125 | } |
| 132 | 131 | |
| 133 | 132 | var str string |
| 134 | 133 | timeElapsed := time.Since(d.startTime) |
| 135 | v := internal.Round(float64(timeElapsed) / float64(st.Current)) | |
| 134 | v := math.Round(float64(timeElapsed) / float64(st.Current)) | |
| 136 | 135 | if math.IsInf(v, 0) || math.IsNaN(v) { |
| 137 | 136 | v = 0 |
| 138 | 137 | } |
| 5 | 5 | "github.com/VividCortex/ewma" |
| 6 | 6 | ) |
| 7 | 7 | |
| 8 | // MovingAverage is the interface that computes a moving average over a time- | |
| 9 | // series stream of numbers. The average may be over a window or exponentially | |
| 10 | // decaying. | |
| 8 | // MovingAverage is the interface that computes a moving average over | |
| 9 | // a time-series stream of numbers. The average may be over a window | |
| 10 | // or exponentially decaying. | |
| 11 | 11 | type MovingAverage interface { |
| 12 | 12 | Add(float64) |
| 13 | 13 | Value() float64 |
| 56 | 56 | s.count++ |
| 57 | 57 | } |
| 58 | 58 | |
| 59 | // NewMedianEwma is ewma based MovingAverage, which gets its values from median MovingAverage. | |
| 59 | // NewMedianEwma is ewma based MovingAverage, which gets its values | |
| 60 | // from median MovingAverage. | |
| 60 | 61 | func NewMedianEwma(age ...float64) MovingAverage { |
| 61 | 62 | return &medianEwma{ |
| 62 | 63 | MovingAverage: ewma.NewMovingAverage(age...), |
| 136 | 136 | return MovingAverageSpeed(unit, unitFormat, ewma.NewMovingAverage(age), wcc...) |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | // MovingAverageSpeed decorator relies on MovingAverage implementation to calculate its average. | |
| 139 | // MovingAverageSpeed decorator relies on MovingAverage implementation | |
| 140 | // to calculate its average. | |
| 140 | 141 | // |
| 141 | 142 | // `unit` one of [0|UnitKiB|UnitKB] zero for no unit |
| 142 | 143 | // |
| 2 | 2 | import ( |
| 3 | 3 | "bytes" |
| 4 | 4 | "testing" |
| 5 | "unicode/utf8" | |
| 5 | 6 | ) |
| 6 | 7 | |
| 7 | 8 | func TestDraw(t *testing.T) { |
| 8 | 9 | // key is termWidth |
| 9 | testSuite := map[int]map[string]struct { | |
| 10 | testSuite := map[int][]struct { | |
| 11 | name string | |
| 10 | 12 | total, current int64 |
| 11 | 13 | barWidth int |
| 12 | barRefill *refill | |
| 14 | trimSpace bool | |
| 15 | rup int64 | |
| 13 | 16 | want string |
| 14 | 17 | }{ |
| 18 | 2: { | |
| 19 | { | |
| 20 | name: "t,c,bw{60,20,80}", | |
| 21 | total: 60, | |
| 22 | current: 20, | |
| 23 | barWidth: 80, | |
| 24 | want: " ", | |
| 25 | }, | |
| 26 | { | |
| 27 | name: "t,c,bw,trim{60,20,80,true}", | |
| 28 | total: 60, | |
| 29 | current: 20, | |
| 30 | barWidth: 80, | |
| 31 | trimSpace: true, | |
| 32 | want: "", | |
| 33 | }, | |
| 34 | }, | |
| 35 | 3: { | |
| 36 | { | |
| 37 | name: "t,c,bw{60,20,80}", | |
| 38 | total: 60, | |
| 39 | current: 20, | |
| 40 | barWidth: 80, | |
| 41 | want: " ", | |
| 42 | }, | |
| 43 | { | |
| 44 | name: "t,c,bw,trim{60,20,80,true}", | |
| 45 | total: 60, | |
| 46 | current: 20, | |
| 47 | barWidth: 80, | |
| 48 | trimSpace: true, | |
| 49 | want: "", | |
| 50 | }, | |
| 51 | }, | |
| 52 | 4: { | |
| 53 | { | |
| 54 | name: "t,c,bw{60,20,80}", | |
| 55 | total: 60, | |
| 56 | current: 20, | |
| 57 | barWidth: 80, | |
| 58 | want: " ", | |
| 59 | }, | |
| 60 | { | |
| 61 | name: "t,c,bw,trim{60,20,80,true}", | |
| 62 | total: 60, | |
| 63 | current: 20, | |
| 64 | barWidth: 80, | |
| 65 | trimSpace: true, | |
| 66 | want: "[]", | |
| 67 | }, | |
| 68 | }, | |
| 69 | 5: { | |
| 70 | { | |
| 71 | name: "t,c,bw{60,20,80}", | |
| 72 | total: 60, | |
| 73 | current: 20, | |
| 74 | barWidth: 80, | |
| 75 | want: " ", | |
| 76 | }, | |
| 77 | { | |
| 78 | name: "t,c,bw,trim{60,20,80,true}", | |
| 79 | total: 60, | |
| 80 | current: 20, | |
| 81 | barWidth: 80, | |
| 82 | trimSpace: true, | |
| 83 | want: "[>--]", | |
| 84 | }, | |
| 85 | }, | |
| 86 | 6: { | |
| 87 | { | |
| 88 | name: "t,c,bw{60,20,80}", | |
| 89 | total: 60, | |
| 90 | current: 20, | |
| 91 | barWidth: 80, | |
| 92 | want: " [] ", | |
| 93 | }, | |
| 94 | { | |
| 95 | name: "t,c,bw,trim{60,20,80,true}", | |
| 96 | total: 60, | |
| 97 | current: 20, | |
| 98 | barWidth: 80, | |
| 99 | trimSpace: true, | |
| 100 | want: "[>---]", | |
| 101 | }, | |
| 102 | }, | |
| 103 | 7: { | |
| 104 | { | |
| 105 | name: "t,c,bw{60,20,80}", | |
| 106 | total: 60, | |
| 107 | current: 20, | |
| 108 | barWidth: 80, | |
| 109 | want: " [>--] ", | |
| 110 | }, | |
| 111 | { | |
| 112 | name: "t,c,bw,trim{60,20,80,true}", | |
| 113 | total: 60, | |
| 114 | current: 20, | |
| 115 | barWidth: 80, | |
| 116 | trimSpace: true, | |
| 117 | want: "[=>---]", | |
| 118 | }, | |
| 119 | }, | |
| 120 | 8: { | |
| 121 | { | |
| 122 | name: "t,c,bw{60,20,80}", | |
| 123 | total: 60, | |
| 124 | current: 20, | |
| 125 | barWidth: 80, | |
| 126 | want: " [>---] ", | |
| 127 | }, | |
| 128 | { | |
| 129 | name: "t,c,bw,trim{60,20,80,true}", | |
| 130 | total: 60, | |
| 131 | current: 20, | |
| 132 | barWidth: 80, | |
| 133 | trimSpace: true, | |
| 134 | want: "[=>----]", | |
| 135 | }, | |
| 136 | }, | |
| 137 | 80: { | |
| 138 | { | |
| 139 | name: "t,c,bw{60,20,80}", | |
| 140 | total: 60, | |
| 141 | current: 20, | |
| 142 | barWidth: 80, | |
| 143 | want: " [========================>---------------------------------------------------] ", | |
| 144 | }, | |
| 145 | { | |
| 146 | name: "t,c,bw,trim{60,20,80,true}", | |
| 147 | total: 60, | |
| 148 | current: 20, | |
| 149 | barWidth: 80, | |
| 150 | trimSpace: true, | |
| 151 | want: "[=========================>----------------------------------------------------]", | |
| 152 | }, | |
| 153 | }, | |
| 15 | 154 | 100: { |
| 16 | "t,c,bw{100,100,0}": { | |
| 155 | { | |
| 156 | name: "t,c,bw{100,100,0}", | |
| 17 | 157 | total: 100, |
| 18 | 158 | current: 0, |
| 19 | 159 | barWidth: 100, |
| 20 | want: "[--------------------------------------------------------------------------------------------------]", | |
| 21 | }, | |
| 22 | "t,c,bw{100,1,100}": { | |
| 160 | want: " [------------------------------------------------------------------------------------------------] ", | |
| 161 | }, | |
| 162 | { | |
| 163 | name: "t,c,bw,trim{100,100,0,true}", | |
| 164 | total: 100, | |
| 165 | current: 0, | |
| 166 | barWidth: 100, | |
| 167 | trimSpace: true, | |
| 168 | want: "[--------------------------------------------------------------------------------------------------]", | |
| 169 | }, | |
| 170 | { | |
| 171 | name: "t,c,bw{100,1,100}", | |
| 23 | 172 | total: 100, |
| 24 | 173 | current: 1, |
| 25 | 174 | barWidth: 100, |
| 26 | want: "[>-------------------------------------------------------------------------------------------------]", | |
| 27 | }, | |
| 28 | "t,c,bw{100,40,100}": { | |
| 175 | want: " [>-----------------------------------------------------------------------------------------------] ", | |
| 176 | }, | |
| 177 | { | |
| 178 | name: "t,c,bw,trim{100,1,100,true}", | |
| 179 | total: 100, | |
| 180 | current: 1, | |
| 181 | barWidth: 100, | |
| 182 | trimSpace: true, | |
| 183 | want: "[>-------------------------------------------------------------------------------------------------]", | |
| 184 | }, | |
| 185 | { | |
| 186 | name: "t,c,bw{100,33,100}", | |
| 187 | total: 100, | |
| 188 | current: 33, | |
| 189 | barWidth: 100, | |
| 190 | want: " [===============================>----------------------------------------------------------------] ", | |
| 191 | }, | |
| 192 | { | |
| 193 | name: "t,c,bw,trim{100,33,100,true}", | |
| 194 | total: 100, | |
| 195 | current: 33, | |
| 196 | barWidth: 100, | |
| 197 | trimSpace: true, | |
| 198 | want: "[===============================>------------------------------------------------------------------]", | |
| 199 | }, | |
| 200 | { | |
| 201 | name: "t,c,bw,rup{100,33,100,33}", | |
| 202 | total: 100, | |
| 203 | current: 33, | |
| 204 | barWidth: 100, | |
| 205 | rup: 33, | |
| 206 | want: " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ", | |
| 207 | }, | |
| 208 | { | |
| 209 | name: "t,c,bw,rup,trim{100,33,100,33,true}", | |
| 210 | total: 100, | |
| 211 | current: 33, | |
| 212 | barWidth: 100, | |
| 213 | rup: 33, | |
| 214 | trimSpace: true, | |
| 215 | want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]", | |
| 216 | }, | |
| 217 | { | |
| 218 | name: "t,c,bw,rup{100,40,100,32}", | |
| 29 | 219 | total: 100, |
| 30 | 220 | current: 40, |
| 31 | 221 | barWidth: 100, |
| 32 | want: "[======================================>-----------------------------------------------------------]", | |
| 33 | }, | |
| 34 | "t,c,bw{100,40,100}refill{'+', 32}": { | |
| 222 | rup: 33, | |
| 223 | want: " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ", | |
| 224 | }, | |
| 225 | { | |
| 226 | name: "t,c,bw,rup,trim{100,40,100,32,true}", | |
| 35 | 227 | total: 100, |
| 36 | 228 | current: 40, |
| 37 | 229 | barWidth: 100, |
| 38 | barRefill: &refill{'+', 32}, | |
| 39 | want: "[+++++++++++++++++++++++++++++++=======>-----------------------------------------------------------]", | |
| 40 | }, | |
| 41 | "t,c,bw{100,99,100}": { | |
| 230 | rup: 33, | |
| 231 | trimSpace: true, | |
| 232 | want: "[++++++++++++++++++++++++++++++++======>-----------------------------------------------------------]", | |
| 233 | }, | |
| 234 | { | |
| 235 | name: "t,c,bw{100,99,100}", | |
| 42 | 236 | total: 100, |
| 43 | 237 | current: 99, |
| 44 | 238 | barWidth: 100, |
| 45 | want: "[================================================================================================>-]", | |
| 46 | }, | |
| 47 | "t,c,bw{100,100,100}": { | |
| 239 | want: " [==============================================================================================>-] ", | |
| 240 | }, | |
| 241 | { | |
| 242 | name: "t,c,bw,trim{100,99,100,true}", | |
| 243 | total: 100, | |
| 244 | current: 99, | |
| 245 | barWidth: 100, | |
| 246 | trimSpace: true, | |
| 247 | want: "[================================================================================================>-]", | |
| 248 | }, | |
| 249 | { | |
| 250 | name: "t,c,bw{100,100,100}", | |
| 48 | 251 | total: 100, |
| 49 | 252 | current: 100, |
| 50 | 253 | barWidth: 100, |
| 51 | want: "[==================================================================================================]", | |
| 52 | }, | |
| 53 | }, | |
| 54 | 2: { | |
| 55 | "t,c,bw{0,0,100}": { | |
| 56 | barWidth: 100, | |
| 57 | want: "[]", | |
| 58 | }, | |
| 59 | "t,c,bw{60,20,80}": { | |
| 60 | total: 60, | |
| 61 | current: 20, | |
| 62 | barWidth: 80, | |
| 63 | want: "[]", | |
| 64 | }, | |
| 65 | }, | |
| 66 | 3: { | |
| 67 | "t,c,bw{100,20,100}": { | |
| 68 | total: 100, | |
| 69 | current: 20, | |
| 70 | barWidth: 100, | |
| 71 | want: "[-]", | |
| 72 | }, | |
| 73 | "t,c,bw{100,98,100}": { | |
| 74 | total: 100, | |
| 75 | current: 98, | |
| 76 | barWidth: 100, | |
| 77 | want: "[=]", | |
| 78 | }, | |
| 79 | "t,c,bw{100,100,100}": { | |
| 80 | total: 100, | |
| 81 | current: 100, | |
| 82 | barWidth: 100, | |
| 83 | want: "[=]", | |
| 84 | }, | |
| 85 | }, | |
| 86 | 5: { | |
| 87 | "t,c,bw{100,20,100}": { | |
| 88 | total: 100, | |
| 89 | current: 20, | |
| 90 | barWidth: 100, | |
| 91 | want: "[>--]", | |
| 92 | }, | |
| 93 | "t,c,bw{100,98,100}": { | |
| 94 | total: 100, | |
| 95 | current: 98, | |
| 96 | barWidth: 100, | |
| 97 | want: "[===]", | |
| 98 | }, | |
| 99 | "t,c,bw{100,100,100}": { | |
| 100 | total: 100, | |
| 101 | current: 100, | |
| 102 | barWidth: 100, | |
| 103 | want: "[===]", | |
| 104 | }, | |
| 105 | }, | |
| 106 | 6: { | |
| 107 | "t,c,bw{100,20,100}": { | |
| 108 | total: 100, | |
| 109 | current: 20, | |
| 110 | barWidth: 100, | |
| 111 | want: "[>---]", | |
| 112 | }, | |
| 113 | "t,c,bw{100,98,100}": { | |
| 114 | total: 100, | |
| 115 | current: 98, | |
| 116 | barWidth: 100, | |
| 117 | want: "[====]", | |
| 118 | }, | |
| 119 | "t,c,bw{100,100,100}": { | |
| 120 | total: 100, | |
| 121 | current: 100, | |
| 122 | barWidth: 100, | |
| 123 | want: "[====]", | |
| 124 | }, | |
| 125 | }, | |
| 126 | 20: { | |
| 127 | "t,c,bw{100,20,100}": { | |
| 128 | total: 100, | |
| 129 | current: 20, | |
| 130 | barWidth: 100, | |
| 131 | want: "[===>--------------]", | |
| 132 | }, | |
| 133 | "t,c,bw{100,60,100}": { | |
| 134 | total: 100, | |
| 135 | current: 60, | |
| 136 | barWidth: 100, | |
| 137 | want: "[==========>-------]", | |
| 138 | }, | |
| 139 | "t,c,bw{100,98,100}": { | |
| 140 | total: 100, | |
| 141 | current: 98, | |
| 142 | barWidth: 100, | |
| 143 | want: "[==================]", | |
| 144 | }, | |
| 145 | "t,c,bw{100,100,100}": { | |
| 146 | total: 100, | |
| 147 | current: 100, | |
| 148 | barWidth: 100, | |
| 149 | want: "[==================]", | |
| 150 | }, | |
| 151 | }, | |
| 152 | 50: { | |
| 153 | "t,c,bw{100,20,100}": { | |
| 154 | total: 100, | |
| 155 | current: 20, | |
| 156 | barWidth: 100, | |
| 157 | want: "[=========>--------------------------------------]", | |
| 158 | }, | |
| 159 | "t,c,bw{100,60,100}": { | |
| 160 | total: 100, | |
| 161 | current: 60, | |
| 162 | barWidth: 100, | |
| 163 | want: "[============================>-------------------]", | |
| 164 | }, | |
| 165 | "t,c,bw{100,98,100}": { | |
| 166 | total: 100, | |
| 167 | current: 98, | |
| 168 | barWidth: 100, | |
| 169 | want: "[==============================================>-]", | |
| 170 | }, | |
| 171 | "t,c,bw{100,100,100}": { | |
| 172 | total: 100, | |
| 173 | current: 100, | |
| 174 | barWidth: 100, | |
| 175 | want: "[================================================]", | |
| 254 | want: " [================================================================================================] ", | |
| 255 | }, | |
| 256 | { | |
| 257 | name: "t,c,bw,trim{100,100,100,true}", | |
| 258 | total: 100, | |
| 259 | current: 100, | |
| 260 | barWidth: 100, | |
| 261 | trimSpace: true, | |
| 262 | want: "[==================================================================================================]", | |
| 176 | 263 | }, |
| 177 | 264 | }, |
| 178 | 265 | } |
| 179 | 266 | |
| 180 | 267 | var tmpBuf bytes.Buffer |
| 181 | 268 | for termWidth, cases := range testSuite { |
| 182 | for name, tc := range cases { | |
| 269 | for _, tc := range cases { | |
| 183 | 270 | s := newTestState() |
| 184 | 271 | s.width = tc.barWidth |
| 185 | 272 | s.total = tc.total |
| 186 | 273 | s.current = tc.current |
| 187 | if tc.barRefill != nil { | |
| 188 | s.refill = tc.barRefill | |
| 274 | s.trimSpace = tc.trimSpace | |
| 275 | if tc.rup > 0 { | |
| 276 | if f, ok := s.filler.(interface{ SetRefill(int64) }); ok { | |
| 277 | f.SetRefill(tc.rup) | |
| 278 | } | |
| 189 | 279 | } |
| 190 | 280 | tmpBuf.Reset() |
| 191 | 281 | tmpBuf.ReadFrom(s.draw(termWidth)) |
| 192 | got := tmpBuf.String() | |
| 193 | want := tc.want + "\n" | |
| 194 | if got != want { | |
| 195 | t.Errorf("termWidth %d; %s: want: %s %d, got: %s %d\n", termWidth, name, want, len(want), got, len(got)) | |
| 282 | by := tmpBuf.Bytes() | |
| 283 | by = by[:len(by)-1] | |
| 284 | ||
| 285 | if utf8.RuneCount(by) > termWidth { | |
| 286 | t.Errorf("termWidth:%d %q barWidth:%d overflow termWidth\n", termWidth, tc.name, utf8.RuneCount(by)) | |
| 287 | } | |
| 288 | ||
| 289 | got := string(by) | |
| 290 | if got != tc.want { | |
| 291 | t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", termWidth, tc.name, tc.want, len(tc.want), got, len(got)) | |
| 196 | 292 | } |
| 197 | 293 | } |
| 198 | 294 | } |
| 200 | 296 | |
| 201 | 297 | func newTestState() *bState { |
| 202 | 298 | s := &bState{ |
| 203 | trimLeftSpace: true, | |
| 204 | trimRightSpace: true, | |
| 205 | bufP: new(bytes.Buffer), | |
| 206 | bufB: new(bytes.Buffer), | |
| 207 | bufA: new(bytes.Buffer), | |
| 299 | filler: newDefaultBarFiller(), | |
| 300 | bufP: new(bytes.Buffer), | |
| 301 | bufB: new(bytes.Buffer), | |
| 302 | bufA: new(bytes.Buffer), | |
| 208 | 303 | } |
| 209 | s.runes = strToBarRunes(pformat) | |
| 210 | 304 | return s |
| 211 | 305 | } |
| 14 | 14 | p := mpb.New( |
| 15 | 15 | // override default (80) width |
| 16 | 16 | mpb.WithWidth(64), |
| 17 | // override default "[=>-]" format | |
| 18 | mpb.WithFormat("╢▌▌░╟"), | |
| 19 | 17 | // override default 120ms refresh rate |
| 20 | 18 | mpb.WithRefreshRate(180*time.Millisecond), |
| 21 | 19 | ) |
| 24 | 22 | name := "Single Bar:" |
| 25 | 23 | // adding a single bar |
| 26 | 24 | bar := p.AddBar(int64(total), |
| 25 | // override default "[=>-]" style | |
| 26 | mpb.BarStyle("╢▌▌░╟"), | |
| 27 | 27 | mpb.PrependDecorators( |
| 28 | 28 | // display our name with one space on the right |
| 29 | 29 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 0 | package main | |
| 1 | ||
| 2 | import ( | |
| 3 | "fmt" | |
| 4 | "math/rand" | |
| 5 | "sync" | |
| 6 | "time" | |
| 7 | ||
| 8 | "github.com/vbauerster/mpb" | |
| 9 | "github.com/vbauerster/mpb/decor" | |
| 10 | ) | |
| 11 | ||
| 12 | func init() { | |
| 13 | rand.Seed(time.Now().UnixNano()) | |
| 14 | } | |
| 15 | ||
| 16 | func main() { | |
| 17 | var wg sync.WaitGroup | |
| 18 | p := mpb.New( | |
| 19 | mpb.WithWaitGroup(&wg), | |
| 20 | // container's width. | |
| 21 | mpb.WithWidth(60), | |
| 22 | ) | |
| 23 | total, numBars := 100, 3 | |
| 24 | wg.Add(numBars) | |
| 25 | ||
| 26 | for i := 0; i < numBars; i++ { | |
| 27 | name := fmt.Sprintf("Bar#%d:", i) | |
| 28 | bar := p.AddBar(int64(total), | |
| 29 | // set BarWidth 40 for bar 1 and 2 | |
| 30 | mpb.OptionOnCondition(mpb.BarWidth(40), func() bool { return i > 0 }), | |
| 31 | mpb.PrependDecorators( | |
| 32 | // simple name decorator | |
| 33 | decor.Name(name), | |
| 34 | // decor.DSyncWidth bit enables column width synchronization | |
| 35 | decor.Percentage(decor.WCSyncSpace), | |
| 36 | ), | |
| 37 | mpb.AppendDecorators( | |
| 38 | // replace ETA decorator with "done" message, OnComplete event | |
| 39 | decor.OnComplete( | |
| 40 | // ETA decorator with ewma age of 60 | |
| 41 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 42 | ), | |
| 43 | ), | |
| 44 | ) | |
| 45 | // simulating some work | |
| 46 | go func() { | |
| 47 | defer wg.Done() | |
| 48 | max := 100 * time.Millisecond | |
| 49 | for i := 0; i < total; i++ { | |
| 50 | start := time.Now() | |
| 51 | time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) | |
| 52 | // ewma based decorators require work duration measurement | |
| 53 | bar.IncrBy(1, time.Since(start)) | |
| 54 | } | |
| 55 | }() | |
| 56 | } | |
| 57 | // wait for all bars to complete and flush | |
| 58 | p.Wait() | |
| 59 | } |
| 38 | 38 | |
| 39 | 39 | p := mpb.New( |
| 40 | 40 | mpb.WithWidth(60), |
| 41 | mpb.WithFormat("[=>-|"), | |
| 42 | 41 | mpb.WithRefreshRate(180*time.Millisecond), |
| 43 | 42 | ) |
| 44 | 43 | |
| 45 | bar := p.AddBar(size, | |
| 44 | bar := p.AddBar(size, mpb.BarStyle("[=>-|"), | |
| 46 | 45 | mpb.PrependDecorators( |
| 47 | 46 | decor.CountersKibiByte("% 6.1f / % 6.1f"), |
| 48 | 47 | ), |
| 22 | 22 | |
| 23 | 23 | for i := 0; i < numBars; i++ { |
| 24 | 24 | name := fmt.Sprintf("Bar#%d:", i) |
| 25 | ||
| 26 | var bOption mpb.BarOption | |
| 27 | if i == 0 { | |
| 28 | bOption = mpb.BarRemoveOnComplete() | |
| 29 | } | |
| 30 | ||
| 31 | 25 | b := p.AddBar(int64(total), mpb.BarID(i), |
| 32 | bOption, | |
| 26 | mpb.OptionOnCondition(mpb.BarRemoveOnComplete(), func() bool { return i == 0 }), | |
| 33 | 27 | mpb.PrependDecorators( |
| 34 | 28 | decor.Name(name), |
| 35 | 29 | decor.EwmaETA(decor.ET_STYLE_GO, 60, decor.WCSyncSpace), |
| 11 | 11 | p := mpb.New( |
| 12 | 12 | // override default (80) width |
| 13 | 13 | mpb.WithWidth(64), |
| 14 | // override default "[=>-]" format | |
| 15 | mpb.WithFormat("╢▌▌░╟"), | |
| 16 | 14 | // override default 120ms refresh rate |
| 17 | 15 | mpb.WithRefreshRate(180*time.Millisecond), |
| 18 | 16 | ) |
| 21 | 19 | name := "Single Bar:" |
| 22 | 20 | // adding a single bar |
| 23 | 21 | bar := p.AddBar(int64(total), |
| 22 | // override default "[=>-]" style | |
| 23 | mpb.BarStyle("╢▌▌░╟"), | |
| 24 | 24 | mpb.PrependDecorators( |
| 25 | 25 | // display our name with one space on the right |
| 26 | 26 | decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), |
| 0 | package main | |
| 1 | ||
| 2 | import ( | |
| 3 | "fmt" | |
| 4 | "math/rand" | |
| 5 | "sync" | |
| 6 | "time" | |
| 7 | ||
| 8 | "github.com/vbauerster/mpb" | |
| 9 | "github.com/vbauerster/mpb/decor" | |
| 10 | ) | |
| 11 | ||
| 12 | func init() { | |
| 13 | rand.Seed(time.Now().UnixNano()) | |
| 14 | } | |
| 15 | ||
| 16 | func main() { | |
| 17 | var wg sync.WaitGroup | |
| 18 | p := mpb.New( | |
| 19 | mpb.WithWaitGroup(&wg), | |
| 20 | mpb.WithWidth(13), | |
| 21 | ) | |
| 22 | total, numBars := 101, 3 | |
| 23 | wg.Add(numBars) | |
| 24 | ||
| 25 | for i := 0; i < numBars; i++ { | |
| 26 | name := fmt.Sprintf("Bar#%d:", i) | |
| 27 | var bar *mpb.Bar | |
| 28 | if i == 0 { | |
| 29 | bar = p.AddBar(int64(total), | |
| 30 | // override default "[=>-]" style | |
| 31 | mpb.BarStyle("╢▌▌░╟"), | |
| 32 | mpb.PrependDecorators( | |
| 33 | // simple name decorator | |
| 34 | decor.Name(name), | |
| 35 | ), | |
| 36 | mpb.AppendDecorators( | |
| 37 | // replace ETA decorator with "done" message, OnComplete event | |
| 38 | decor.OnComplete( | |
| 39 | // ETA decorator with ewma age of 60 | |
| 40 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 41 | ), | |
| 42 | ), | |
| 43 | ) | |
| 44 | } else { | |
| 45 | bar = p.AddSpinner(int64(total), mpb.SpinnerOnMiddle, | |
| 46 | // override default {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} style | |
| 47 | mpb.SpinnerStyle([]string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}), | |
| 48 | mpb.PrependDecorators( | |
| 49 | // simple name decorator | |
| 50 | decor.Name(name), | |
| 51 | ), | |
| 52 | mpb.AppendDecorators( | |
| 53 | // replace ETA decorator with "done" message, OnComplete event | |
| 54 | decor.OnComplete( | |
| 55 | // ETA decorator with ewma age of 60 | |
| 56 | decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", | |
| 57 | ), | |
| 58 | ), | |
| 59 | ) | |
| 60 | } | |
| 61 | ||
| 62 | // simulating some work | |
| 63 | go func() { | |
| 64 | defer wg.Done() | |
| 65 | max := 100 * time.Millisecond | |
| 66 | for i := 0; i < total; i++ { | |
| 67 | start := time.Now() | |
| 68 | time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) | |
| 69 | // ewma based decorators require work duration measurement | |
| 70 | bar.IncrBy(1, time.Since(start)) | |
| 71 | } | |
| 72 | }() | |
| 73 | } | |
| 74 | // wait for all bars to complete and flush | |
| 75 | p.Wait() | |
| 76 | } |
| 19 | 19 | |
| 20 | 20 | func main() { |
| 21 | 21 | var wg sync.WaitGroup |
| 22 | p := mpb.New(mpb.WithWaitGroup(&wg)) | |
| 22 | p := mpb.New( | |
| 23 | mpb.WithWaitGroup(&wg), | |
| 24 | mpb.WithRefreshRate(50*time.Millisecond), | |
| 25 | ) | |
| 23 | 26 | wg.Add(totalBars) |
| 24 | 27 | |
| 25 | 28 | for i := 0; i < totalBars; i++ { |
| 0 | 0 | package mpb |
| 1 | 1 | |
| 2 | var SyncWidth = syncWidth | |
| 2 | var ( | |
| 3 | SyncWidth = syncWidth | |
| 4 | DefaultBarStyle = defaultBarStyle | |
| 5 | ) |
| 0 | 0 | package internal |
| 1 | ||
| 2 | import "math" | |
| 1 | 3 | |
| 2 | 4 | // Percentage is a helper function, to calculate percentage. |
| 3 | 5 | func Percentage(total, current, width int64) int64 { |
| 5 | 7 | return 0 |
| 6 | 8 | } |
| 7 | 9 | p := float64(width*current) / float64(total) |
| 8 | return int64(Round(p)) | |
| 10 | return int64(math.Round(p)) | |
| 9 | 11 | } |
| 0 | 0 | package internal |
| 1 | 1 | |
| 2 | import ( | |
| 3 | "testing" | |
| 4 | ) | |
| 2 | import "testing" | |
| 5 | 3 | |
| 6 | 4 | func TestPercentage(t *testing.T) { |
| 7 | 5 | // key is barWidth |
| 8 | testSuite := map[int64]map[string]struct { | |
| 6 | testSuite := map[int64][]struct { | |
| 7 | name string | |
| 9 | 8 | total, current, expected int64 |
| 10 | 9 | }{ |
| 11 | 10 | 100: { |
| 12 | "t,c,e{-1,-1,0}": {-1, -1, 0}, | |
| 13 | "t,c,e{0,-1,0}": {0, -1, 0}, | |
| 14 | "t,c,e{0,0,0}": {0, 0, 0}, | |
| 15 | "t,c,e{0,1,0}": {0, 1, 0}, | |
| 16 | "t,c,e{100,0,0}": {100, 0, 0}, | |
| 17 | "t,c,e{100,10,10}": {100, 10, 10}, | |
| 18 | "t,c,e{100,15,15}": {100, 15, 15}, | |
| 19 | "t,c,e{100,50,50}": {100, 50, 50}, | |
| 20 | "t,c,e{100,99,99}": {100, 99, 99}, | |
| 21 | "t,c,e{100,100,100}": {100, 100, 100}, | |
| 22 | "t,c,e{100,101,101}": {100, 101, 101}, | |
| 23 | "t,c,e{100,102,101}": {100, 102, 102}, | |
| 24 | "t,c,e{120,0,0}": {120, 0, 0}, | |
| 25 | "t,c,e{120,10,8}": {120, 10, 8}, | |
| 26 | "t,c,e{120,15,13}": {120, 15, 13}, | |
| 27 | "t,c,e{120,50,42}": {120, 50, 42}, | |
| 28 | "t,c,e{120,60,50}": {120, 60, 50}, | |
| 29 | "t,c,e{120,99,83}": {120, 99, 83}, | |
| 30 | "t,c,e{120,101,84}": {120, 101, 84}, | |
| 31 | "t,c,e{120,118,98}": {120, 118, 98}, | |
| 32 | "t,c,e{120,119,99}": {120, 119, 99}, | |
| 33 | "t,c,e{120,120,100}": {120, 120, 100}, | |
| 34 | "t,c,e{120,121,101}": {120, 121, 101}, | |
| 35 | "t,c,e{120,122,101}": {120, 122, 102}, | |
| 11 | {"t,c,e{-1,-1,0}", -1, -1, 0}, | |
| 12 | {"t,c,e{0,-1,0}", 0, -1, 0}, | |
| 13 | {"t,c,e{0,0,0}", 0, 0, 0}, | |
| 14 | {"t,c,e{0,1,0}", 0, 1, 0}, | |
| 15 | {"t,c,e{100,0,0}", 100, 0, 0}, | |
| 16 | {"t,c,e{100,10,10}", 100, 10, 10}, | |
| 17 | {"t,c,e{100,15,15}", 100, 15, 15}, | |
| 18 | {"t,c,e{100,50,50}", 100, 50, 50}, | |
| 19 | {"t,c,e{100,99,99}", 100, 99, 99}, | |
| 20 | {"t,c,e{100,100,100}", 100, 100, 100}, | |
| 21 | {"t,c,e{100,101,101}", 100, 101, 101}, | |
| 22 | {"t,c,e{100,102,101}", 100, 102, 102}, | |
| 23 | {"t,c,e{120,0,0}", 120, 0, 0}, | |
| 24 | {"t,c,e{120,10,8}", 120, 10, 8}, | |
| 25 | {"t,c,e{120,15,13}", 120, 15, 13}, | |
| 26 | {"t,c,e{120,50,42}", 120, 50, 42}, | |
| 27 | {"t,c,e{120,60,50}", 120, 60, 50}, | |
| 28 | {"t,c,e{120,99,83}", 120, 99, 83}, | |
| 29 | {"t,c,e{120,101,84}", 120, 101, 84}, | |
| 30 | {"t,c,e{120,118,98}", 120, 118, 98}, | |
| 31 | {"t,c,e{120,119,99}", 120, 119, 99}, | |
| 32 | {"t,c,e{120,120,100}", 120, 120, 100}, | |
| 33 | {"t,c,e{120,121,101}", 120, 121, 101}, | |
| 34 | {"t,c,e{120,122,101}", 120, 122, 102}, | |
| 36 | 35 | }, |
| 37 | 36 | 80: { |
| 38 | "t,c,e{-1,-1,0}": {-1, -1, 0}, | |
| 39 | "t,c,e{0,-1,0}": {0, -1, 0}, | |
| 40 | "t,c,e{0,0,0}": {0, 0, 0}, | |
| 41 | "t,c,e{0,1,0}": {0, 1, 0}, | |
| 42 | "t,c,e{100,0,0}": {100, 0, 0}, | |
| 43 | "t,c,e{100,10,8}": {100, 10, 8}, | |
| 44 | "t,c,e{100,15,12}": {100, 15, 12}, | |
| 45 | "t,c,e{100,50,40}": {100, 50, 40}, | |
| 46 | "t,c,e{100,99,79}": {100, 99, 79}, | |
| 47 | "t,c,e{100,100,80}": {100, 100, 80}, | |
| 48 | "t,c,e{100,101,81}": {100, 101, 81}, | |
| 49 | "t,c,e{100,102,82}": {100, 102, 82}, | |
| 50 | "t,c,e{120,0,0}": {120, 0, 0}, | |
| 51 | "t,c,e{120,10,7}": {120, 10, 7}, | |
| 52 | "t,c,e{120,15,10}": {120, 15, 10}, | |
| 53 | "t,c,e{120,50,33}": {120, 50, 33}, | |
| 54 | "t,c,e{120,60,40}": {120, 60, 40}, | |
| 55 | "t,c,e{120,99,66}": {120, 99, 66}, | |
| 56 | "t,c,e{120,101,67}": {120, 101, 67}, | |
| 57 | "t,c,e{120,118,79}": {120, 118, 79}, | |
| 58 | "t,c,e{120,119,79}": {120, 119, 79}, | |
| 59 | "t,c,e{120,120,80}": {120, 120, 80}, | |
| 60 | "t,c,e{120,121,81}": {120, 121, 81}, | |
| 61 | "t,c,e{120,122,81}": {120, 122, 81}, | |
| 37 | {"t,c,e{-1,-1,0}", -1, -1, 0}, | |
| 38 | {"t,c,e{0,-1,0}", 0, -1, 0}, | |
| 39 | {"t,c,e{0,0,0}", 0, 0, 0}, | |
| 40 | {"t,c,e{0,1,0}", 0, 1, 0}, | |
| 41 | {"t,c,e{100,0,0}", 100, 0, 0}, | |
| 42 | {"t,c,e{100,10,8}", 100, 10, 8}, | |
| 43 | {"t,c,e{100,15,12}", 100, 15, 12}, | |
| 44 | {"t,c,e{100,50,40}", 100, 50, 40}, | |
| 45 | {"t,c,e{100,99,79}", 100, 99, 79}, | |
| 46 | {"t,c,e{100,100,80}", 100, 100, 80}, | |
| 47 | {"t,c,e{100,101,81}", 100, 101, 81}, | |
| 48 | {"t,c,e{100,102,82}", 100, 102, 82}, | |
| 49 | {"t,c,e{120,0,0}", 120, 0, 0}, | |
| 50 | {"t,c,e{120,10,7}", 120, 10, 7}, | |
| 51 | {"t,c,e{120,15,10}", 120, 15, 10}, | |
| 52 | {"t,c,e{120,50,33}", 120, 50, 33}, | |
| 53 | {"t,c,e{120,60,40}", 120, 60, 40}, | |
| 54 | {"t,c,e{120,99,66}", 120, 99, 66}, | |
| 55 | {"t,c,e{120,101,67}", 120, 101, 67}, | |
| 56 | {"t,c,e{120,118,79}", 120, 118, 79}, | |
| 57 | {"t,c,e{120,119,79}", 120, 119, 79}, | |
| 58 | {"t,c,e{120,120,80}", 120, 120, 80}, | |
| 59 | {"t,c,e{120,121,81}", 120, 121, 81}, | |
| 60 | {"t,c,e{120,122,81}", 120, 122, 81}, | |
| 62 | 61 | }, |
| 63 | 62 | } |
| 64 | 63 | |
| 65 | 64 | for width, cases := range testSuite { |
| 66 | for name, tc := range cases { | |
| 65 | for _, tc := range cases { | |
| 67 | 66 | got := Percentage(tc.total, tc.current, width) |
| 68 | 67 | if got != tc.expected { |
| 69 | t.Errorf("width %d; %s: Expected: %d, got: %d\n", width, name, tc.expected, got) | |
| 68 | t.Errorf("width %d; %s: Expected: %d, got: %d\n", width, tc.name, tc.expected, got) | |
| 70 | 69 | } |
| 71 | 70 | } |
| 72 | 71 | } |
| 0 | package internal | |
| 1 | ||
| 2 | import "math" | |
| 3 | ||
| 4 | const ( | |
| 5 | uvone = 0x3FF0000000000000 | |
| 6 | mask = 0x7FF | |
| 7 | shift = 64 - 11 - 1 | |
| 8 | bias = 1023 | |
| 9 | signMask = 1 << 63 | |
| 10 | fracMask = 1<<shift - 1 | |
| 11 | ) | |
| 12 | ||
| 13 | // Round returns the nearest integer, rounding half away from zero. | |
| 14 | // | |
| 15 | // Special cases are: | |
| 16 | // Round(±0) = ±0 | |
| 17 | // Round(±Inf) = ±Inf | |
| 18 | // Round(NaN) = NaN | |
| 19 | func Round(x float64) float64 { | |
| 20 | // Round is a faster implementation of: | |
| 21 | // | |
| 22 | // func Round(x float64) float64 { | |
| 23 | // t := Trunc(x) | |
| 24 | // if Abs(x-t) >= 0.5 { | |
| 25 | // return t + Copysign(1, x) | |
| 26 | // } | |
| 27 | // return t | |
| 28 | // } | |
| 29 | bits := math.Float64bits(x) | |
| 30 | e := uint(bits>>shift) & mask | |
| 31 | if e < bias { | |
| 32 | // Round abs(x) < 1 including denormals. | |
| 33 | bits &= signMask // +-0 | |
| 34 | if e == bias-1 { | |
| 35 | bits |= uvone // +-1 | |
| 36 | } | |
| 37 | } else if e < bias+shift { | |
| 38 | // Round any abs(x) >= 1 containing a fractional component [0,1). | |
| 39 | // | |
| 40 | // Numbers with larger exponents are returned unchanged since they | |
| 41 | // must be either an integer, infinity, or NaN. | |
| 42 | const half = 1 << (shift - 1) | |
| 43 | e -= bias | |
| 44 | bits += half >> e | |
| 45 | bits &^= fracMask >> e | |
| 46 | } | |
| 47 | return math.Float64frombits(bits) | |
| 48 | } |
| 0 | 0 | package mpb |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | "context" | |
| 3 | 4 | "io" |
| 4 | 5 | "sync" |
| 5 | 6 | "time" |
| 6 | "unicode/utf8" | |
| 7 | 7 | |
| 8 | 8 | "github.com/vbauerster/mpb/cwriter" |
| 9 | 9 | ) |
| 10 | 10 | |
| 11 | // ProgressOption is a function option which changes the default behavior of | |
| 12 | // progress pool, if passed to mpb.New(...ProgressOption) | |
| 11 | // ProgressOption is a function option which changes the default | |
| 12 | // behavior of progress pool, if passed to mpb.New(...ProgressOption). | |
| 13 | 13 | type ProgressOption func(*pState) |
| 14 | 14 | |
| 15 | // WithWaitGroup provides means to have a single joint point. | |
| 16 | // If *sync.WaitGroup is provided, you can safely call just p.Wait() | |
| 17 | // without calling Wait() on provided *sync.WaitGroup. | |
| 18 | // Makes sense when there are more than one bar to render. | |
| 15 | // WithWaitGroup provides means to have a single joint point. If | |
| 16 | // *sync.WaitGroup is provided, you can safely call just p.Wait() | |
| 17 | // without calling Wait() on provided *sync.WaitGroup. Makes sense | |
| 18 | // when there are more than one bar to render. | |
| 19 | 19 | func WithWaitGroup(wg *sync.WaitGroup) ProgressOption { |
| 20 | 20 | return func(s *pState) { |
| 21 | 21 | s.uwg = wg |
| 22 | 22 | } |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | // WithWidth overrides default width 80 | |
| 25 | // WithWidth sets container width. Default is 80. Bars inherit this | |
| 26 | // width, as long as no BarWidth is applied. | |
| 26 | 27 | func WithWidth(w int) ProgressOption { |
| 27 | 28 | return func(s *pState) { |
| 28 | 29 | if w >= 0 { |
| 31 | 32 | } |
| 32 | 33 | } |
| 33 | 34 | |
| 34 | // WithFormat overrides default bar format "[=>-]" | |
| 35 | func WithFormat(format string) ProgressOption { | |
| 36 | return func(s *pState) { | |
| 37 | if utf8.RuneCountInString(format) == formatLen { | |
| 38 | s.format = format | |
| 39 | } | |
| 40 | } | |
| 41 | } | |
| 42 | ||
| 43 | // WithRefreshRate overrides default 120ms refresh rate | |
| 35 | // WithRefreshRate overrides default 120ms refresh rate. | |
| 44 | 36 | func WithRefreshRate(d time.Duration) ProgressOption { |
| 45 | 37 | return func(s *pState) { |
| 46 | 38 | if d < 10*time.Millisecond { |
| 58 | 50 | } |
| 59 | 51 | } |
| 60 | 52 | |
| 61 | // WithCancel provide your cancel channel, | |
| 62 | // which you plan to close at some point. | |
| 63 | func WithCancel(ch <-chan struct{}) ProgressOption { | |
| 53 | // WithContext provided context will be used for cancellation purposes. | |
| 54 | func WithContext(ctx context.Context) ProgressOption { | |
| 64 | 55 | return func(s *pState) { |
| 65 | s.cancel = ch | |
| 56 | if ctx == nil { | |
| 57 | return | |
| 58 | } | |
| 59 | s.ctx = ctx | |
| 66 | 60 | } |
| 67 | 61 | } |
| 68 | 62 | |
| 69 | // WithShutdownNotifier provided chanel will be closed, after all bars have been rendered. | |
| 63 | // WithShutdownNotifier provided chanel will be closed, after all bars | |
| 64 | // have been rendered. | |
| 70 | 65 | func WithShutdownNotifier(ch chan struct{}) ProgressOption { |
| 71 | 66 | return func(s *pState) { |
| 72 | 67 | s.shutdownNotifier = ch |
| 73 | 68 | } |
| 74 | 69 | } |
| 75 | 70 | |
| 76 | // WithOutput overrides default output os.Stdout | |
| 71 | // WithOutput overrides default output os.Stdout. | |
| 77 | 72 | func WithOutput(w io.Writer) ProgressOption { |
| 78 | 73 | return func(s *pState) { |
| 79 | 74 | if w == nil { |
| 0 | //+build go1.7 | |
| 1 | ||
| 2 | package mpb | |
| 3 | ||
| 4 | import "context" | |
| 5 | ||
| 6 | // WithContext provided context will be used for cancellation purposes | |
| 7 | func WithContext(ctx context.Context) ProgressOption { | |
| 8 | return func(s *pState) { | |
| 9 | if ctx == nil { | |
| 10 | panic("ctx must not be nil") | |
| 11 | } | |
| 12 | s.cancel = ctx.Done() | |
| 13 | } | |
| 14 | } |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "container/heap" |
| 4 | "context" | |
| 4 | 5 | "fmt" |
| 5 | 6 | "io" |
| 6 | 7 | "io/ioutil" |
| 16 | 17 | prr = 120 * time.Millisecond |
| 17 | 18 | // default width |
| 18 | 19 | pwidth = 80 |
| 19 | // default format | |
| 20 | pformat = "[=>-]" | |
| 21 | 20 | ) |
| 22 | 21 | |
| 23 | 22 | // Progress represents the container that renders Progress bars |
| 41 | 40 | pMatrix map[int][]chan int |
| 42 | 41 | aMatrix map[int][]chan int |
| 43 | 42 | |
| 44 | // following are provided by user | |
| 43 | // following are provided/overrided by user | |
| 44 | ctx context.Context | |
| 45 | 45 | uwg *sync.WaitGroup |
| 46 | 46 | manualRefreshCh <-chan time.Time |
| 47 | cancel <-chan struct{} | |
| 48 | 47 | shutdownNotifier chan struct{} |
| 49 | 48 | waitBars map[*Bar]*Bar |
| 50 | 49 | debugOut io.Writer |
| 51 | 50 | } |
| 52 | 51 | |
| 53 | // New creates new Progress instance, which orchestrates bars rendering process. | |
| 54 | // Accepts mpb.ProgressOption funcs for customization. | |
| 52 | // New creates new Progress instance, which orchestrates bars rendering | |
| 53 | // process. Accepts mpb.ProgressOption funcs for customization. | |
| 55 | 54 | func New(options ...ProgressOption) *Progress { |
| 56 | 55 | pq := make(priorityQueue, 0) |
| 57 | 56 | heap.Init(&pq) |
| 58 | 57 | s := &pState{ |
| 58 | ctx: context.Background(), | |
| 59 | 59 | bHeap: &pq, |
| 60 | 60 | width: pwidth, |
| 61 | format: pformat, | |
| 62 | 61 | cw: cwriter.New(os.Stdout), |
| 63 | 62 | rr: prr, |
| 64 | 63 | waitBars: make(map[*Bar]*Bar), |
| 83 | 82 | |
| 84 | 83 | // AddBar creates a new progress bar and adds to the container. |
| 85 | 84 | func (p *Progress) AddBar(total int64, options ...BarOption) *Bar { |
| 85 | return p.Add(total, newDefaultBarFiller(), options...) | |
| 86 | } | |
| 87 | ||
| 88 | // AddSpinner creates a new spinner bar and adds to the container. | |
| 89 | func (p *Progress) AddSpinner(total int64, alignment SpinnerAlignment, options ...BarOption) *Bar { | |
| 90 | filler := &spinnerFiller{ | |
| 91 | frames: defaultSpinnerStyle, | |
| 92 | alignment: alignment, | |
| 93 | } | |
| 94 | return p.Add(total, filler, options...) | |
| 95 | } | |
| 96 | ||
| 97 | // Add creates a bar which renders itself by provided filler. | |
| 98 | func (p *Progress) Add(total int64, filler Filler, options ...BarOption) *Bar { | |
| 99 | if filler == nil { | |
| 100 | filler = newDefaultBarFiller() | |
| 101 | } | |
| 86 | 102 | p.wg.Add(1) |
| 87 | 103 | result := make(chan *Bar) |
| 88 | 104 | select { |
| 89 | 105 | case p.operateState <- func(s *pState) { |
| 90 | options = append(options, barWidth(s.width), barFormat(s.format)) | |
| 91 | b := newBar(p.wg, s.idCounter, total, s.cancel, options...) | |
| 106 | b := newBar(s.ctx, p.wg, filler, s.idCounter, s.width, total, options...) | |
| 92 | 107 | if b.runningBar != nil { |
| 93 | 108 | s.waitBars[b.runningBar] = b |
| 94 | 109 | } else { |
| 105 | 120 | } |
| 106 | 121 | } |
| 107 | 122 | |
| 108 | // Abort is only effective while bar progress is running, | |
| 109 | // it means remove bar now without waiting for its completion. | |
| 110 | // If bar is already completed, there is nothing to abort. | |
| 111 | // If you need to remove bar after completion, use BarRemoveOnComplete BarOption. | |
| 123 | // Abort is only effective while bar progress is running, it means | |
| 124 | // remove bar now without waiting for its completion. If bar is already | |
| 125 | // completed, there is nothing to abort. If you need to remove bar | |
| 126 | // after completion, use BarRemoveOnComplete BarOption. | |
| 112 | 127 | func (p *Progress) Abort(b *Bar, remove bool) { |
| 113 | 128 | select { |
| 114 | 129 | case p.operateState <- func(s *pState) { |
| 144 | 159 | } |
| 145 | 160 | } |
| 146 | 161 | |
| 147 | // Wait first waits for user provided *sync.WaitGroup, if any, | |
| 148 | // then waits far all bars to complete and finally shutdowns master goroutine. | |
| 149 | // After this method has been called, there is no way to reuse *Progress instance. | |
| 162 | // Wait first waits for user provided *sync.WaitGroup, if any, then | |
| 163 | // waits far all bars to complete and finally shutdowns master goroutine. | |
| 164 | // After this method has been called, there is no way to reuse *Progress | |
| 165 | // instance. | |
| 150 | 166 | func (p *Progress) Wait() { |
| 151 | 167 | if p.uwg != nil { |
| 152 | 168 | p.uwg.Wait() |
| 204 | 220 | defer func() { |
| 205 | 221 | if frameReader.toShutdown { |
| 206 | 222 | // shutdown at next flush, in other words decrement underlying WaitGroup |
| 207 | // only after the bar with completed state has been flushed. | |
| 208 | // this ensures no bar ends up with less than 100% rendered. | |
| 223 | // only after the bar with completed state has been flushed. this | |
| 224 | // ensures no bar ends up with less than 100% rendered. | |
| 209 | 225 | s.shutdownPending = append(s.shutdownPending, bar) |
| 210 | 226 | if replacementBar, ok := s.waitBars[bar]; ok { |
| 211 | 227 | heap.Push(s.bHeap, replacementBar) |
| 0 | //+build go1.7 | |
| 1 | ||
| 2 | package mpb_test | |
| 3 | ||
| 4 | import ( | |
| 5 | "context" | |
| 6 | "io/ioutil" | |
| 7 | "testing" | |
| 8 | "time" | |
| 9 | ||
| 10 | "github.com/vbauerster/mpb" | |
| 11 | ) | |
| 12 | ||
| 13 | func TestWithContext(t *testing.T) { | |
| 14 | ctx, cancel := context.WithCancel(context.Background()) | |
| 15 | shutdown := make(chan struct{}) | |
| 16 | p := mpb.New( | |
| 17 | mpb.WithOutput(ioutil.Discard), | |
| 18 | mpb.WithContext(ctx), | |
| 19 | mpb.WithShutdownNotifier(shutdown), | |
| 20 | ) | |
| 21 | ||
| 22 | total := 1000 | |
| 23 | numBars := 3 | |
| 24 | bars := make([]*mpb.Bar, 0, numBars) | |
| 25 | for i := 0; i < numBars; i++ { | |
| 26 | bar := p.AddBar(int64(total)) | |
| 27 | bars = append(bars, bar) | |
| 28 | go func() { | |
| 29 | for !bar.Completed() { | |
| 30 | time.Sleep(randomDuration(40 * time.Millisecond)) | |
| 31 | bar.Increment() | |
| 32 | } | |
| 33 | }() | |
| 34 | } | |
| 35 | ||
| 36 | time.AfterFunc(100*time.Millisecond, cancel) | |
| 37 | ||
| 38 | p.Wait() | |
| 39 | for _, bar := range bars { | |
| 40 | if bar.Current() >= int64(total) { | |
| 41 | t.Errorf("bar %d: total = %d, current = %d\n", bar.ID(), total, bar.Current()) | |
| 42 | } | |
| 43 | } | |
| 44 | select { | |
| 45 | case <-shutdown: | |
| 46 | case <-time.After(100 * time.Millisecond): | |
| 47 | t.Error("Progress didn't stop") | |
| 48 | } | |
| 49 | } |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "bytes" |
| 4 | "context" | |
| 4 | 5 | "fmt" |
| 5 | 6 | "io/ioutil" |
| 6 | 7 | "math/rand" |
| 8 | 9 | "testing" |
| 9 | 10 | "time" |
| 10 | 11 | |
| 12 | "github.com/vbauerster/mpb" | |
| 11 | 13 | . "github.com/vbauerster/mpb" |
| 12 | 14 | "github.com/vbauerster/mpb/cwriter" |
| 13 | 15 | ) |
| 79 | 81 | p.Wait() |
| 80 | 82 | } |
| 81 | 83 | |
| 82 | func TestWithCancel(t *testing.T) { | |
| 83 | cancel := make(chan struct{}) | |
| 84 | func TestWithContext(t *testing.T) { | |
| 85 | ctx, cancel := context.WithCancel(context.Background()) | |
| 84 | 86 | shutdown := make(chan struct{}) |
| 85 | p := New( | |
| 86 | WithOutput(ioutil.Discard), | |
| 87 | WithCancel(cancel), | |
| 88 | WithShutdownNotifier(shutdown), | |
| 87 | p := mpb.New( | |
| 88 | mpb.WithOutput(ioutil.Discard), | |
| 89 | mpb.WithContext(ctx), | |
| 90 | mpb.WithRefreshRate(50*time.Millisecond), | |
| 91 | mpb.WithShutdownNotifier(shutdown), | |
| 89 | 92 | ) |
| 90 | 93 | |
| 91 | for i := 0; i < 2; i++ { | |
| 92 | bar := p.AddBar(int64(1000), BarID(i)) | |
| 94 | total := 10000 | |
| 95 | numBars := 3 | |
| 96 | bars := make([]*mpb.Bar, 0, numBars) | |
| 97 | for i := 0; i < numBars; i++ { | |
| 98 | bar := p.AddBar(int64(total)) | |
| 99 | bars = append(bars, bar) | |
| 93 | 100 | go func() { |
| 94 | 101 | for !bar.Completed() { |
| 102 | bar.Increment() | |
| 95 | 103 | time.Sleep(randomDuration(100 * time.Millisecond)) |
| 96 | bar.Increment() | |
| 97 | 104 | } |
| 98 | 105 | }() |
| 99 | 106 | } |
| 100 | 107 | |
| 101 | time.AfterFunc(100*time.Millisecond, func() { | |
| 102 | close(cancel) | |
| 103 | }) | |
| 108 | time.Sleep(50 * time.Millisecond) | |
| 109 | cancel() | |
| 104 | 110 | |
| 105 | 111 | p.Wait() |
| 106 | ||
| 107 | 112 | select { |
| 108 | 113 | case <-shutdown: |
| 109 | case <-time.After(200 * time.Millisecond): | |
| 110 | t.FailNow() | |
| 111 | } | |
| 112 | } | |
| 113 | ||
| 114 | func TestWithFormat(t *testing.T) { | |
| 115 | var buf bytes.Buffer | |
| 116 | customFormat := "╢▌▌░╟" | |
| 117 | p := New(WithOutput(&buf), WithFormat(customFormat)) | |
| 118 | bar := p.AddBar(100, BarTrim()) | |
| 119 | ||
| 120 | for i := 0; i < 100; i++ { | |
| 121 | if i == 33 { | |
| 122 | p.Abort(bar, true) | |
| 123 | break | |
| 124 | } | |
| 125 | time.Sleep(randomDuration(100 * time.Millisecond)) | |
| 126 | bar.Increment() | |
| 127 | } | |
| 128 | ||
| 129 | p.Wait() | |
| 130 | ||
| 131 | lastLine := getLastLine(buf.Bytes()) | |
| 132 | ||
| 133 | for _, r := range customFormat { | |
| 134 | if !bytes.ContainsRune(lastLine, r) { | |
| 135 | t.Errorf("Rune %#U not found in bar\n", r) | |
| 136 | } | |
| 114 | case <-time.After(100 * time.Millisecond): | |
| 115 | t.Error("Progress didn't stop") | |
| 137 | 116 | } |
| 138 | 117 | } |
| 139 | 118 | |
| 0 | 0 | package mpb_test |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | "bytes" | |
| 4 | 3 | "io" |
| 5 | 4 | "io/ioutil" |
| 6 | 5 | "strings" |
| 17 | 16 | cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id |
| 18 | 17 | est laborum.` |
| 19 | 18 | |
| 19 | type testReader struct { | |
| 20 | io.Reader | |
| 21 | called bool | |
| 22 | } | |
| 23 | ||
| 24 | func (r *testReader) Read(p []byte) (n int, err error) { | |
| 25 | r.called = true | |
| 26 | return r.Reader.Read(p) | |
| 27 | } | |
| 28 | ||
| 20 | 29 | func TestProxyReader(t *testing.T) { |
| 21 | var buf bytes.Buffer | |
| 22 | p := mpb.New(mpb.WithOutput(&buf)) | |
| 23 | 30 | |
| 24 | reader := strings.NewReader(content) | |
| 31 | p := mpb.New(mpb.WithOutput(ioutil.Discard)) | |
| 32 | ||
| 33 | reader := &testReader{Reader: strings.NewReader(content)} | |
| 25 | 34 | |
| 26 | 35 | total := len(content) |
| 27 | bar := p.AddBar(100, mpb.BarTrim()) | |
| 28 | preader := bar.ProxyReader(reader) | |
| 36 | bar := p.AddBar(100, mpb.TrimSpace()) | |
| 29 | 37 | |
| 30 | if _, ok := preader.(io.Closer); !ok { | |
| 31 | t.Error("type assertion to io.Closer is not ok") | |
| 32 | } | |
| 33 | ||
| 34 | written, err := io.Copy(ioutil.Discard, preader) | |
| 38 | written, err := io.Copy(ioutil.Discard, bar.ProxyReader(reader)) | |
| 35 | 39 | if err != nil { |
| 36 | 40 | t.Errorf("Error copying from reader: %+v\n", err) |
| 37 | 41 | } |
| 38 | 42 | |
| 39 | 43 | p.Wait() |
| 40 | 44 | |
| 45 | if !reader.called { | |
| 46 | t.Error("Read not called") | |
| 47 | } | |
| 48 | ||
| 41 | 49 | if written != int64(total) { |
| 42 | 50 | t.Errorf("Expected written: %d, got: %d\n", total, written) |
| 43 | 51 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "strings" | |
| 5 | "unicode/utf8" | |
| 6 | ||
| 7 | "github.com/vbauerster/mpb/decor" | |
| 8 | ) | |
| 9 | ||
| 10 | // SpinnerAlignment enum. | |
| 11 | type SpinnerAlignment int | |
| 12 | ||
| 13 | // SpinnerAlignment kinds. | |
| 14 | const ( | |
| 15 | SpinnerOnLeft SpinnerAlignment = iota | |
| 16 | SpinnerOnMiddle | |
| 17 | SpinnerOnRight | |
| 18 | ) | |
| 19 | ||
| 20 | var defaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 21 | ||
| 22 | type spinnerFiller struct { | |
| 23 | frames []string | |
| 24 | count uint | |
| 25 | alignment SpinnerAlignment | |
| 26 | } | |
| 27 | ||
| 28 | func (s *spinnerFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 29 | ||
| 30 | frame := s.frames[s.count%uint(len(s.frames))] | |
| 31 | frameWidth := utf8.RuneCountInString(frame) | |
| 32 | ||
| 33 | if width < frameWidth { | |
| 34 | return | |
| 35 | } | |
| 36 | ||
| 37 | switch rest := width - frameWidth; s.alignment { | |
| 38 | case SpinnerOnLeft: | |
| 39 | io.WriteString(w, frame+strings.Repeat(" ", rest)) | |
| 40 | case SpinnerOnMiddle: | |
| 41 | str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) | |
| 42 | io.WriteString(w, str) | |
| 43 | case SpinnerOnRight: | |
| 44 | io.WriteString(w, strings.Repeat(" ", rest)+frame) | |
| 45 | } | |
| 46 | s.count++ | |
| 47 | } |