add OccupiedWidth and TermWidth to decor.Statistics
Vladimir Bauer
6 years ago
| 9 | 9 | "time" |
| 10 | 10 | "unicode/utf8" |
| 11 | 11 | |
| 12 | "github.com/acarl005/stripansi" | |
| 12 | 13 | "github.com/vbauerster/mpb/v5/decor" |
| 13 | 14 | ) |
| 14 | ||
| 15 | // BarFiller interface. | |
| 16 | // Bar renders itself by calling BarFiller's Fill method. You can | |
| 17 | // literally have any bar kind, by implementing this interface and | |
| 18 | // passing it to the *Progress.Add(...) *Bar method. | |
| 19 | type BarFiller interface { | |
| 20 | Fill(w io.Writer, width int, stat *decor.Statistics) | |
| 21 | } | |
| 22 | ||
| 23 | // BarFillerFunc is function type adapter to convert function into Filler. | |
| 24 | type BarFillerFunc func(w io.Writer, width int, stat *decor.Statistics) | |
| 25 | ||
| 26 | func (f BarFillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 27 | f(w, width, stat) | |
| 28 | } | |
| 29 | 15 | |
| 30 | 16 | // Bar represents a progress Bar. |
| 31 | 17 | type Bar struct { |
| 54 | 40 | recoveredPanic interface{} |
| 55 | 41 | } |
| 56 | 42 | |
| 57 | type extFunc func(in io.Reader, tw int, st *decor.Statistics) (out io.Reader, lines int) | |
| 43 | type extFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int) | |
| 58 | 44 | |
| 59 | 45 | type bState struct { |
| 60 | 46 | baseF BarFiller |
| 334 | 320 | } |
| 335 | 321 | }() |
| 336 | 322 | |
| 337 | st := newStatistics(s) | |
| 338 | frame := s.draw(tw, st) | |
| 339 | frame, b.extendedLines = s.extender(frame, tw, st) | |
| 323 | st := newStatistics(tw, s) | |
| 324 | frame, lines := s.extender(s.draw(st), s.width, st) | |
| 325 | b.extendedLines = lines | |
| 340 | 326 | |
| 341 | 327 | b.toShutdown = s.toComplete && !s.completeFlushed |
| 342 | 328 | s.completeFlushed = s.toComplete |
| 344 | 330 | }: |
| 345 | 331 | case <-b.done: |
| 346 | 332 | s := b.cacheState |
| 347 | st := newStatistics(s) | |
| 348 | frame := s.draw(tw, st) | |
| 349 | frame, b.extendedLines = s.extender(frame, tw, st) | |
| 333 | st := newStatistics(tw, s) | |
| 334 | frame, lines := s.extender(s.draw(st), s.width, st) | |
| 335 | b.extendedLines = lines | |
| 350 | 336 | b.frameCh <- frame |
| 351 | 337 | } |
| 352 | 338 | } |
| 397 | 383 | } |
| 398 | 384 | } |
| 399 | 385 | |
| 400 | func (s *bState) draw(termWidth int, stat *decor.Statistics) io.Reader { | |
| 386 | func (s *bState) draw(stat decor.Statistics) io.Reader { | |
| 401 | 387 | for _, d := range s.pDecorators { |
| 402 | s.bufP.WriteString(d.Decor(stat)) | |
| 388 | str := d.Decor(stat) | |
| 389 | stat.OccupiedWidth += utf8.RuneCountInString(stripansi.Strip(str)) | |
| 390 | s.bufP.WriteString(str) | |
| 403 | 391 | } |
| 404 | 392 | |
| 405 | 393 | for _, d := range s.aDecorators { |
| 406 | s.bufA.WriteString(d.Decor(stat)) | |
| 394 | str := d.Decor(stat) | |
| 395 | stat.OccupiedWidth += utf8.RuneCountInString(stripansi.Strip(str)) | |
| 396 | s.bufA.WriteString(str) | |
| 407 | 397 | } |
| 408 | 398 | |
| 409 | 399 | s.bufA.WriteByte('\n') |
| 410 | 400 | |
| 411 | prependCount := utf8.RuneCount(s.bufP.Bytes()) | |
| 412 | appendCount := utf8.RuneCount(s.bufA.Bytes()) - 1 | |
| 413 | ||
| 414 | if fitWidth := s.width; termWidth > 1 { | |
| 415 | if !s.trimSpace { | |
| 416 | // reserve space for edge spaces | |
| 417 | termWidth -= 2 | |
| 418 | s.bufB.WriteByte(' ') | |
| 419 | defer s.bufB.WriteByte(' ') | |
| 420 | } | |
| 421 | if prependCount+s.width+appendCount > termWidth { | |
| 422 | fitWidth = termWidth - prependCount - appendCount | |
| 423 | } | |
| 424 | s.filler.Fill(s.bufB, fitWidth, stat) | |
| 425 | } | |
| 401 | if !s.trimSpace && stat.TermWidth >= 2 { | |
| 402 | defer s.bufB.WriteByte(' ') | |
| 403 | s.bufB.WriteByte(' ') | |
| 404 | stat.OccupiedWidth += 2 | |
| 405 | } | |
| 406 | ||
| 407 | s.filler.Fill(s.bufB, s.width, stat) | |
| 426 | 408 | |
| 427 | 409 | return io.MultiReader(s.bufP, s.bufB, s.bufA) |
| 428 | 410 | } |
| 449 | 431 | return table |
| 450 | 432 | } |
| 451 | 433 | |
| 452 | func newStatistics(s *bState) *decor.Statistics { | |
| 453 | return &decor.Statistics{ | |
| 434 | func newStatistics(tw int, s *bState) decor.Statistics { | |
| 435 | return decor.Statistics{ | |
| 454 | 436 | ID: s.id, |
| 455 | 437 | Completed: s.completeFlushed, |
| 456 | 438 | Total: s.total, |
| 457 | 439 | Current: s.current, |
| 440 | TermWidth: tw, | |
| 458 | 441 | } |
| 459 | 442 | } |
| 460 | 443 | |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "io" |
| 4 | "unicode/utf8" | |
| 5 | 4 | |
| 6 | 5 | "github.com/vbauerster/mpb/v5/decor" |
| 7 | "github.com/vbauerster/mpb/v5/internal" | |
| 8 | 6 | ) |
| 9 | 7 | |
| 10 | const ( | |
| 11 | rLeft = iota | |
| 12 | rFill | |
| 13 | rTip | |
| 14 | rEmpty | |
| 15 | rRight | |
| 16 | rRevTip | |
| 17 | rRefill | |
| 18 | ) | |
| 19 | ||
| 20 | // DefaultBarStyle is a string containing 7 runes. | |
| 21 | // Each rune is a building block of a progress bar. | |
| 8 | // BarFiller interface. | |
| 9 | // Bar (without decorators) renders itself by calling BarFiller's Fill method. | |
| 22 | 10 | // |
| 23 | // '1st rune' stands for left boundary rune | |
| 11 | // `reqWidth` is requested width, which is set via: | |
| 12 | // func WithWidth(width int) ContainerOption | |
| 13 | // func BarWidth(width int) BarOption | |
| 24 | 14 | // |
| 25 | // '2nd rune' stands for fill rune | |
| 15 | // Default implementations can be obtained via: | |
| 26 | 16 | // |
| 27 | // '3rd rune' stands for tip rune | |
| 17 | // func NewBarFiller(style string, reverse bool) BarFiller | |
| 18 | // func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller | |
| 28 | 19 | // |
| 29 | // '4th rune' stands for empty rune | |
| 30 | // | |
| 31 | // '5th rune' stands for right boundary rune | |
| 32 | // | |
| 33 | // '6th rune' stands for reverse tip rune | |
| 34 | // | |
| 35 | // '7th rune' stands for refill rune | |
| 36 | // | |
| 37 | const DefaultBarStyle string = "[=>-]<+" | |
| 38 | ||
| 39 | type barFiller struct { | |
| 40 | format [][]byte | |
| 41 | tip []byte | |
| 42 | refill int64 | |
| 43 | reverse bool | |
| 44 | flush func(w io.Writer, bb [][]byte) | |
| 20 | type BarFiller interface { | |
| 21 | Fill(w io.Writer, reqWidth int, stat decor.Statistics) | |
| 45 | 22 | } |
| 46 | 23 | |
| 47 | // NewBarFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. | |
| 48 | func NewBarFiller(style string, reverse bool) BarFiller { | |
| 49 | if style == "" { | |
| 50 | style = DefaultBarStyle | |
| 51 | } | |
| 52 | bf := &barFiller{ | |
| 53 | format: make([][]byte, utf8.RuneCountInString(style)), | |
| 54 | reverse: reverse, | |
| 55 | } | |
| 56 | bf.SetStyle(style) | |
| 57 | return bf | |
| 24 | // BarFillerFunc is function type adapter to convert function into BarFiller. | |
| 25 | type BarFillerFunc func(w io.Writer, reqWidth int, stat decor.Statistics) | |
| 26 | ||
| 27 | func (f BarFillerFunc) Fill(w io.Writer, reqWidth int, stat decor.Statistics) { | |
| 28 | f(w, reqWidth, stat) | |
| 58 | 29 | } |
| 59 | ||
| 60 | func (s *barFiller) SetStyle(style string) { | |
| 61 | if !utf8.ValidString(style) { | |
| 62 | return | |
| 63 | } | |
| 64 | src := make([][]byte, 0, utf8.RuneCountInString(style)) | |
| 65 | for _, r := range style { | |
| 66 | src = append(src, []byte(string(r))) | |
| 67 | } | |
| 68 | copy(s.format, src) | |
| 69 | s.SetReverse(s.reverse) | |
| 70 | } | |
| 71 | ||
| 72 | func (s *barFiller) SetReverse(reverse bool) { | |
| 73 | if reverse { | |
| 74 | s.tip = s.format[rRevTip] | |
| 75 | s.flush = reverseFlush | |
| 76 | } else { | |
| 77 | s.tip = s.format[rTip] | |
| 78 | s.flush = regularFlush | |
| 79 | } | |
| 80 | s.reverse = reverse | |
| 81 | } | |
| 82 | ||
| 83 | func (s *barFiller) SetRefill(amount int64) { | |
| 84 | s.refill = amount | |
| 85 | } | |
| 86 | ||
| 87 | func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 88 | // don't count rLeft and rRight as progress | |
| 89 | width -= 2 | |
| 90 | if width < 2 { | |
| 91 | return | |
| 92 | } | |
| 93 | w.Write(s.format[rLeft]) | |
| 94 | defer w.Write(s.format[rRight]) | |
| 95 | ||
| 96 | bb := make([][]byte, width) | |
| 97 | ||
| 98 | cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width)) | |
| 99 | ||
| 100 | for i := 0; i < cwidth; i++ { | |
| 101 | bb[i] = s.format[rFill] | |
| 102 | } | |
| 103 | ||
| 104 | if s.refill > 0 { | |
| 105 | var rwidth int | |
| 106 | if s.refill > stat.Current { | |
| 107 | rwidth = cwidth | |
| 108 | } else { | |
| 109 | rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width)) | |
| 110 | } | |
| 111 | for i := 0; i < rwidth; i++ { | |
| 112 | bb[i] = s.format[rRefill] | |
| 113 | } | |
| 114 | } | |
| 115 | ||
| 116 | if cwidth > 0 && cwidth < width { | |
| 117 | bb[cwidth-1] = s.tip | |
| 118 | } | |
| 119 | ||
| 120 | for i := cwidth; i < width; i++ { | |
| 121 | bb[i] = s.format[rEmpty] | |
| 122 | } | |
| 123 | ||
| 124 | s.flush(w, bb) | |
| 125 | } | |
| 126 | ||
| 127 | func regularFlush(w io.Writer, bb [][]byte) { | |
| 128 | for i := 0; i < len(bb); i++ { | |
| 129 | w.Write(bb[i]) | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 133 | func reverseFlush(w io.Writer, bb [][]byte) { | |
| 134 | for i := len(bb) - 1; i >= 0; i-- { | |
| 135 | w.Write(bb[i]) | |
| 136 | } | |
| 137 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "unicode/utf8" | |
| 5 | ||
| 6 | "github.com/vbauerster/mpb/v5/decor" | |
| 7 | "github.com/vbauerster/mpb/v5/internal" | |
| 8 | ) | |
| 9 | ||
| 10 | const ( | |
| 11 | rLeft = iota | |
| 12 | rFill | |
| 13 | rTip | |
| 14 | rEmpty | |
| 15 | rRight | |
| 16 | rRevTip | |
| 17 | rRefill | |
| 18 | ) | |
| 19 | ||
| 20 | // DefaultBarStyle is a string containing 7 runes. | |
| 21 | // Each rune is a building block of a progress bar. | |
| 22 | // | |
| 23 | // '1st rune' stands for left boundary rune | |
| 24 | // | |
| 25 | // '2nd rune' stands for fill rune | |
| 26 | // | |
| 27 | // '3rd rune' stands for tip rune | |
| 28 | // | |
| 29 | // '4th rune' stands for empty rune | |
| 30 | // | |
| 31 | // '5th rune' stands for right boundary rune | |
| 32 | // | |
| 33 | // '6th rune' stands for reverse tip rune | |
| 34 | // | |
| 35 | // '7th rune' stands for refill rune | |
| 36 | // | |
| 37 | const DefaultBarStyle string = "[=>-]<+" | |
| 38 | ||
| 39 | type barFiller struct { | |
| 40 | format [][]byte | |
| 41 | tip []byte | |
| 42 | refill int64 | |
| 43 | reverse bool | |
| 44 | flush func(w io.Writer, bb [][]byte) | |
| 45 | } | |
| 46 | ||
| 47 | // NewBarFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. | |
| 48 | func NewBarFiller(style string, reverse bool) BarFiller { | |
| 49 | if style == "" { | |
| 50 | style = DefaultBarStyle | |
| 51 | } | |
| 52 | bf := &barFiller{ | |
| 53 | format: make([][]byte, utf8.RuneCountInString(style)), | |
| 54 | reverse: reverse, | |
| 55 | } | |
| 56 | bf.SetStyle(style) | |
| 57 | return bf | |
| 58 | } | |
| 59 | ||
| 60 | func (s *barFiller) SetStyle(style string) { | |
| 61 | if !utf8.ValidString(style) { | |
| 62 | return | |
| 63 | } | |
| 64 | src := make([][]byte, 0, utf8.RuneCountInString(style)) | |
| 65 | for _, r := range style { | |
| 66 | src = append(src, []byte(string(r))) | |
| 67 | } | |
| 68 | copy(s.format, src) | |
| 69 | s.SetReverse(s.reverse) | |
| 70 | } | |
| 71 | ||
| 72 | func (s *barFiller) SetReverse(reverse bool) { | |
| 73 | if reverse { | |
| 74 | s.tip = s.format[rRevTip] | |
| 75 | s.flush = reverseFlush | |
| 76 | } else { | |
| 77 | s.tip = s.format[rTip] | |
| 78 | s.flush = regularFlush | |
| 79 | } | |
| 80 | s.reverse = reverse | |
| 81 | } | |
| 82 | ||
| 83 | func (s *barFiller) SetRefill(amount int64) { | |
| 84 | s.refill = amount | |
| 85 | } | |
| 86 | ||
| 87 | func (s *barFiller) Fill(w io.Writer, width int, stat decor.Statistics) { | |
| 88 | // auto shrink | |
| 89 | if stat.OccupiedWidth+width > stat.TermWidth { | |
| 90 | width = stat.TermWidth - stat.OccupiedWidth | |
| 91 | } | |
| 92 | // don't count rLeft and rRight as progress | |
| 93 | width -= 2 | |
| 94 | if width < 2 { | |
| 95 | return | |
| 96 | } | |
| 97 | w.Write(s.format[rLeft]) | |
| 98 | defer w.Write(s.format[rRight]) | |
| 99 | ||
| 100 | bb := make([][]byte, width) | |
| 101 | ||
| 102 | cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width)) | |
| 103 | ||
| 104 | for i := 0; i < cwidth; i++ { | |
| 105 | bb[i] = s.format[rFill] | |
| 106 | } | |
| 107 | ||
| 108 | if s.refill > 0 { | |
| 109 | var rwidth int | |
| 110 | if s.refill > stat.Current { | |
| 111 | rwidth = cwidth | |
| 112 | } else { | |
| 113 | rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width)) | |
| 114 | } | |
| 115 | for i := 0; i < rwidth; i++ { | |
| 116 | bb[i] = s.format[rRefill] | |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | if cwidth > 0 && cwidth < width { | |
| 121 | bb[cwidth-1] = s.tip | |
| 122 | } | |
| 123 | ||
| 124 | for i := cwidth; i < width; i++ { | |
| 125 | bb[i] = s.format[rEmpty] | |
| 126 | } | |
| 127 | ||
| 128 | s.flush(w, bb) | |
| 129 | } | |
| 130 | ||
| 131 | func regularFlush(w io.Writer, bb [][]byte) { | |
| 132 | for i := 0; i < len(bb); i++ { | |
| 133 | w.Write(bb[i]) | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | func reverseFlush(w io.Writer, bb [][]byte) { | |
| 138 | for i := len(bb) - 1; i >= 0; i-- { | |
| 139 | w.Write(bb[i]) | |
| 140 | } | |
| 141 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "strings" | |
| 5 | "unicode/utf8" | |
| 6 | ||
| 7 | "github.com/vbauerster/mpb/v5/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 | // DefaultSpinnerStyle is a slice of strings, which makes a spinner. | |
| 21 | var DefaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 22 | ||
| 23 | type spinnerFiller struct { | |
| 24 | frames []string | |
| 25 | count uint | |
| 26 | alignment SpinnerAlignment | |
| 27 | } | |
| 28 | ||
| 29 | // NewSpinnerFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. | |
| 30 | func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller { | |
| 31 | if len(style) == 0 { | |
| 32 | style = DefaultSpinnerStyle | |
| 33 | } | |
| 34 | filler := &spinnerFiller{ | |
| 35 | frames: style, | |
| 36 | alignment: alignment, | |
| 37 | } | |
| 38 | return filler | |
| 39 | } | |
| 40 | ||
| 41 | func (s *spinnerFiller) Fill(w io.Writer, width int, stat decor.Statistics) { | |
| 42 | // auto shrink | |
| 43 | if stat.OccupiedWidth+width > stat.TermWidth { | |
| 44 | width = stat.TermWidth - stat.OccupiedWidth | |
| 45 | } | |
| 46 | ||
| 47 | frame := s.frames[s.count%uint(len(s.frames))] | |
| 48 | frameWidth := utf8.RuneCountInString(frame) | |
| 49 | ||
| 50 | if width < frameWidth { | |
| 51 | return | |
| 52 | } | |
| 53 | ||
| 54 | switch rest := width - frameWidth; s.alignment { | |
| 55 | case SpinnerOnLeft: | |
| 56 | io.WriteString(w, frame+strings.Repeat(" ", rest)) | |
| 57 | case SpinnerOnMiddle: | |
| 58 | str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) | |
| 59 | io.WriteString(w, str) | |
| 60 | case SpinnerOnRight: | |
| 61 | io.WriteString(w, strings.Repeat(" ", rest)+frame) | |
| 62 | } | |
| 63 | s.count++ | |
| 64 | } |
| 82 | 82 | } |
| 83 | 83 | |
| 84 | 84 | func makeBarFillerOnComplete(filler BarFiller, message string) BarFiller { |
| 85 | return BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { | |
| 85 | return BarFillerFunc(func(w io.Writer, width int, st decor.Statistics) { | |
| 86 | 86 | if st.Completed { |
| 87 | 87 | io.WriteString(w, message) |
| 88 | 88 | } else { |
| 102 | 102 | |
| 103 | 103 | // BarExtender is an option to extend bar to the next new line, with |
| 104 | 104 | // arbitrary output. |
| 105 | func BarExtender(extender BarFiller) BarOption { | |
| 106 | if extender == nil { | |
| 107 | return nil | |
| 108 | } | |
| 109 | return func(s *bState) { | |
| 110 | s.extender = makeExtFunc(extender) | |
| 111 | } | |
| 112 | } | |
| 113 | ||
| 114 | func makeExtFunc(extender BarFiller) extFunc { | |
| 105 | func BarExtender(filler BarFiller) BarOption { | |
| 106 | if filler == nil { | |
| 107 | return nil | |
| 108 | } | |
| 109 | return func(s *bState) { | |
| 110 | s.extender = makeExtFunc(filler) | |
| 111 | } | |
| 112 | } | |
| 113 | ||
| 114 | func makeExtFunc(filler BarFiller) extFunc { | |
| 115 | 115 | buf := new(bytes.Buffer) |
| 116 | nl := []byte("\n") | |
| 117 | return func(r io.Reader, tw int, st *decor.Statistics) (io.Reader, int) { | |
| 118 | extender.Fill(buf, tw, st) | |
| 119 | return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), nl) | |
| 116 | return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) { | |
| 117 | filler.Fill(buf, reqWidth, st) | |
| 118 | return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n")) | |
| 120 | 119 | } |
| 121 | 120 | } |
| 122 | 121 | |
| 176 | 176 | var pCount uint32 |
| 177 | 177 | bar := p.AddBar(int64(total), |
| 178 | 178 | PrependDecorators(panicDecorator(panicMsg, |
| 179 | func(st *decor.Statistics) bool { | |
| 179 | func(st decor.Statistics) bool { | |
| 180 | 180 | if st.Current >= 42 { |
| 181 | 181 | atomic.AddUint32(&pCount, 1) |
| 182 | 182 | return true |
| 212 | 212 | var pCount uint32 |
| 213 | 213 | bar := p.AddBar(int64(total), |
| 214 | 214 | PrependDecorators(panicDecorator(panicMsg, |
| 215 | func(st *decor.Statistics) bool { | |
| 215 | func(st decor.Statistics) bool { | |
| 216 | 216 | if st.Completed { |
| 217 | 217 | atomic.AddUint32(&pCount, 1) |
| 218 | 218 | return true |
| 239 | 239 | } |
| 240 | 240 | } |
| 241 | 241 | |
| 242 | func panicDecorator(panicMsg string, cond func(*decor.Statistics) bool) decor.Decorator { | |
| 242 | func panicDecorator(panicMsg string, cond func(decor.Statistics) bool) decor.Decorator { | |
| 243 | 243 | d := &decorator{ |
| 244 | 244 | panicMsg: panicMsg, |
| 245 | 245 | cond: cond, |
| 251 | 251 | type decorator struct { |
| 252 | 252 | decor.WC |
| 253 | 253 | panicMsg string |
| 254 | cond func(*decor.Statistics) bool | |
| 255 | } | |
| 256 | ||
| 257 | func (d *decorator) Decor(st *decor.Statistics) string { | |
| 254 | cond func(decor.Statistics) bool | |
| 255 | } | |
| 256 | ||
| 257 | func (d *decorator) Decor(st decor.Statistics) string { | |
| 258 | 258 | if d.cond(st) { |
| 259 | 259 | panic(d.panicMsg) |
| 260 | 260 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "io/ioutil" | |
| 5 | "sync" | |
| 6 | "time" | |
| 7 | ) | |
| 8 | ||
| 9 | // ContainerOption is a function option which changes the default | |
| 10 | // behavior of progress container, if passed to mpb.New(...ContainerOption). | |
| 11 | type ContainerOption func(*pState) | |
| 12 | ||
| 13 | // WithWaitGroup provides means to have a single joint point. If | |
| 14 | // *sync.WaitGroup is provided, you can safely call just p.Wait() | |
| 15 | // without calling Wait() on provided *sync.WaitGroup. Makes sense | |
| 16 | // when there are more than one bar to render. | |
| 17 | func WithWaitGroup(wg *sync.WaitGroup) ContainerOption { | |
| 18 | return func(s *pState) { | |
| 19 | s.uwg = wg | |
| 20 | } | |
| 21 | } | |
| 22 | ||
| 23 | // WithWidth sets container width. Default is 80. Bars inherit this | |
| 24 | // width, as long as no BarWidth is applied. | |
| 25 | func WithWidth(width int) ContainerOption { | |
| 26 | return func(s *pState) { | |
| 27 | if width < 0 { | |
| 28 | return | |
| 29 | } | |
| 30 | s.width = width | |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | // WithRefreshRate overrides default 120ms refresh rate. | |
| 35 | func WithRefreshRate(d time.Duration) ContainerOption { | |
| 36 | return func(s *pState) { | |
| 37 | s.rr = d | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | // WithManualRefresh disables internal auto refresh time.Ticker. | |
| 42 | // Refresh will occur upon receive value from provided ch. | |
| 43 | func WithManualRefresh(ch <-chan time.Time) ContainerOption { | |
| 44 | return func(s *pState) { | |
| 45 | s.refreshSrc = ch | |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | // WithRenderDelay delays rendering. By default rendering starts as | |
| 50 | // soon as bar is added, with this option it's possible to delay | |
| 51 | // rendering process by keeping provided chan unclosed. In other words | |
| 52 | // rendering will start as soon as provided chan is closed. | |
| 53 | func WithRenderDelay(ch <-chan struct{}) ContainerOption { | |
| 54 | return func(s *pState) { | |
| 55 | s.renderDelay = ch | |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | // WithShutdownNotifier provided chanel will be closed, after all bars | |
| 60 | // have been rendered. | |
| 61 | func WithShutdownNotifier(ch chan struct{}) ContainerOption { | |
| 62 | return func(s *pState) { | |
| 63 | s.shutdownNotifier = ch | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | // WithOutput overrides default os.Stdout output. Setting it to nil | |
| 68 | // will effectively disable auto refresh rate and discard any output, | |
| 69 | // useful if you want to disable progress bars with little overhead. | |
| 70 | func WithOutput(w io.Writer) ContainerOption { | |
| 71 | return func(s *pState) { | |
| 72 | if w == nil { | |
| 73 | s.refreshSrc = make(chan time.Time) | |
| 74 | s.output = ioutil.Discard | |
| 75 | return | |
| 76 | } | |
| 77 | s.output = w | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | // WithDebugOutput sets debug output. | |
| 82 | func WithDebugOutput(w io.Writer) ContainerOption { | |
| 83 | if w == nil { | |
| 84 | return nil | |
| 85 | } | |
| 86 | return func(s *pState) { | |
| 87 | s.debugOut = w | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | // PopCompletedMode will pop and stop rendering completed bars. | |
| 92 | func PopCompletedMode() ContainerOption { | |
| 93 | return func(s *pState) { | |
| 94 | s.popCompleted = true | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | // ContainerOptOn returns option when condition evaluates to true. | |
| 99 | func ContainerOptOn(option ContainerOption, condition func() bool) ContainerOption { | |
| 100 | if condition() { | |
| 101 | return option | |
| 102 | } | |
| 103 | return nil | |
| 104 | } |
| 0 | 0 | package decor |
| 1 | 1 | |
| 2 | 2 | // Any decorator displays text, that can be changed during decorator's |
| 3 | // lifetime via provided func call back. | |
| 3 | // lifetime via provided DecorFunc. | |
| 4 | 4 | // |
| 5 | // `f` call back which provides string to display | |
| 5 | // `fn` DecorFunc callback | |
| 6 | 6 | // |
| 7 | 7 | // `wcc` optional WC config |
| 8 | 8 | // |
| 9 | func Any(f func(*Statistics) string, wcc ...WC) Decorator { | |
| 10 | return &any{initWC(wcc...), f} | |
| 9 | func Any(fn DecorFunc, wcc ...WC) Decorator { | |
| 10 | return &any{initWC(wcc...), fn} | |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | 13 | type any struct { |
| 14 | 14 | WC |
| 15 | f func(*Statistics) string | |
| 15 | fn DecorFunc | |
| 16 | 16 | } |
| 17 | 17 | |
| 18 | func (d *any) Decor(s *Statistics) string { | |
| 19 | return d.FormatMsg(d.f(s)) | |
| 18 | func (d *any) Decor(s Statistics) string { | |
| 19 | return d.FormatMsg(d.fn(s)) | |
| 20 | 20 | } |
| 45 | 45 | return Any(chooseSizeProducer(unit, pairFmt), wcc...) |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | func chooseSizeProducer(unit int, format string) func(*Statistics) string { | |
| 48 | func chooseSizeProducer(unit int, format string) DecorFunc { | |
| 49 | 49 | if format == "" { |
| 50 | 50 | format = "%d / %d" |
| 51 | 51 | } |
| 52 | 52 | switch unit { |
| 53 | 53 | case UnitKiB: |
| 54 | return func(s *Statistics) string { | |
| 54 | return func(s Statistics) string { | |
| 55 | 55 | return fmt.Sprintf(format, SizeB1024(s.Current), SizeB1024(s.Total)) |
| 56 | 56 | } |
| 57 | 57 | case UnitKB: |
| 58 | return func(s *Statistics) string { | |
| 58 | return func(s Statistics) string { | |
| 59 | 59 | return fmt.Sprintf(format, SizeB1000(s.Current), SizeB1000(s.Total)) |
| 60 | 60 | } |
| 61 | 61 | default: |
| 62 | return func(s *Statistics) string { | |
| 62 | return func(s Statistics) string { | |
| 63 | 63 | return fmt.Sprintf(format, s.Current, s.Total) |
| 64 | 64 | } |
| 65 | 65 | } |
| 46 | 46 | // Statistics consists of progress related statistics, that Decorator |
| 47 | 47 | // may need. |
| 48 | 48 | type Statistics struct { |
| 49 | ID int | |
| 50 | Completed bool | |
| 51 | Total int64 | |
| 52 | Current int64 | |
| 49 | ID int | |
| 50 | Completed bool | |
| 51 | Total int64 | |
| 52 | Current int64 | |
| 53 | TermWidth int | |
| 54 | OccupiedWidth int | |
| 53 | 55 | } |
| 54 | 56 | |
| 55 | 57 | // Decorator interface. |
| 56 | // Implementors should embed WC type, that way only single method | |
| 57 | // Decor(*Statistics) needs to be implemented, the rest will be handled | |
| 58 | // by WC type. | |
| 58 | // Most of the time there is no need to implement this interface | |
| 59 | // manually, as decor package already provides a wide range of decorators | |
| 60 | // which implement this interface. If however built-in decorators don't | |
| 61 | // meet your needs, you're free to implement your own one by implementing | |
| 62 | // this particular interface. The easy way to go is to convert a | |
| 63 | // `DecorFunc` into a `Decorator` interface by using provided | |
| 64 | // `func Any`(DecorFunc, ...WC) Decorator`. | |
| 59 | 65 | type Decorator interface { |
| 60 | 66 | Configurator |
| 61 | 67 | Synchronizer |
| 62 | Decor(*Statistics) string | |
| 68 | Decor(Statistics) string | |
| 63 | 69 | } |
| 70 | ||
| 71 | // DecorFunc func type. | |
| 72 | // To be used with `func Any`(DecorFunc, ...WC) Decorator`. | |
| 73 | type DecorFunc func(Statistics) string | |
| 64 | 74 | |
| 65 | 75 | // Synchronizer interface. |
| 66 | 76 | // All decorators implement this interface implicitly. Its Sync |
| 24 | 24 | func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator { |
| 25 | 25 | var msg string |
| 26 | 26 | producer := chooseTimeProducer(style) |
| 27 | f := func(s *Statistics) string { | |
| 27 | fn := func(s Statistics) string { | |
| 28 | 28 | if !s.Completed { |
| 29 | 29 | msg = producer(time.Since(startTime)) |
| 30 | 30 | } |
| 31 | 31 | return msg |
| 32 | 32 | } |
| 33 | return Any(f, wcc...) | |
| 33 | return Any(fn, wcc...) | |
| 34 | 34 | } |
| 62 | 62 | producer func(time.Duration) string |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | func (d *movingAverageETA) Decor(s *Statistics) string { | |
| 65 | func (d *movingAverageETA) Decor(s Statistics) string { | |
| 66 | 66 | v := math.Round(d.average.Value()) |
| 67 | 67 | remaining := time.Duration((s.Total - s.Current) * int64(v)) |
| 68 | 68 | if d.normalizer != nil { |
| 116 | 116 | producer func(time.Duration) string |
| 117 | 117 | } |
| 118 | 118 | |
| 119 | func (d *averageETA) Decor(s *Statistics) string { | |
| 119 | func (d *averageETA) Decor(s Statistics) string { | |
| 120 | 120 | var remaining time.Duration |
| 121 | 121 | if s.Current != 0 { |
| 122 | 122 | durPerItem := float64(time.Since(d.startTime)) / float64(s.Current) |
| 3 | 3 | "fmt" |
| 4 | 4 | "strings" |
| 5 | 5 | "unicode/utf8" |
| 6 | ||
| 7 | "github.com/acarl005/stripansi" | |
| 6 | 8 | ) |
| 7 | 9 | |
| 8 | 10 | // Merge wraps its decorator argument with intention to sync width |
| 63 | 65 | return d.Decorator |
| 64 | 66 | } |
| 65 | 67 | |
| 66 | func (d *mergeDecorator) Decor(s *Statistics) string { | |
| 68 | func (d *mergeDecorator) Decor(s Statistics) string { | |
| 67 | 69 | msg := d.Decorator.Decor(s) |
| 68 | msgLen := utf8.RuneCountInString(msg) | |
| 70 | msgLen := utf8.RuneCountInString(stripansi.Strip(msg)) | |
| 69 | 71 | if (d.wc.C & DextraSpace) != 0 { |
| 70 | 72 | msgLen++ |
| 71 | 73 | } |
| 100 | 102 | WC |
| 101 | 103 | } |
| 102 | 104 | |
| 103 | func (d *placeHolderDecorator) Decor(*Statistics) string { | |
| 105 | func (d *placeHolderDecorator) Decor(Statistics) string { | |
| 104 | 106 | return "" |
| 105 | 107 | } |
| 7 | 7 | // `wcc` optional WC config |
| 8 | 8 | // |
| 9 | 9 | func Name(str string, wcc ...WC) Decorator { |
| 10 | return Any(func(*Statistics) string { return str }, wcc...) | |
| 10 | return Any(func(Statistics) string { return str }, wcc...) | |
| 11 | 11 | } |
| 23 | 23 | msg string |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | func (d *onCompleteWrapper) Decor(s *Statistics) string { | |
| 26 | func (d *onCompleteWrapper) Decor(s Statistics) string { | |
| 27 | 27 | if s.Completed { |
| 28 | 28 | wc := d.GetConf() |
| 29 | 29 | return wc.FormatMsg(d.msg) |
| 49 | 49 | if format == "" { |
| 50 | 50 | format = "% d" |
| 51 | 51 | } |
| 52 | f := func(s *Statistics) string { | |
| 52 | f := func(s Statistics) string { | |
| 53 | 53 | p := internal.Percentage(s.Total, s.Current, 100) |
| 54 | 54 | return fmt.Sprintf(format, percentageType(p)) |
| 55 | 55 | } |
| 77 | 77 | msg string |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | func (d *movingAverageSpeed) Decor(s *Statistics) string { | |
| 80 | func (d *movingAverageSpeed) Decor(s Statistics) string { | |
| 81 | 81 | if !s.Completed { |
| 82 | 82 | var speed float64 |
| 83 | 83 | if v := d.average.Value(); v > 0 { |
| 139 | 139 | msg string |
| 140 | 140 | } |
| 141 | 141 | |
| 142 | func (d *averageSpeed) Decor(s *Statistics) string { | |
| 142 | func (d *averageSpeed) Decor(s Statistics) string { | |
| 143 | 143 | if !s.Completed { |
| 144 | 144 | speed := float64(s.Current) / float64(time.Since(d.startTime)) |
| 145 | 145 | d.msg = d.producer(speed * 1e9) |
| 121 | 121 | for _, tc := range cases { |
| 122 | 122 | t.Run(tc.name, func(t *testing.T) { |
| 123 | 123 | decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) |
| 124 | stat := &Statistics{ | |
| 124 | stat := Statistics{ | |
| 125 | 125 | Current: tc.current, |
| 126 | 126 | } |
| 127 | 127 | res := decor.Decor(stat) |
| 249 | 249 | for _, tc := range cases { |
| 250 | 250 | t.Run(tc.name, func(t *testing.T) { |
| 251 | 251 | decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) |
| 252 | stat := &Statistics{ | |
| 252 | stat := Statistics{ | |
| 253 | 253 | Current: tc.current, |
| 254 | 254 | } |
| 255 | 255 | res := decor.Decor(stat) |
| 11 | 11 | frames = defaultSpinnerStyle |
| 12 | 12 | } |
| 13 | 13 | var count uint |
| 14 | f := func(s *Statistics) string { | |
| 14 | f := func(s Statistics) string { | |
| 15 | 15 | frame := frames[count%uint(len(frames))] |
| 16 | 16 | count++ |
| 17 | 17 | return frame |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | for _, test := range tests { |
| 34 | got := test.decorator.Decor(new(decor.Statistics)) | |
| 34 | got := test.decorator.Decor(decor.Statistics{}) | |
| 35 | 35 | if got != test.want { |
| 36 | 36 | t.Errorf("Want: %q, Got: %q\n", test.want, got) |
| 37 | 37 | } |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | 41 | type step struct { |
| 42 | stat *decor.Statistics | |
| 42 | stat decor.Statistics | |
| 43 | 43 | decorator decor.Decorator |
| 44 | 44 | want string |
| 45 | 45 | } |
| 49 | 49 | testCases := [][]step{ |
| 50 | 50 | { |
| 51 | 51 | { |
| 52 | &decor.Statistics{Total: 100, Current: 8}, | |
| 52 | decor.Statistics{Total: 100, Current: 8}, | |
| 53 | 53 | decor.Percentage(decor.WCSyncWidth), |
| 54 | 54 | "8 %", |
| 55 | 55 | }, |
| 56 | 56 | { |
| 57 | &decor.Statistics{Total: 100, Current: 9}, | |
| 57 | decor.Statistics{Total: 100, Current: 9}, | |
| 58 | 58 | decor.Percentage(decor.WCSyncWidth), |
| 59 | 59 | "9 %", |
| 60 | 60 | }, |
| 61 | 61 | }, |
| 62 | 62 | { |
| 63 | 63 | { |
| 64 | &decor.Statistics{Total: 100, Current: 9}, | |
| 64 | decor.Statistics{Total: 100, Current: 9}, | |
| 65 | 65 | decor.Percentage(decor.WCSyncWidth), |
| 66 | 66 | " 9 %", |
| 67 | 67 | }, |
| 68 | 68 | { |
| 69 | &decor.Statistics{Total: 100, Current: 10}, | |
| 69 | decor.Statistics{Total: 100, Current: 10}, | |
| 70 | 70 | decor.Percentage(decor.WCSyncWidth), |
| 71 | 71 | "10 %", |
| 72 | 72 | }, |
| 73 | 73 | }, |
| 74 | 74 | { |
| 75 | 75 | { |
| 76 | &decor.Statistics{Total: 100, Current: 9}, | |
| 76 | decor.Statistics{Total: 100, Current: 9}, | |
| 77 | 77 | decor.Percentage(decor.WCSyncWidth), |
| 78 | 78 | " 9 %", |
| 79 | 79 | }, |
| 80 | 80 | { |
| 81 | &decor.Statistics{Total: 100, Current: 100}, | |
| 81 | decor.Statistics{Total: 100, Current: 100}, | |
| 82 | 82 | decor.Percentage(decor.WCSyncWidth), |
| 83 | 83 | "100 %", |
| 84 | 84 | }, |
| 93 | 93 | testCases := [][]step{ |
| 94 | 94 | { |
| 95 | 95 | { |
| 96 | &decor.Statistics{Total: 100, Current: 8}, | |
| 96 | decor.Statistics{Total: 100, Current: 8}, | |
| 97 | 97 | decor.Percentage(decor.WCSyncWidthR), |
| 98 | 98 | "8 %", |
| 99 | 99 | }, |
| 100 | 100 | { |
| 101 | &decor.Statistics{Total: 100, Current: 9}, | |
| 101 | decor.Statistics{Total: 100, Current: 9}, | |
| 102 | 102 | decor.Percentage(decor.WCSyncWidthR), |
| 103 | 103 | "9 %", |
| 104 | 104 | }, |
| 105 | 105 | }, |
| 106 | 106 | { |
| 107 | 107 | { |
| 108 | &decor.Statistics{Total: 100, Current: 9}, | |
| 108 | decor.Statistics{Total: 100, Current: 9}, | |
| 109 | 109 | decor.Percentage(decor.WCSyncWidthR), |
| 110 | 110 | "9 % ", |
| 111 | 111 | }, |
| 112 | 112 | { |
| 113 | &decor.Statistics{Total: 100, Current: 10}, | |
| 113 | decor.Statistics{Total: 100, Current: 10}, | |
| 114 | 114 | decor.Percentage(decor.WCSyncWidthR), |
| 115 | 115 | "10 %", |
| 116 | 116 | }, |
| 117 | 117 | }, |
| 118 | 118 | { |
| 119 | 119 | { |
| 120 | &decor.Statistics{Total: 100, Current: 9}, | |
| 120 | decor.Statistics{Total: 100, Current: 9}, | |
| 121 | 121 | decor.Percentage(decor.WCSyncWidthR), |
| 122 | 122 | "9 % ", |
| 123 | 123 | }, |
| 124 | 124 | { |
| 125 | &decor.Statistics{Total: 100, Current: 100}, | |
| 125 | decor.Statistics{Total: 100, Current: 100}, | |
| 126 | 126 | decor.Percentage(decor.WCSyncWidthR), |
| 127 | 127 | "100 %", |
| 128 | 128 | }, |
| 137 | 137 | testCases := [][]step{ |
| 138 | 138 | { |
| 139 | 139 | { |
| 140 | &decor.Statistics{Total: 100, Current: 8}, | |
| 140 | decor.Statistics{Total: 100, Current: 8}, | |
| 141 | 141 | decor.Percentage(decor.WCSyncSpace), |
| 142 | 142 | " 8 %", |
| 143 | 143 | }, |
| 144 | 144 | { |
| 145 | &decor.Statistics{Total: 100, Current: 9}, | |
| 145 | decor.Statistics{Total: 100, Current: 9}, | |
| 146 | 146 | decor.Percentage(decor.WCSyncSpace), |
| 147 | 147 | " 9 %", |
| 148 | 148 | }, |
| 149 | 149 | }, |
| 150 | 150 | { |
| 151 | 151 | { |
| 152 | &decor.Statistics{Total: 100, Current: 9}, | |
| 152 | decor.Statistics{Total: 100, Current: 9}, | |
| 153 | 153 | decor.Percentage(decor.WCSyncSpace), |
| 154 | 154 | " 9 %", |
| 155 | 155 | }, |
| 156 | 156 | { |
| 157 | &decor.Statistics{Total: 100, Current: 10}, | |
| 157 | decor.Statistics{Total: 100, Current: 10}, | |
| 158 | 158 | decor.Percentage(decor.WCSyncSpace), |
| 159 | 159 | " 10 %", |
| 160 | 160 | }, |
| 161 | 161 | }, |
| 162 | 162 | { |
| 163 | 163 | { |
| 164 | &decor.Statistics{Total: 100, Current: 9}, | |
| 164 | decor.Statistics{Total: 100, Current: 9}, | |
| 165 | 165 | decor.Percentage(decor.WCSyncSpace), |
| 166 | 166 | " 9 %", |
| 167 | 167 | }, |
| 168 | 168 | { |
| 169 | &decor.Statistics{Total: 100, Current: 100}, | |
| 169 | decor.Statistics{Total: 100, Current: 100}, | |
| 170 | 170 | decor.Percentage(decor.WCSyncSpace), |
| 171 | 171 | " 100 %", |
| 172 | 172 | }, |
| 25 | 25 | want: "", |
| 26 | 26 | }, |
| 27 | 27 | { |
| 28 | name: "t,c,bw{60,20,80}", | |
| 28 | name: "t,c,bw{60,20,80}trim", | |
| 29 | 29 | total: 60, |
| 30 | 30 | current: 20, |
| 31 | 31 | barWidth: 80, |
| 42 | 42 | want: "", |
| 43 | 43 | }, |
| 44 | 44 | { |
| 45 | name: "t,c,bw{60,20,80}", | |
| 45 | name: "t,c,bw{60,20,80}trim", | |
| 46 | 46 | total: 60, |
| 47 | 47 | current: 20, |
| 48 | 48 | barWidth: 80, |
| 59 | 59 | want: " ", |
| 60 | 60 | }, |
| 61 | 61 | { |
| 62 | name: "t,c,bw,trim{60,20,80,true}", | |
| 62 | name: "t,c,bw{60,20,80}trim", | |
| 63 | 63 | total: 60, |
| 64 | 64 | current: 20, |
| 65 | 65 | barWidth: 80, |
| 76 | 76 | want: " ", |
| 77 | 77 | }, |
| 78 | 78 | { |
| 79 | name: "t,c,bw,trim{60,20,80,true}", | |
| 79 | name: "t,c,bw{60,20,80}trim", | |
| 80 | 80 | total: 60, |
| 81 | 81 | current: 20, |
| 82 | 82 | barWidth: 80, |
| 93 | 93 | want: " ", |
| 94 | 94 | }, |
| 95 | 95 | { |
| 96 | name: "t,c,bw,trim{60,20,80,true}", | |
| 96 | name: "t,c,bw{60,20,80}trim", | |
| 97 | 97 | total: 60, |
| 98 | 98 | current: 20, |
| 99 | 99 | barWidth: 80, |
| 110 | 110 | want: " ", |
| 111 | 111 | }, |
| 112 | 112 | { |
| 113 | name: "t,c,bw,trim{60,20,80,true}", | |
| 113 | name: "t,c,bw{60,20,80}trim", | |
| 114 | 114 | total: 60, |
| 115 | 115 | current: 20, |
| 116 | 116 | barWidth: 80, |
| 127 | 127 | want: " [>-] ", |
| 128 | 128 | }, |
| 129 | 129 | { |
| 130 | name: "t,c,bw,trim{60,20,80,true}", | |
| 130 | name: "t,c,bw{60,20,80}trim", | |
| 131 | 131 | total: 60, |
| 132 | 132 | current: 20, |
| 133 | 133 | barWidth: 80, |
| 144 | 144 | want: " [>--] ", |
| 145 | 145 | }, |
| 146 | 146 | { |
| 147 | name: "t,c,bw,trim{60,20,80,true}", | |
| 147 | name: "t,c,bw{60,20,80}trim", | |
| 148 | 148 | total: 60, |
| 149 | 149 | current: 20, |
| 150 | 150 | barWidth: 80, |
| 161 | 161 | want: " [>---] ", |
| 162 | 162 | }, |
| 163 | 163 | { |
| 164 | name: "t,c,bw,trim{60,20,80,true}", | |
| 164 | name: "t,c,bw{60,20,80}trim", | |
| 165 | 165 | total: 60, |
| 166 | 166 | current: 20, |
| 167 | 167 | barWidth: 80, |
| 178 | 178 | want: " [========================>---------------------------------------------------] ", |
| 179 | 179 | }, |
| 180 | 180 | { |
| 181 | name: "t,c,bw,trim{60,20,80,true}", | |
| 181 | name: "t,c,bw{60,20,80}trim", | |
| 182 | 182 | total: 60, |
| 183 | 183 | current: 20, |
| 184 | 184 | barWidth: 80, |
| 195 | 195 | want: " [------------------------------------------------------------------------------------------------] ", |
| 196 | 196 | }, |
| 197 | 197 | { |
| 198 | name: "t,c,bw,trim{100,100,0,true}", | |
| 198 | name: "t,c,bw{100,100,0}trim", | |
| 199 | 199 | total: 100, |
| 200 | 200 | current: 0, |
| 201 | 201 | barWidth: 100, |
| 210 | 210 | want: " [>-----------------------------------------------------------------------------------------------] ", |
| 211 | 211 | }, |
| 212 | 212 | { |
| 213 | name: "t,c,bw,trim{100,1,100,true}", | |
| 213 | name: "t,c,bw{100,1,100}trim", | |
| 214 | 214 | total: 100, |
| 215 | 215 | current: 1, |
| 216 | 216 | barWidth: 100, |
| 225 | 225 | want: " [===============================>----------------------------------------------------------------] ", |
| 226 | 226 | }, |
| 227 | 227 | { |
| 228 | name: "t,c,bw,trim{100,33,100,true}", | |
| 228 | name: "t,c,bw{100,33,100}trim", | |
| 229 | 229 | total: 100, |
| 230 | 230 | current: 33, |
| 231 | 231 | barWidth: 100, |
| 233 | 233 | want: "[===============================>------------------------------------------------------------------]", |
| 234 | 234 | }, |
| 235 | 235 | { |
| 236 | name: "t,c,bw,trim,rev{100,33,100,true,true}", | |
| 236 | name: "t,c,bw,rev{100,33,100}trim", | |
| 237 | 237 | total: 100, |
| 238 | 238 | current: 33, |
| 239 | 239 | barWidth: 100, |
| 250 | 250 | want: " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ", |
| 251 | 251 | }, |
| 252 | 252 | { |
| 253 | name: "t,c,bw,rup,trim{100,33,100,33,true}", | |
| 253 | name: "t,c,bw,rup{100,33,100,33}trim", | |
| 254 | 254 | total: 100, |
| 255 | 255 | current: 33, |
| 256 | 256 | barWidth: 100, |
| 259 | 259 | want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]", |
| 260 | 260 | }, |
| 261 | 261 | { |
| 262 | name: "t,c,bw,rup,trim,rev{100,33,100,33,true,true}", | |
| 262 | name: "t,c,bw,rup,rev{100,33,100,33}trim", | |
| 263 | 263 | total: 100, |
| 264 | 264 | current: 33, |
| 265 | 265 | barWidth: 100, |
| 277 | 277 | want: " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ", |
| 278 | 278 | }, |
| 279 | 279 | { |
| 280 | name: "t,c,bw,rup,trim{100,40,100,32,true}", | |
| 280 | name: "t,c,bw,rup{100,40,100,32}trim", | |
| 281 | 281 | total: 100, |
| 282 | 282 | current: 40, |
| 283 | 283 | barWidth: 100, |
| 293 | 293 | want: " [==============================================================================================>-] ", |
| 294 | 294 | }, |
| 295 | 295 | { |
| 296 | name: "t,c,bw,trim{100,99,100,true}", | |
| 296 | name: "t,c,bw{100,99,100}trim", | |
| 297 | 297 | total: 100, |
| 298 | 298 | current: 99, |
| 299 | 299 | barWidth: 100, |
| 308 | 308 | want: " [================================================================================================] ", |
| 309 | 309 | }, |
| 310 | 310 | { |
| 311 | name: "t,c,bw,trim{100,100,100,true}", | |
| 311 | name: "t,c,bw{100,100,100}trim", | |
| 312 | 312 | total: 100, |
| 313 | 313 | current: 100, |
| 314 | 314 | barWidth: 100, |
| 332 | 332 | } |
| 333 | 333 | } |
| 334 | 334 | tmpBuf.Reset() |
| 335 | tmpBuf.ReadFrom(s.draw(termWidth, newStatistics(s))) | |
| 335 | tmpBuf.ReadFrom(s.draw(newStatistics(termWidth, s))) | |
| 336 | 336 | by := tmpBuf.Bytes() |
| 337 | 337 | by = by[:len(by)-1] |
| 338 | 338 | |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "io/ioutil" | |
| 5 | "sync" | |
| 6 | "time" | |
| 7 | ) | |
| 8 | ||
| 9 | // ContainerOption is a function option which changes the default | |
| 10 | // behavior of progress container, if passed to mpb.New(...ContainerOption). | |
| 11 | type ContainerOption func(*pState) | |
| 12 | ||
| 13 | // WithWaitGroup provides means to have a single joint point. If | |
| 14 | // *sync.WaitGroup is provided, you can safely call just p.Wait() | |
| 15 | // without calling Wait() on provided *sync.WaitGroup. Makes sense | |
| 16 | // when there are more than one bar to render. | |
| 17 | func WithWaitGroup(wg *sync.WaitGroup) ContainerOption { | |
| 18 | return func(s *pState) { | |
| 19 | s.uwg = wg | |
| 20 | } | |
| 21 | } | |
| 22 | ||
| 23 | // WithWidth sets container width. Default is 80. Bars inherit this | |
| 24 | // width, as long as no BarWidth is applied. | |
| 25 | func WithWidth(w int) ContainerOption { | |
| 26 | return func(s *pState) { | |
| 27 | if w < 0 { | |
| 28 | return | |
| 29 | } | |
| 30 | s.width = w | |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | // WithRefreshRate overrides default 120ms refresh rate. | |
| 35 | func WithRefreshRate(d time.Duration) ContainerOption { | |
| 36 | return func(s *pState) { | |
| 37 | s.rr = d | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | // WithManualRefresh disables internal auto refresh time.Ticker. | |
| 42 | // Refresh will occur upon receive value from provided ch. | |
| 43 | func WithManualRefresh(ch <-chan time.Time) ContainerOption { | |
| 44 | return func(s *pState) { | |
| 45 | s.refreshSrc = ch | |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | // WithRenderDelay delays rendering. By default rendering starts as | |
| 50 | // soon as bar is added, with this option it's possible to delay | |
| 51 | // rendering process by keeping provided chan unclosed. In other words | |
| 52 | // rendering will start as soon as provided chan is closed. | |
| 53 | func WithRenderDelay(ch <-chan struct{}) ContainerOption { | |
| 54 | return func(s *pState) { | |
| 55 | s.renderDelay = ch | |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | // WithShutdownNotifier provided chanel will be closed, after all bars | |
| 60 | // have been rendered. | |
| 61 | func WithShutdownNotifier(ch chan struct{}) ContainerOption { | |
| 62 | return func(s *pState) { | |
| 63 | s.shutdownNotifier = ch | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | // WithOutput overrides default os.Stdout output. Setting it to nil | |
| 68 | // will effectively disable auto refresh rate and discard any output, | |
| 69 | // useful if you want to disable progress bars with little overhead. | |
| 70 | func WithOutput(w io.Writer) ContainerOption { | |
| 71 | return func(s *pState) { | |
| 72 | if w == nil { | |
| 73 | s.refreshSrc = make(chan time.Time) | |
| 74 | s.output = ioutil.Discard | |
| 75 | return | |
| 76 | } | |
| 77 | s.output = w | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | // WithDebugOutput sets debug output. | |
| 82 | func WithDebugOutput(w io.Writer) ContainerOption { | |
| 83 | if w == nil { | |
| 84 | return nil | |
| 85 | } | |
| 86 | return func(s *pState) { | |
| 87 | s.debugOut = w | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | // PopCompletedMode will pop and stop rendering completed bars. | |
| 92 | func PopCompletedMode() ContainerOption { | |
| 93 | return func(s *pState) { | |
| 94 | s.popCompleted = true | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | // ContainerOptOn returns option when condition evaluates to true. | |
| 99 | func ContainerOptOn(option ContainerOption, condition func() bool) ContainerOption { | |
| 100 | if condition() { | |
| 101 | return option | |
| 102 | } | |
| 103 | return nil | |
| 104 | } |
| 348 | 348 | id: s.idCount, |
| 349 | 349 | width: s.width, |
| 350 | 350 | debugOut: s.debugOut, |
| 351 | extender: func(r io.Reader, _ int, _ *decor.Statistics) (io.Reader, int) { | |
| 351 | extender: func(r io.Reader, _ int, _ decor.Statistics) (io.Reader, int) { | |
| 352 | 352 | return r, 0 |
| 353 | 353 | }, |
| 354 | 354 | } |
| 0 | package mpb | |
| 1 | ||
| 2 | import ( | |
| 3 | "io" | |
| 4 | "strings" | |
| 5 | "unicode/utf8" | |
| 6 | ||
| 7 | "github.com/vbauerster/mpb/v5/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 | // DefaultSpinnerStyle is a slice of strings, which makes a spinner. | |
| 21 | var DefaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 22 | ||
| 23 | type spinnerFiller struct { | |
| 24 | frames []string | |
| 25 | count uint | |
| 26 | alignment SpinnerAlignment | |
| 27 | } | |
| 28 | ||
| 29 | // NewSpinnerFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. | |
| 30 | func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller { | |
| 31 | if len(style) == 0 { | |
| 32 | style = DefaultSpinnerStyle | |
| 33 | } | |
| 34 | filler := &spinnerFiller{ | |
| 35 | frames: style, | |
| 36 | alignment: alignment, | |
| 37 | } | |
| 38 | return filler | |
| 39 | } | |
| 40 | ||
| 41 | func (s *spinnerFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { | |
| 42 | ||
| 43 | frame := s.frames[s.count%uint(len(s.frames))] | |
| 44 | frameWidth := utf8.RuneCountInString(frame) | |
| 45 | ||
| 46 | if width < frameWidth { | |
| 47 | return | |
| 48 | } | |
| 49 | ||
| 50 | switch rest := width - frameWidth; s.alignment { | |
| 51 | case SpinnerOnLeft: | |
| 52 | io.WriteString(w, frame+strings.Repeat(" ", rest)) | |
| 53 | case SpinnerOnMiddle: | |
| 54 | str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) | |
| 55 | io.WriteString(w, str) | |
| 56 | case SpinnerOnRight: | |
| 57 | io.WriteString(w, strings.Repeat(" ", rest)+frame) | |
| 58 | } | |
| 59 | s.count++ | |
| 60 | } |