diff --git a/bar.go b/bar.go index 13bda22..81d2166 100644 --- a/bar.go +++ b/bar.go @@ -10,23 +10,9 @@ "time" "unicode/utf8" + "github.com/acarl005/stripansi" "github.com/vbauerster/mpb/v5/decor" ) - -// BarFiller interface. -// Bar renders itself by calling BarFiller's Fill method. You can -// literally have any bar kind, by implementing this interface and -// passing it to the *Progress.Add(...) *Bar method. -type BarFiller interface { - Fill(w io.Writer, width int, stat *decor.Statistics) -} - -// BarFillerFunc is function type adapter to convert function into Filler. -type BarFillerFunc func(w io.Writer, width int, stat *decor.Statistics) - -func (f BarFillerFunc) Fill(w io.Writer, width int, stat *decor.Statistics) { - f(w, width, stat) -} // Bar represents a progress Bar. type Bar struct { @@ -55,7 +41,7 @@ recoveredPanic interface{} } -type extFunc func(in io.Reader, tw int, st *decor.Statistics) (out io.Reader, lines int) +type extFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int) type bState struct { baseF BarFiller @@ -335,9 +321,9 @@ } }() - st := newStatistics(s) - frame := s.draw(tw, st) - frame, b.extendedLines = s.extender(frame, tw, st) + st := newStatistics(tw, s) + frame, lines := s.extender(s.draw(st), s.width, st) + b.extendedLines = lines b.toShutdown = s.toComplete && !s.completeFlushed s.completeFlushed = s.toComplete @@ -345,9 +331,9 @@ }: case <-b.done: s := b.cacheState - st := newStatistics(s) - frame := s.draw(tw, st) - frame, b.extendedLines = s.extender(frame, tw, st) + st := newStatistics(tw, s) + frame, lines := s.extender(s.draw(st), s.width, st) + b.extendedLines = lines b.frameCh <- frame } } @@ -398,32 +384,28 @@ } } -func (s *bState) draw(termWidth int, stat *decor.Statistics) io.Reader { +func (s *bState) draw(stat decor.Statistics) io.Reader { for _, d := range s.pDecorators { - s.bufP.WriteString(d.Decor(stat)) + str := d.Decor(stat) + stat.OccupiedWidth += utf8.RuneCountInString(stripansi.Strip(str)) + s.bufP.WriteString(str) } for _, d := range s.aDecorators { - s.bufA.WriteString(d.Decor(stat)) + str := d.Decor(stat) + stat.OccupiedWidth += utf8.RuneCountInString(stripansi.Strip(str)) + s.bufA.WriteString(str) } s.bufA.WriteByte('\n') - prependCount := utf8.RuneCount(s.bufP.Bytes()) - appendCount := utf8.RuneCount(s.bufA.Bytes()) - 1 - - if fitWidth := s.width; termWidth > 1 { - if !s.trimSpace { - // reserve space for edge spaces - termWidth -= 2 - s.bufB.WriteByte(' ') - defer s.bufB.WriteByte(' ') - } - if prependCount+s.width+appendCount > termWidth { - fitWidth = termWidth - prependCount - appendCount - } - s.filler.Fill(s.bufB, fitWidth, stat) - } + if !s.trimSpace && stat.TermWidth >= 2 { + defer s.bufB.WriteByte(' ') + s.bufB.WriteByte(' ') + stat.OccupiedWidth += 2 + } + + s.filler.Fill(s.bufB, s.width, stat) return io.MultiReader(s.bufP, s.bufB, s.bufA) } @@ -450,12 +432,13 @@ return table } -func newStatistics(s *bState) *decor.Statistics { - return &decor.Statistics{ +func newStatistics(tw int, s *bState) decor.Statistics { + return decor.Statistics{ ID: s.id, Completed: s.completeFlushed, Total: s.total, Current: s.current, + TermWidth: tw, } } diff --git a/bar_filler.go b/bar_filler.go index 33dbf19..07148bf 100644 --- a/bar_filler.go +++ b/bar_filler.go @@ -2,137 +2,29 @@ import ( "io" - "unicode/utf8" "github.com/vbauerster/mpb/v5/decor" - "github.com/vbauerster/mpb/v5/internal" ) -const ( - rLeft = iota - rFill - rTip - rEmpty - rRight - rRevTip - rRefill -) - -// DefaultBarStyle is a string containing 7 runes. -// Each rune is a building block of a progress bar. +// BarFiller interface. +// Bar (without decorators) renders itself by calling BarFiller's Fill method. // -// '1st rune' stands for left boundary rune +// `reqWidth` is requested width, which is set via: +// func WithWidth(width int) ContainerOption +// func BarWidth(width int) BarOption // -// '2nd rune' stands for fill rune +// Default implementations can be obtained via: // -// '3rd rune' stands for tip rune +// func NewBarFiller(style string, reverse bool) BarFiller +// func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller // -// '4th rune' stands for empty rune -// -// '5th rune' stands for right boundary rune -// -// '6th rune' stands for reverse tip rune -// -// '7th rune' stands for refill rune -// -const DefaultBarStyle string = "[=>-]<+" - -type barFiller struct { - format [][]byte - tip []byte - refill int64 - reverse bool - flush func(w io.Writer, bb [][]byte) +type BarFiller interface { + Fill(w io.Writer, reqWidth int, stat decor.Statistics) } -// NewBarFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. -func NewBarFiller(style string, reverse bool) BarFiller { - if style == "" { - style = DefaultBarStyle - } - bf := &barFiller{ - format: make([][]byte, utf8.RuneCountInString(style)), - reverse: reverse, - } - bf.SetStyle(style) - return bf +// BarFillerFunc is function type adapter to convert function into BarFiller. +type BarFillerFunc func(w io.Writer, reqWidth int, stat decor.Statistics) + +func (f BarFillerFunc) Fill(w io.Writer, reqWidth int, stat decor.Statistics) { + f(w, reqWidth, stat) } - -func (s *barFiller) SetStyle(style string) { - if !utf8.ValidString(style) { - return - } - src := make([][]byte, 0, utf8.RuneCountInString(style)) - for _, r := range style { - src = append(src, []byte(string(r))) - } - copy(s.format, src) - s.SetReverse(s.reverse) -} - -func (s *barFiller) SetReverse(reverse bool) { - if reverse { - s.tip = s.format[rRevTip] - s.flush = reverseFlush - } else { - s.tip = s.format[rTip] - s.flush = regularFlush - } - s.reverse = reverse -} - -func (s *barFiller) SetRefill(amount int64) { - s.refill = amount -} - -func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { - // don't count rLeft and rRight as progress - width -= 2 - if width < 2 { - return - } - w.Write(s.format[rLeft]) - defer w.Write(s.format[rRight]) - - bb := make([][]byte, width) - - cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width)) - - for i := 0; i < cwidth; i++ { - bb[i] = s.format[rFill] - } - - if s.refill > 0 { - var rwidth int - if s.refill > stat.Current { - rwidth = cwidth - } else { - rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width)) - } - for i := 0; i < rwidth; i++ { - bb[i] = s.format[rRefill] - } - } - - if cwidth > 0 && cwidth < width { - bb[cwidth-1] = s.tip - } - - for i := cwidth; i < width; i++ { - bb[i] = s.format[rEmpty] - } - - s.flush(w, bb) -} - -func regularFlush(w io.Writer, bb [][]byte) { - for i := 0; i < len(bb); i++ { - w.Write(bb[i]) - } -} - -func reverseFlush(w io.Writer, bb [][]byte) { - for i := len(bb) - 1; i >= 0; i-- { - w.Write(bb[i]) - } -} diff --git a/bar_filler_bar.go b/bar_filler_bar.go new file mode 100644 index 0000000..b473966 --- /dev/null +++ b/bar_filler_bar.go @@ -0,0 +1,142 @@ +package mpb + +import ( + "io" + "unicode/utf8" + + "github.com/vbauerster/mpb/v5/decor" + "github.com/vbauerster/mpb/v5/internal" +) + +const ( + rLeft = iota + rFill + rTip + rEmpty + rRight + rRevTip + rRefill +) + +// DefaultBarStyle is a string containing 7 runes. +// Each rune is a building block of a progress bar. +// +// '1st rune' stands for left boundary rune +// +// '2nd rune' stands for fill rune +// +// '3rd rune' stands for tip rune +// +// '4th rune' stands for empty rune +// +// '5th rune' stands for right boundary rune +// +// '6th rune' stands for reverse tip rune +// +// '7th rune' stands for refill rune +// +const DefaultBarStyle string = "[=>-]<+" + +type barFiller struct { + format [][]byte + tip []byte + refill int64 + reverse bool + flush func(w io.Writer, bb [][]byte) +} + +// NewBarFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. +func NewBarFiller(style string, reverse bool) BarFiller { + if style == "" { + style = DefaultBarStyle + } + bf := &barFiller{ + format: make([][]byte, utf8.RuneCountInString(style)), + reverse: reverse, + } + bf.SetStyle(style) + return bf +} + +func (s *barFiller) SetStyle(style string) { + if !utf8.ValidString(style) { + return + } + src := make([][]byte, 0, utf8.RuneCountInString(style)) + for _, r := range style { + src = append(src, []byte(string(r))) + } + copy(s.format, src) + s.SetReverse(s.reverse) +} + +func (s *barFiller) SetReverse(reverse bool) { + if reverse { + s.tip = s.format[rRevTip] + s.flush = reverseFlush + } else { + s.tip = s.format[rTip] + s.flush = regularFlush + } + s.reverse = reverse +} + +func (s *barFiller) SetRefill(amount int64) { + s.refill = amount +} + +func (s *barFiller) Fill(w io.Writer, width int, stat decor.Statistics) { + // auto shrink + if stat.OccupiedWidth+width > stat.TermWidth { + width = stat.TermWidth - stat.OccupiedWidth + } + // don't count rLeft and rRight as progress + width -= 2 + if width < 2 { + return + } + w.Write(s.format[rLeft]) + defer w.Write(s.format[rRight]) + + bb := make([][]byte, width) + + cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width)) + + for i := 0; i < cwidth; i++ { + bb[i] = s.format[rFill] + } + + if s.refill > 0 { + var rwidth int + if s.refill > stat.Current { + rwidth = cwidth + } else { + rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width)) + } + for i := 0; i < rwidth; i++ { + bb[i] = s.format[rRefill] + } + } + + if cwidth > 0 && cwidth < width { + bb[cwidth-1] = s.tip + } + + for i := cwidth; i < width; i++ { + bb[i] = s.format[rEmpty] + } + + s.flush(w, bb) +} + +func regularFlush(w io.Writer, bb [][]byte) { + for i := 0; i < len(bb); i++ { + w.Write(bb[i]) + } +} + +func reverseFlush(w io.Writer, bb [][]byte) { + for i := len(bb) - 1; i >= 0; i-- { + w.Write(bb[i]) + } +} diff --git a/bar_filler_spinner.go b/bar_filler_spinner.go new file mode 100644 index 0000000..e90b09d --- /dev/null +++ b/bar_filler_spinner.go @@ -0,0 +1,65 @@ +package mpb + +import ( + "io" + "strings" + "unicode/utf8" + + "github.com/vbauerster/mpb/v5/decor" +) + +// SpinnerAlignment enum. +type SpinnerAlignment int + +// SpinnerAlignment kinds. +const ( + SpinnerOnLeft SpinnerAlignment = iota + SpinnerOnMiddle + SpinnerOnRight +) + +// DefaultSpinnerStyle is a slice of strings, which makes a spinner. +var DefaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} + +type spinnerFiller struct { + frames []string + count uint + alignment SpinnerAlignment +} + +// NewSpinnerFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. +func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller { + if len(style) == 0 { + style = DefaultSpinnerStyle + } + filler := &spinnerFiller{ + frames: style, + alignment: alignment, + } + return filler +} + +func (s *spinnerFiller) Fill(w io.Writer, width int, stat decor.Statistics) { + // auto shrink + if stat.OccupiedWidth+width > stat.TermWidth { + width = stat.TermWidth - stat.OccupiedWidth + } + + frame := s.frames[s.count%uint(len(s.frames))] + frameWidth := utf8.RuneCountInString(frame) + + if width < frameWidth { + return + } + + switch rest := width - frameWidth; s.alignment { + case SpinnerOnLeft: + io.WriteString(w, frame+strings.Repeat(" ", rest)) + case SpinnerOnMiddle: + str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) + io.WriteString(w, str) + case SpinnerOnRight: + io.WriteString(w, strings.Repeat(" ", rest)+frame) + } + s.count++ +} diff --git a/bar_option.go b/bar_option.go index 76f2050..02589fc 100644 --- a/bar_option.go +++ b/bar_option.go @@ -83,7 +83,7 @@ } func makeBarFillerOnComplete(filler BarFiller, message string) BarFiller { - return BarFillerFunc(func(w io.Writer, width int, st *decor.Statistics) { + return BarFillerFunc(func(w io.Writer, width int, st decor.Statistics) { if st.Completed { io.WriteString(w, message) } else { @@ -103,21 +103,20 @@ // BarExtender is an option to extend bar to the next new line, with // arbitrary output. -func BarExtender(extender BarFiller) BarOption { - if extender == nil { - return nil - } - return func(s *bState) { - s.extender = makeExtFunc(extender) - } -} - -func makeExtFunc(extender BarFiller) extFunc { +func BarExtender(filler BarFiller) BarOption { + if filler == nil { + return nil + } + return func(s *bState) { + s.extender = makeExtFunc(filler) + } +} + +func makeExtFunc(filler BarFiller) extFunc { buf := new(bytes.Buffer) - nl := []byte("\n") - return func(r io.Reader, tw int, st *decor.Statistics) (io.Reader, int) { - extender.Fill(buf, tw, st) - return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), nl) + return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) { + filler.Fill(buf, reqWidth, st) + return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n")) } } diff --git a/bar_test.go b/bar_test.go index b442195..21de834 100644 --- a/bar_test.go +++ b/bar_test.go @@ -177,7 +177,7 @@ var pCount uint32 bar := p.AddBar(int64(total), PrependDecorators(panicDecorator(panicMsg, - func(st *decor.Statistics) bool { + func(st decor.Statistics) bool { if st.Current >= 42 { atomic.AddUint32(&pCount, 1) return true @@ -213,7 +213,7 @@ var pCount uint32 bar := p.AddBar(int64(total), PrependDecorators(panicDecorator(panicMsg, - func(st *decor.Statistics) bool { + func(st decor.Statistics) bool { if st.Completed { atomic.AddUint32(&pCount, 1) return true @@ -240,7 +240,7 @@ } } -func panicDecorator(panicMsg string, cond func(*decor.Statistics) bool) decor.Decorator { +func panicDecorator(panicMsg string, cond func(decor.Statistics) bool) decor.Decorator { d := &decorator{ panicMsg: panicMsg, cond: cond, @@ -252,10 +252,10 @@ type decorator struct { decor.WC panicMsg string - cond func(*decor.Statistics) bool -} - -func (d *decorator) Decor(st *decor.Statistics) string { + cond func(decor.Statistics) bool +} + +func (d *decorator) Decor(st decor.Statistics) string { if d.cond(st) { panic(d.panicMsg) } diff --git a/container_option.go b/container_option.go new file mode 100644 index 0000000..2f96bb4 --- /dev/null +++ b/container_option.go @@ -0,0 +1,105 @@ +package mpb + +import ( + "io" + "io/ioutil" + "sync" + "time" +) + +// ContainerOption is a function option which changes the default +// behavior of progress container, if passed to mpb.New(...ContainerOption). +type ContainerOption func(*pState) + +// WithWaitGroup provides means to have a single joint point. If +// *sync.WaitGroup is provided, you can safely call just p.Wait() +// without calling Wait() on provided *sync.WaitGroup. Makes sense +// when there are more than one bar to render. +func WithWaitGroup(wg *sync.WaitGroup) ContainerOption { + return func(s *pState) { + s.uwg = wg + } +} + +// WithWidth sets container width. Default is 80. Bars inherit this +// width, as long as no BarWidth is applied. +func WithWidth(width int) ContainerOption { + return func(s *pState) { + if width < 0 { + return + } + s.width = width + } +} + +// WithRefreshRate overrides default 120ms refresh rate. +func WithRefreshRate(d time.Duration) ContainerOption { + return func(s *pState) { + s.rr = d + } +} + +// WithManualRefresh disables internal auto refresh time.Ticker. +// Refresh will occur upon receive value from provided ch. +func WithManualRefresh(ch <-chan time.Time) ContainerOption { + return func(s *pState) { + s.refreshSrc = ch + } +} + +// WithRenderDelay delays rendering. By default rendering starts as +// soon as bar is added, with this option it's possible to delay +// rendering process by keeping provided chan unclosed. In other words +// rendering will start as soon as provided chan is closed. +func WithRenderDelay(ch <-chan struct{}) ContainerOption { + return func(s *pState) { + s.renderDelay = ch + } +} + +// WithShutdownNotifier provided chanel will be closed, after all bars +// have been rendered. +func WithShutdownNotifier(ch chan struct{}) ContainerOption { + return func(s *pState) { + s.shutdownNotifier = ch + } +} + +// WithOutput overrides default os.Stdout output. Setting it to nil +// will effectively disable auto refresh rate and discard any output, +// useful if you want to disable progress bars with little overhead. +func WithOutput(w io.Writer) ContainerOption { + return func(s *pState) { + if w == nil { + s.refreshSrc = make(chan time.Time) + s.output = ioutil.Discard + return + } + s.output = w + } +} + +// WithDebugOutput sets debug output. +func WithDebugOutput(w io.Writer) ContainerOption { + if w == nil { + return nil + } + return func(s *pState) { + s.debugOut = w + } +} + +// PopCompletedMode will pop and stop rendering completed bars. +func PopCompletedMode() ContainerOption { + return func(s *pState) { + s.popCompleted = true + } +} + +// ContainerOptOn returns option when condition evaluates to true. +func ContainerOptOn(option ContainerOption, condition func() bool) ContainerOption { + if condition() { + return option + } + return nil +} diff --git a/decor/any.go b/decor/any.go index bf9cf51..39518f5 100644 --- a/decor/any.go +++ b/decor/any.go @@ -1,21 +1,21 @@ package decor // Any decorator displays text, that can be changed during decorator's -// lifetime via provided func call back. +// lifetime via provided DecorFunc. // -// `f` call back which provides string to display +// `fn` DecorFunc callback // // `wcc` optional WC config // -func Any(f func(*Statistics) string, wcc ...WC) Decorator { - return &any{initWC(wcc...), f} +func Any(fn DecorFunc, wcc ...WC) Decorator { + return &any{initWC(wcc...), fn} } type any struct { WC - f func(*Statistics) string + fn DecorFunc } -func (d *any) Decor(s *Statistics) string { - return d.FormatMsg(d.f(s)) +func (d *any) Decor(s Statistics) string { + return d.FormatMsg(d.fn(s)) } diff --git a/decor/counters.go b/decor/counters.go index 297bf93..010ec37 100644 --- a/decor/counters.go +++ b/decor/counters.go @@ -46,21 +46,21 @@ return Any(chooseSizeProducer(unit, pairFmt), wcc...) } -func chooseSizeProducer(unit int, format string) func(*Statistics) string { +func chooseSizeProducer(unit int, format string) DecorFunc { if format == "" { format = "%d / %d" } switch unit { case UnitKiB: - return func(s *Statistics) string { + return func(s Statistics) string { return fmt.Sprintf(format, SizeB1024(s.Current), SizeB1024(s.Total)) } case UnitKB: - return func(s *Statistics) string { + return func(s Statistics) string { return fmt.Sprintf(format, SizeB1000(s.Current), SizeB1000(s.Total)) } default: - return func(s *Statistics) string { + return func(s Statistics) string { return fmt.Sprintf(format, s.Current, s.Total) } } diff --git a/decor/decorator.go b/decor/decorator.go index 5bca63d..b896af8 100644 --- a/decor/decorator.go +++ b/decor/decorator.go @@ -47,21 +47,31 @@ // Statistics consists of progress related statistics, that Decorator // may need. type Statistics struct { - ID int - Completed bool - Total int64 - Current int64 + ID int + Completed bool + Total int64 + Current int64 + TermWidth int + OccupiedWidth int } // Decorator interface. -// Implementors should embed WC type, that way only single method -// Decor(*Statistics) needs to be implemented, the rest will be handled -// by WC type. +// Most of the time there is no need to implement this interface +// manually, as decor package already provides a wide range of decorators +// which implement this interface. If however built-in decorators don't +// meet your needs, you're free to implement your own one by implementing +// this particular interface. The easy way to go is to convert a +// `DecorFunc` into a `Decorator` interface by using provided +// `func Any`(DecorFunc, ...WC) Decorator`. type Decorator interface { Configurator Synchronizer - Decor(*Statistics) string + Decor(Statistics) string } + +// DecorFunc func type. +// To be used with `func Any`(DecorFunc, ...WC) Decorator`. +type DecorFunc func(Statistics) string // Synchronizer interface. // All decorators implement this interface implicitly. Its Sync diff --git a/decor/elapsed.go b/decor/elapsed.go index c9999a3..e389f15 100644 --- a/decor/elapsed.go +++ b/decor/elapsed.go @@ -25,11 +25,11 @@ func NewElapsed(style TimeStyle, startTime time.Time, wcc ...WC) Decorator { var msg string producer := chooseTimeProducer(style) - f := func(s *Statistics) string { + fn := func(s Statistics) string { if !s.Completed { msg = producer(time.Since(startTime)) } return msg } - return Any(f, wcc...) + return Any(fn, wcc...) } diff --git a/decor/eta.go b/decor/eta.go index 6cb27a2..d03caa7 100644 --- a/decor/eta.go +++ b/decor/eta.go @@ -63,7 +63,7 @@ producer func(time.Duration) string } -func (d *movingAverageETA) Decor(s *Statistics) string { +func (d *movingAverageETA) Decor(s Statistics) string { v := math.Round(d.average.Value()) remaining := time.Duration((s.Total - s.Current) * int64(v)) if d.normalizer != nil { @@ -117,7 +117,7 @@ producer func(time.Duration) string } -func (d *averageETA) Decor(s *Statistics) string { +func (d *averageETA) Decor(s Statistics) string { var remaining time.Duration if s.Current != 0 { durPerItem := float64(time.Since(d.startTime)) / float64(s.Current) diff --git a/decor/merge.go b/decor/merge.go index 520f13a..d6cc004 100644 --- a/decor/merge.go +++ b/decor/merge.go @@ -4,6 +4,8 @@ "fmt" "strings" "unicode/utf8" + + "github.com/acarl005/stripansi" ) // Merge wraps its decorator argument with intention to sync width @@ -64,9 +66,9 @@ return d.Decorator } -func (d *mergeDecorator) Decor(s *Statistics) string { +func (d *mergeDecorator) Decor(s Statistics) string { msg := d.Decorator.Decor(s) - msgLen := utf8.RuneCountInString(msg) + msgLen := utf8.RuneCountInString(stripansi.Strip(msg)) if (d.wc.C & DextraSpace) != 0 { msgLen++ } @@ -101,6 +103,6 @@ WC } -func (d *placeHolderDecorator) Decor(*Statistics) string { +func (d *placeHolderDecorator) Decor(Statistics) string { return "" } diff --git a/decor/name.go b/decor/name.go index a7d477e..3af3112 100644 --- a/decor/name.go +++ b/decor/name.go @@ -8,5 +8,5 @@ // `wcc` optional WC config // func Name(str string, wcc ...WC) Decorator { - return Any(func(*Statistics) string { return str }, wcc...) + return Any(func(Statistics) string { return str }, wcc...) } diff --git a/decor/on_complete.go b/decor/on_complete.go index 0a1526b..f46b19a 100644 --- a/decor/on_complete.go +++ b/decor/on_complete.go @@ -24,7 +24,7 @@ msg string } -func (d *onCompleteWrapper) Decor(s *Statistics) string { +func (d *onCompleteWrapper) Decor(s Statistics) string { if s.Completed { wc := d.GetConf() return wc.FormatMsg(d.msg) diff --git a/decor/percentage.go b/decor/percentage.go index 65ca7d3..d6314a6 100644 --- a/decor/percentage.go +++ b/decor/percentage.go @@ -50,7 +50,7 @@ if format == "" { format = "% d" } - f := func(s *Statistics) string { + f := func(s Statistics) string { p := internal.Percentage(s.Total, s.Current, 100) return fmt.Sprintf(format, percentageType(p)) } diff --git a/decor/speed.go b/decor/speed.go index 8a48e3f..634edab 100644 --- a/decor/speed.go +++ b/decor/speed.go @@ -78,7 +78,7 @@ msg string } -func (d *movingAverageSpeed) Decor(s *Statistics) string { +func (d *movingAverageSpeed) Decor(s Statistics) string { if !s.Completed { var speed float64 if v := d.average.Value(); v > 0 { @@ -140,7 +140,7 @@ msg string } -func (d *averageSpeed) Decor(s *Statistics) string { +func (d *averageSpeed) Decor(s Statistics) string { if !s.Completed { speed := float64(s.Current) / float64(time.Since(d.startTime)) d.msg = d.producer(speed * 1e9) diff --git a/decor/speed_test.go b/decor/speed_test.go index 4f16aea..7f7d09d 100644 --- a/decor/speed_test.go +++ b/decor/speed_test.go @@ -122,7 +122,7 @@ for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) - stat := &Statistics{ + stat := Statistics{ Current: tc.current, } res := decor.Decor(stat) @@ -250,7 +250,7 @@ for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { decor := NewAverageSpeed(tc.unit, tc.fmt, time.Now().Add(-tc.elapsed)) - stat := &Statistics{ + stat := Statistics{ Current: tc.current, } res := decor.Decor(stat) diff --git a/decor/spinner.go b/decor/spinner.go index abfb2f7..6871639 100644 --- a/decor/spinner.go +++ b/decor/spinner.go @@ -12,7 +12,7 @@ frames = defaultSpinnerStyle } var count uint - f := func(s *Statistics) string { + f := func(s Statistics) string { frame := frames[count%uint(len(frames))] count++ return frame diff --git a/decorators_test.go b/decorators_test.go index 4bc475a..ca58c73 100644 --- a/decorators_test.go +++ b/decorators_test.go @@ -32,7 +32,7 @@ } for _, test := range tests { - got := test.decorator.Decor(new(decor.Statistics)) + got := test.decorator.Decor(decor.Statistics{}) if got != test.want { t.Errorf("Want: %q, Got: %q\n", test.want, got) } @@ -40,7 +40,7 @@ } type step struct { - stat *decor.Statistics + stat decor.Statistics decorator decor.Decorator want string } @@ -50,36 +50,36 @@ testCases := [][]step{ { { - &decor.Statistics{Total: 100, Current: 8}, + decor.Statistics{Total: 100, Current: 8}, decor.Percentage(decor.WCSyncWidth), "8 %", }, { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidth), "9 %", }, }, { { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidth), " 9 %", }, { - &decor.Statistics{Total: 100, Current: 10}, + decor.Statistics{Total: 100, Current: 10}, decor.Percentage(decor.WCSyncWidth), "10 %", }, }, { { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidth), " 9 %", }, { - &decor.Statistics{Total: 100, Current: 100}, + decor.Statistics{Total: 100, Current: 100}, decor.Percentage(decor.WCSyncWidth), "100 %", }, @@ -94,36 +94,36 @@ testCases := [][]step{ { { - &decor.Statistics{Total: 100, Current: 8}, + decor.Statistics{Total: 100, Current: 8}, decor.Percentage(decor.WCSyncWidthR), "8 %", }, { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidthR), "9 %", }, }, { { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidthR), "9 % ", }, { - &decor.Statistics{Total: 100, Current: 10}, + decor.Statistics{Total: 100, Current: 10}, decor.Percentage(decor.WCSyncWidthR), "10 %", }, }, { { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncWidthR), "9 % ", }, { - &decor.Statistics{Total: 100, Current: 100}, + decor.Statistics{Total: 100, Current: 100}, decor.Percentage(decor.WCSyncWidthR), "100 %", }, @@ -138,36 +138,36 @@ testCases := [][]step{ { { - &decor.Statistics{Total: 100, Current: 8}, + decor.Statistics{Total: 100, Current: 8}, decor.Percentage(decor.WCSyncSpace), " 8 %", }, { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncSpace), " 9 %", }, }, { { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncSpace), " 9 %", }, { - &decor.Statistics{Total: 100, Current: 10}, + decor.Statistics{Total: 100, Current: 10}, decor.Percentage(decor.WCSyncSpace), " 10 %", }, }, { { - &decor.Statistics{Total: 100, Current: 9}, + decor.Statistics{Total: 100, Current: 9}, decor.Percentage(decor.WCSyncSpace), " 9 %", }, { - &decor.Statistics{Total: 100, Current: 100}, + decor.Statistics{Total: 100, Current: 100}, decor.Percentage(decor.WCSyncSpace), " 100 %", }, diff --git a/draw_test.go b/draw_test.go index cc59513..de3e2a9 100644 --- a/draw_test.go +++ b/draw_test.go @@ -26,7 +26,7 @@ want: "", }, { - name: "t,c,bw{60,20,80}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -43,7 +43,7 @@ want: "", }, { - name: "t,c,bw{60,20,80}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -60,7 +60,7 @@ want: " ", }, { - name: "t,c,bw,trim{60,20,80,true}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -77,7 +77,7 @@ want: " ", }, { - name: "t,c,bw,trim{60,20,80,true}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -94,7 +94,7 @@ want: " ", }, { - name: "t,c,bw,trim{60,20,80,true}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -111,7 +111,7 @@ want: " ", }, { - name: "t,c,bw,trim{60,20,80,true}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -128,7 +128,7 @@ want: " [>-] ", }, { - name: "t,c,bw,trim{60,20,80,true}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -145,7 +145,7 @@ want: " [>--] ", }, { - name: "t,c,bw,trim{60,20,80,true}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -162,7 +162,7 @@ want: " [>---] ", }, { - name: "t,c,bw,trim{60,20,80,true}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -179,7 +179,7 @@ want: " [========================>---------------------------------------------------] ", }, { - name: "t,c,bw,trim{60,20,80,true}", + name: "t,c,bw{60,20,80}trim", total: 60, current: 20, barWidth: 80, @@ -196,7 +196,7 @@ want: " [------------------------------------------------------------------------------------------------] ", }, { - name: "t,c,bw,trim{100,100,0,true}", + name: "t,c,bw{100,100,0}trim", total: 100, current: 0, barWidth: 100, @@ -211,7 +211,7 @@ want: " [>-----------------------------------------------------------------------------------------------] ", }, { - name: "t,c,bw,trim{100,1,100,true}", + name: "t,c,bw{100,1,100}trim", total: 100, current: 1, barWidth: 100, @@ -226,7 +226,7 @@ want: " [===============================>----------------------------------------------------------------] ", }, { - name: "t,c,bw,trim{100,33,100,true}", + name: "t,c,bw{100,33,100}trim", total: 100, current: 33, barWidth: 100, @@ -234,7 +234,7 @@ want: "[===============================>------------------------------------------------------------------]", }, { - name: "t,c,bw,trim,rev{100,33,100,true,true}", + name: "t,c,bw,rev{100,33,100}trim", total: 100, current: 33, barWidth: 100, @@ -251,7 +251,7 @@ want: " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ", }, { - name: "t,c,bw,rup,trim{100,33,100,33,true}", + name: "t,c,bw,rup{100,33,100,33}trim", total: 100, current: 33, barWidth: 100, @@ -260,7 +260,7 @@ want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]", }, { - name: "t,c,bw,rup,trim,rev{100,33,100,33,true,true}", + name: "t,c,bw,rup,rev{100,33,100,33}trim", total: 100, current: 33, barWidth: 100, @@ -278,7 +278,7 @@ want: " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ", }, { - name: "t,c,bw,rup,trim{100,40,100,32,true}", + name: "t,c,bw,rup{100,40,100,32}trim", total: 100, current: 40, barWidth: 100, @@ -294,7 +294,7 @@ want: " [==============================================================================================>-] ", }, { - name: "t,c,bw,trim{100,99,100,true}", + name: "t,c,bw{100,99,100}trim", total: 100, current: 99, barWidth: 100, @@ -309,7 +309,7 @@ want: " [================================================================================================] ", }, { - name: "t,c,bw,trim{100,100,100,true}", + name: "t,c,bw{100,100,100}trim", total: 100, current: 100, barWidth: 100, @@ -333,7 +333,7 @@ } } tmpBuf.Reset() - tmpBuf.ReadFrom(s.draw(termWidth, newStatistics(s))) + tmpBuf.ReadFrom(s.draw(newStatistics(termWidth, s))) by := tmpBuf.Bytes() by = by[:len(by)-1] diff --git a/options.go b/options.go deleted file mode 100644 index 0488702..0000000 --- a/options.go +++ /dev/null @@ -1,105 +0,0 @@ -package mpb - -import ( - "io" - "io/ioutil" - "sync" - "time" -) - -// ContainerOption is a function option which changes the default -// behavior of progress container, if passed to mpb.New(...ContainerOption). -type ContainerOption func(*pState) - -// WithWaitGroup provides means to have a single joint point. If -// *sync.WaitGroup is provided, you can safely call just p.Wait() -// without calling Wait() on provided *sync.WaitGroup. Makes sense -// when there are more than one bar to render. -func WithWaitGroup(wg *sync.WaitGroup) ContainerOption { - return func(s *pState) { - s.uwg = wg - } -} - -// WithWidth sets container width. Default is 80. Bars inherit this -// width, as long as no BarWidth is applied. -func WithWidth(w int) ContainerOption { - return func(s *pState) { - if w < 0 { - return - } - s.width = w - } -} - -// WithRefreshRate overrides default 120ms refresh rate. -func WithRefreshRate(d time.Duration) ContainerOption { - return func(s *pState) { - s.rr = d - } -} - -// WithManualRefresh disables internal auto refresh time.Ticker. -// Refresh will occur upon receive value from provided ch. -func WithManualRefresh(ch <-chan time.Time) ContainerOption { - return func(s *pState) { - s.refreshSrc = ch - } -} - -// WithRenderDelay delays rendering. By default rendering starts as -// soon as bar is added, with this option it's possible to delay -// rendering process by keeping provided chan unclosed. In other words -// rendering will start as soon as provided chan is closed. -func WithRenderDelay(ch <-chan struct{}) ContainerOption { - return func(s *pState) { - s.renderDelay = ch - } -} - -// WithShutdownNotifier provided chanel will be closed, after all bars -// have been rendered. -func WithShutdownNotifier(ch chan struct{}) ContainerOption { - return func(s *pState) { - s.shutdownNotifier = ch - } -} - -// WithOutput overrides default os.Stdout output. Setting it to nil -// will effectively disable auto refresh rate and discard any output, -// useful if you want to disable progress bars with little overhead. -func WithOutput(w io.Writer) ContainerOption { - return func(s *pState) { - if w == nil { - s.refreshSrc = make(chan time.Time) - s.output = ioutil.Discard - return - } - s.output = w - } -} - -// WithDebugOutput sets debug output. -func WithDebugOutput(w io.Writer) ContainerOption { - if w == nil { - return nil - } - return func(s *pState) { - s.debugOut = w - } -} - -// PopCompletedMode will pop and stop rendering completed bars. -func PopCompletedMode() ContainerOption { - return func(s *pState) { - s.popCompleted = true - } -} - -// ContainerOptOn returns option when condition evaluates to true. -func ContainerOptOn(option ContainerOption, condition func() bool) ContainerOption { - if condition() { - return option - } - return nil -} diff --git a/progress.go b/progress.go index a366b92..70c6fb4 100644 --- a/progress.go +++ b/progress.go @@ -349,7 +349,7 @@ id: s.idCount, width: s.width, debugOut: s.debugOut, - extender: func(r io.Reader, _ int, _ *decor.Statistics) (io.Reader, int) { + extender: func(r io.Reader, _ int, _ decor.Statistics) (io.Reader, int) { return r, 0 }, } diff --git a/spinner_filler.go b/spinner_filler.go deleted file mode 100644 index 517725f..0000000 --- a/spinner_filler.go +++ /dev/null @@ -1,61 +0,0 @@ -package mpb - -import ( - "io" - "strings" - "unicode/utf8" - - "github.com/vbauerster/mpb/v5/decor" -) - -// SpinnerAlignment enum. -type SpinnerAlignment int - -// SpinnerAlignment kinds. -const ( - SpinnerOnLeft SpinnerAlignment = iota - SpinnerOnMiddle - SpinnerOnRight -) - -// DefaultSpinnerStyle is a slice of strings, which makes a spinner. -var DefaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} - -type spinnerFiller struct { - frames []string - count uint - alignment SpinnerAlignment -} - -// NewSpinnerFiller constucts mpb.BarFiller, to be used with *Progress.Add(...) *Bar method. -func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller { - if len(style) == 0 { - style = DefaultSpinnerStyle - } - filler := &spinnerFiller{ - frames: style, - alignment: alignment, - } - return filler -} - -func (s *spinnerFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { - - frame := s.frames[s.count%uint(len(s.frames))] - frameWidth := utf8.RuneCountInString(frame) - - if width < frameWidth { - return - } - - switch rest := width - frameWidth; s.alignment { - case SpinnerOnLeft: - io.WriteString(w, frame+strings.Repeat(" ", rest)) - case SpinnerOnMiddle: - str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2) - io.WriteString(w, str) - case SpinnerOnRight: - io.WriteString(w, strings.Repeat(" ", rest)+frame) - } - s.count++ -}