diff --git a/bar.go b/bar.go index e97381b..8a1a555 100644 --- a/bar.go +++ b/bar.go @@ -9,6 +9,7 @@ "time" "unicode/utf8" + "github.com/VividCortex/ewma" "github.com/vbauerster/mpb/decor" ) @@ -22,7 +23,6 @@ const ( formatLen = 5 - etaAlpha = 0.12 ) type barRunes [formatLen]rune @@ -36,6 +36,7 @@ cacheState *bState operateState chan func(*bState) frameReaderCh chan io.Reader + startBlockCh <-chan time.Time // done is closed by Bar's goroutine, after cacheState is written done chan struct{} @@ -48,7 +49,6 @@ id int width int runes barRunes - etaAlpha float64 total int64 current int64 totalAutoIncrTrigger int64 @@ -63,17 +63,18 @@ startTime time.Time blockStartTime time.Time timeElapsed time.Duration - timePerItemEstimate time.Duration - timeRemaining time.Duration - aDecorators []decor.DecoratorFunc - pDecorators []decor.DecoratorFunc + aDecorators []decor.Decorator + pDecorators []decor.Decorator refill *refill bufP, bufB, bufA *bytes.Buffer panicMsg string + ewmAverage ewma.MovingAverage + // following options are assigned to the *Bar - priority int - runningBar *Bar + priority int + runningBar *Bar + startBlockCh chan time.Time } refill struct { char rune @@ -96,7 +97,6 @@ id: id, priority: id, total: total, - etaAlpha: etaAlpha, dynamic: dynamic, } @@ -113,12 +113,15 @@ b := &Bar{ priority: s.priority, runningBar: s.runningBar, + startBlockCh: s.startBlockCh, operateState: make(chan func(*bState)), frameReaderCh: make(chan io.Reader, 1), done: make(chan struct{}), shutdown: make(chan struct{}), } + s.startBlockCh = nil + if b.runningBar != nil { b.priority = b.runningBar.priority } @@ -144,8 +147,15 @@ } // ProxyReader wrapper for io operations, like io.Copy -func (b *Bar) ProxyReader(r io.Reader) *Reader { - return &Reader{r, b} +func (b *Bar) ProxyReader(r io.Reader, startBlock ...chan<- time.Time) *Reader { + proxyReader := &Reader{ + Reader: r, + bar: b, + } + if len(startBlock) > 0 { + proxyReader.startBlockCh = startBlock[0] + } + return proxyReader } // Increment is a shorthand for b.IncrBy(1) @@ -223,30 +233,11 @@ // SetTotal sets total dynamically. The final param indicates the very last set, // in other words you should set it to true when total is determined. func (b *Bar) SetTotal(total int64, final bool) { - select { - case b.operateState <- func(s *bState) { + b.operateState <- func(s *bState) { if total != 0 { s.total = total } s.dynamic = !final - }: - case <-b.done: - } -} - -// StartBlock updates start timestamp of the current increment block. -// It is optional to call, unless ETA decorator is used. -// If *bar.ProxyReader is used, it will be called implicitly. -func (b *Bar) StartBlock() { - now := time.Now() - select { - case b.operateState <- func(s *bState) { - if s.current == 0 { - s.startTime = now - } - s.blockStartTime = now - }: - case <-b.done: } } @@ -257,7 +248,11 @@ case b.operateState <- func(s *bState) { s.current += int64(n) s.timeElapsed = now.Sub(s.startTime) - s.timeRemaining = s.calcETA(n, now.Sub(s.blockStartTime)) + if s.ewmAverage != nil { + lastBlockTime := now.Sub(s.blockStartTime) + lastItemEstimate := float64(lastBlockTime) / float64(n) + s.ewmAverage.Add(lastItemEstimate) + } if s.dynamic { curp := decor.CalcPercentage(s.total, s.current, 100) if 100-curp <= s.totalAutoIncrTrigger { @@ -286,11 +281,12 @@ func (b *Bar) serve(wg *sync.WaitGroup, s *bState, cancel <-chan struct{}) { defer wg.Done() s.startTime = time.Now() - s.blockStartTime = s.startTime for { select { case op := <-b.operateState: op(s) + case now := <-b.startBlockCh: + s.blockStartTime = now case <-cancel: s.toComplete = true cancel = nil @@ -349,12 +345,12 @@ stat := newStatistics(s) // render prepend functions to the left of the bar - for i, f := range s.pDecorators { - s.bufP.WriteString(f(stat, pSyncer.Accumulator[i], pSyncer.Distributor[i])) - } - - for i, f := range s.aDecorators { - s.bufA.WriteString(f(stat, aSyncer.Accumulator[i], aSyncer.Distributor[i])) + for i, d := range s.pDecorators { + s.bufP.WriteString(d.Decor(stat, pSyncer.Accumulator[i], pSyncer.Distributor[i])) + } + + for i, d := range s.aDecorators { + s.bufA.WriteString(d.Decor(stat, aSyncer.Accumulator[i], aSyncer.Distributor[i])) } prependCount := utf8.RuneCount(s.bufP.Bytes()) @@ -430,22 +426,14 @@ } } -func (s *bState) calcETA(n int, lastBlockTime time.Duration) time.Duration { - lastItemEstimate := float64(lastBlockTime) / float64(n) - s.timePerItemEstimate = time.Duration((s.etaAlpha * lastItemEstimate) + (1-s.etaAlpha)*float64(s.timePerItemEstimate)) - return time.Duration(s.total-s.current) * s.timePerItemEstimate -} - func newStatistics(s *bState) *decor.Statistics { return &decor.Statistics{ - ID: s.id, - Completed: s.completeFlushed, - Total: s.total, - Current: s.current, - StartTime: s.startTime, - TimeElapsed: s.timeElapsed, - TimeRemaining: s.timeRemaining, - TimePerItemEstimate: s.timePerItemEstimate, + ID: s.id, + Completed: s.completeFlushed, + Total: s.total, + Current: s.current, + StartTime: s.startTime, + TimeElapsed: s.timeElapsed, } } diff --git a/bar_option.go b/bar_option.go index 57a9c04..13162c6 100644 --- a/bar_option.go +++ b/bar_option.go @@ -9,16 +9,28 @@ type BarOption func(*bState) // AppendDecorators let you inject decorators to the bar's right side -func AppendDecorators(appenders ...decor.DecoratorFunc) BarOption { +func AppendDecorators(appenders ...decor.Decorator) BarOption { return func(s *bState) { - s.aDecorators = append(s.aDecorators, appenders...) + for _, decorator := range appenders { + if t, ok := decorator.(*decor.EwmaETA); ok { + s.ewmAverage = t + s.startBlockCh = t.StartBlockCh + } + s.aDecorators = append(s.aDecorators, decorator) + } } } // PrependDecorators let you inject decorators to the bar's left side -func PrependDecorators(prependers ...decor.DecoratorFunc) BarOption { +func PrependDecorators(prependers ...decor.Decorator) BarOption { return func(s *bState) { - s.pDecorators = append(s.pDecorators, prependers...) + for _, decorator := range prependers { + if t, ok := decorator.(*decor.EwmaETA); ok { + s.ewmAverage = t + s.startBlockCh = t.StartBlockCh + } + s.pDecorators = append(s.pDecorators, decorator) + } } } @@ -48,15 +60,6 @@ func BarID(id int) BarOption { return func(s *bState) { s.id = id - } -} - -// BarEtaAlpha option is a way to adjust ETA behavior. -// You can play with it, if you're not satisfied with default behavior. -// Default value is 0.12 -func BarEtaAlpha(a float64) BarOption { - return func(s *bState) { - s.etaAlpha = a } } diff --git a/bar_test.go b/bar_test.go index d8663d6..7933475 100644 --- a/bar_test.go +++ b/bar_test.go @@ -90,12 +90,12 @@ total := 100 bar := p.AddBar(int64(total), PrependDecorators( - func(s *decor.Statistics, _ chan<- int, _ <-chan int) string { + decor.DecoratorFunc(func(s *decor.Statistics, _ chan<- int, _ <-chan int) string { if s.Current >= 42 { panic(wantPanic) } return "test" - }, + }), )) go func() { diff --git a/barbench_test.go b/barbench_test.go index 0ef4fea..20f4082 100644 --- a/barbench_test.go +++ b/barbench_test.go @@ -13,15 +13,6 @@ } } -func BenchmarkIncrSingleBarStartBlock(b *testing.B) { - p := New(WithOutput(ioutil.Discard)) - bar := p.AddBar(int64(b.N)) - for i := 0; i < b.N; i++ { - bar.StartBlock() - bar.Increment() - } -} - func BenchmarkIncrSingleBarWhileIsNotCompleted(b *testing.B) { p := New(WithOutput(ioutil.Discard)) bar := p.AddBar(int64(b.N)) diff --git a/decor/decorators.go b/decor/decorators.go index 29f11ef..df75b0d 100644 --- a/decor/decorators.go +++ b/decor/decorators.go @@ -5,6 +5,8 @@ "math" "time" "unicode/utf8" + + "github.com/VividCortex/ewma" ) const ( @@ -13,144 +15,188 @@ // | foo| b| Without DidentRight DidentRight = 1 << iota - // DwidthSync bit enables same column width synchronization. - // Effective on multiple bars only. - DwidthSync - - // DextraSpace bit adds extra space, makes sense with DwidthSync only. + // DextraSpace bit adds extra space, makes sense with DSyncWidth only. // When DidentRight bit set, the space will be added to the right, // otherwise to the left. DextraSpace - // DSyncSpace is shortcut for DwidthSync|DextraSpace - DSyncSpace = DwidthSync | DextraSpace - - // DSyncSpaceR is shortcut for DwidthSync|DextraSpace|DidentRight - DSyncSpaceR = DwidthSync | DextraSpace | DidentRight + // DSyncWidth bit enables same column width synchronization. + // Effective with multiple bars only. + DSyncWidth + + // DSyncWidthR is shortcut for DSyncWidth|DidentRight + DSyncWidthR = DSyncWidth | DidentRight + + // DSyncSpace is shortcut for DSyncWidth|DextraSpace + DSyncSpace = DSyncWidth | DextraSpace + + // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight + DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight +) + +const ( + ET_STYLE_GO = iota + ET_STYLE_HHMMSS + ET_STYLE_HHMM + ET_STYLE_MMSS ) // Statistics contains values useful for implementing a DecoratorFunc. type Statistics struct { - ID int - Completed bool - Total int64 - Current int64 - StartTime time.Time - TimeElapsed time.Duration - TimeRemaining time.Duration - TimePerItemEstimate time.Duration -} - -// DecoratorFunc is a function that can be prepended and appended to the progress bar + ID int + Completed bool + Total int64 + Current int64 + StartTime time.Time + TimeElapsed time.Duration +} + +type Decorator interface { + Decor(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string +} + +type CompleteMessenger interface { + OnComplete(string, ...WC) +} + +// DecoratorFunc is an adapter for Decorator interface type DecoratorFunc func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string -// OnComplete returns decorator, which wraps provided `fn` decorator, with sole -// purpose to display final on complete message. -// -// `fn` DecoratorFunc to wrap -// -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] -func OnComplete(fn DecoratorFunc, message string, width, conf int) DecoratorFunc { - msgDecorator := StaticName(message, width, conf) - return func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { +func (f DecoratorFunc) Decor(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + return f(s, widthAccumulator, widthDistributor) +} + +// WC is a struct with two public fields W and C, both of int type. +// W represents width and C represents bit set of width related config. +type WC struct { + W int + C int + format string +} + +func (wc WC) formatMsg(msg string, widthAccumulator chan<- int, widthDistributor <-chan int) string { + format := wc.buildFormat() + if (wc.C & DSyncWidth) != 0 { + widthAccumulator <- utf8.RuneCountInString(msg) + max := <-widthDistributor + if max == 0 { + max = wc.W + } + if (wc.C & DextraSpace) != 0 { + max++ + } + return fmt.Sprintf(fmt.Sprintf(format, max), msg) + } + return fmt.Sprintf(fmt.Sprintf(format, wc.W), msg) +} + +func (wc *WC) buildFormat() string { + if wc.format != "" { + return wc.format + } + wc.format = "%%" + if (wc.C & DidentRight) != 0 { + wc.format += "-" + } + wc.format += "%ds" + return wc.format +} + +// Global convenience shortcuts +var ( + WCSyncWidth = WC{C: DSyncWidth} + WCSyncWidthR = WC{C: DSyncWidthR} + WCSyncSpace = WC{C: DSyncSpace} + WCSyncSpaceR = WC{C: DSyncSpaceR} +) + +// OnComplete returns decorator, which wraps provided decorator, with sole +// purpose to display provided message on complete event. +// +// `decorator` Decorator to wrap +// +// `message` message to display on complete event +// +// `wc` optional WC config +func OnComplete(decorator Decorator, message string, wc ...WC) Decorator { + if cm, ok := decorator.(CompleteMessenger); ok { + cm.OnComplete(message, wc...) + return decorator + } + msgDecorator := Name(message, wc...) + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { if s.Completed { - return msgDecorator(s, widthAccumulator, widthDistributor) - } - return fn(s, widthAccumulator, widthDistributor) - } -} - -// StaticName returns static name/message decorator. + return msgDecorator.Decor(s, widthAccumulator, widthDistributor) + } + return decorator.Decor(s, widthAccumulator, widthDistributor) + }) +} + +// StaticName returns name decorator. // // `name` string to display // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] -func StaticName(name string, width, conf int) DecoratorFunc { - nameFn := func(*Statistics) string { - return name - } - return DynamicName(nameFn, width, conf) -} - -// DynamicName returns dynamic name/message decorator. -// -// `messageFn` callback function to get dynamic string message -// -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] -func DynamicName(messageFn func(*Statistics) string, width, conf int) DecoratorFunc { - format := "%%" - if (conf & DidentRight) != 0 { - format += "-" - } - format += "%ds" - return func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - name := messageFn(s) - if (conf & DwidthSync) != 0 { - widthAccumulator <- utf8.RuneCountInString(name) - max := <-widthDistributor - if (conf & DextraSpace) != 0 { - max++ - } - return fmt.Sprintf(fmt.Sprintf(format, max), name) - } - return fmt.Sprintf(fmt.Sprintf(format, width), name) - } +// `wc` optional WC config +func StaticName(name string, wc ...WC) Decorator { + return Name(name, wc...) +} + +// Name returns name decorator. +// +// `name` string to display +// +// `wc` optional WC config +func Name(name string, wc ...WC) Decorator { + var wc0 WC + if len(wc) > 0 { + wc0 = wc[0] + } + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + return wc0.formatMsg(name, widthAccumulator, widthDistributor) + }) } // CountersNoUnit returns raw counters decorator // // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] -func CountersNoUnit(pairFormat string, width, conf int) DecoratorFunc { - return counters(pairFormat, 0, width, conf) +// `wc` optional WC config +func CountersNoUnit(pairFormat string, wc ...WC) Decorator { + return counters(pairFormat, 0, wc...) } // CountersKibiByte returns human friendly byte counters decorator, where counters unit is multiple by 1024. // // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] +// `wc` optional WC config // // pairFormat example: // // "%.1f / %.1f" = "1.0MiB / 12.0MiB" or "% .1f / % .1f" = "1.0 MiB / 12.0 MiB" -func CountersKibiByte(pairFormat string, width, conf int) DecoratorFunc { - return counters(pairFormat, unitKiB, width, conf) +func CountersKibiByte(pairFormat string, wc ...WC) Decorator { + return counters(pairFormat, unitKiB, wc...) } // CountersKiloByte returns human friendly byte counters decorator, where counters unit is multiple by 1000. // // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] +// `wc` optional WC config // // pairFormat example: // // "%.1f / %.1f" = "1.0MB / 12.0MB" or "% .1f / % .1f" = "1.0 MB / 12.0 MB" -func CountersKiloByte(pairFormat string, width, conf int) DecoratorFunc { - return counters(pairFormat, unitKB, width, conf) -} - -func counters(pairFormat string, unit, width, conf int) DecoratorFunc { - format := "%%" - if (conf & DidentRight) != 0 { - format += "-" - } - format += "%ds" - return func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { +func CountersKiloByte(pairFormat string, wc ...WC) Decorator { + return counters(pairFormat, unitKB, wc...) +} + +func counters(pairFormat string, unit int, wc ...WC) Decorator { + var wc0 WC + if len(wc) > 0 { + wc0 = wc[0] + } + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { var str string switch unit { case unitKiB: @@ -160,94 +206,128 @@ default: str = fmt.Sprintf(pairFormat, s.Current, s.Total) } - if (conf & DwidthSync) != 0 { - widthAccumulator <- utf8.RuneCountInString(str) - max := <-widthDistributor - if (conf & DextraSpace) != 0 { - max++ - } - return fmt.Sprintf(fmt.Sprintf(format, max), str) - } - return fmt.Sprintf(fmt.Sprintf(format, width), str) - } + return wc0.formatMsg(str, widthAccumulator, widthDistributor) + }) } // ETA returns exponential-weighted-moving-average ETA decorator. // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] -// -// To correctly estimate non io progress, *Bar.StartBlock should be called, -// before each increment block. -func ETA(width, conf int) DecoratorFunc { - format := "%%" - if (conf & DidentRight) != 0 { - format += "-" - } - format += "%ds" - return func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - str := fmt.Sprint(time.Duration(s.TimeRemaining.Seconds()) * time.Second) - if (conf & DwidthSync) != 0 { - widthAccumulator <- utf8.RuneCountInString(str) - max := <-widthDistributor - if (conf & DextraSpace) != 0 { - max++ - } - return fmt.Sprintf(fmt.Sprintf(format, max), str) - } - return fmt.Sprintf(fmt.Sprintf(format, width), str) - } +// `style` onfe of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] +// +// `age` is a decay factor alpha for underlying ewma. +// General rule of thumb, for the best value: +// expected progress time in seconds divided by two. +// For example expected progress duration is one hour. +// age = 3600 / 2 +// +// `startBlock` is channel, user suppose to send time.Now() on each iteration of block start. +// +// `wc` optional WC config +func ETA(style int, age float64, startBlock chan time.Time, wc ...WC) Decorator { + var wc0 WC + if len(wc) > 0 { + wc0 = wc[0] + } + if age == .0 { + age = ewma.AVG_METRIC_AGE + } + return &EwmaETA{ + MovingAverage: ewma.NewMovingAverage(age), + StartBlockCh: startBlock, + style: style, + wc: wc0, + } +} + +type EwmaETA struct { + ewma.MovingAverage + StartBlockCh chan time.Time + style int + wc WC + onComplete *struct { + msg string + wc WC + } +} + +func (s *EwmaETA) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + if st.Completed && s.onComplete != nil { + return s.onComplete.wc.formatMsg(s.onComplete.msg, widthAccumulator, widthDistributor) + } + + var str string + timeRemaining := time.Duration(float64(st.Total-st.Current) * s.MovingAverage.Value()) + hours := int64((timeRemaining / time.Hour) % 60) + minutes := int64((timeRemaining / time.Minute) % 60) + seconds := int64((timeRemaining / time.Second) % 60) + + switch s.style { + case ET_STYLE_GO: + str = fmt.Sprint(time.Duration(timeRemaining.Seconds()) * time.Second) + case ET_STYLE_HHMMSS: + str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) + case ET_STYLE_HHMM: + str = fmt.Sprintf("%02d:%02d", hours, minutes) + case ET_STYLE_MMSS: + str = fmt.Sprintf("%02d:%02d", minutes, seconds) + } + + return s.wc.formatMsg(str, widthAccumulator, widthDistributor) +} + +func (s *EwmaETA) OnComplete(msg string, wc ...WC) { + var wc0 WC + if len(wc) > 0 { + wc0 = wc[0] + } + s.onComplete = &struct { + msg string + wc WC + }{msg, wc0} } // Elapsed returns elapsed time decorator. // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] -func Elapsed(width, conf int) DecoratorFunc { - format := "%%" - if (conf & DidentRight) != 0 { - format += "-" - } - format += "%ds" - return func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { - str := fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second) - if (conf & DwidthSync) != 0 { - widthAccumulator <- utf8.RuneCountInString(str) - max := <-widthDistributor - if (conf & DextraSpace) != 0 { - max++ - } - return fmt.Sprintf(fmt.Sprintf(format, max), str) - } - return fmt.Sprintf(fmt.Sprintf(format, width), str) - } +// `style` onfe of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] +// +// `wc` optional WC config +func Elapsed(style int, wc ...WC) Decorator { + var wc0 WC + if len(wc) > 0 { + wc0 = wc[0] + } + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { + var str string + hours := int64((s.TimeElapsed / time.Hour) % 60) + minutes := int64((s.TimeElapsed / time.Minute) % 60) + seconds := int64((s.TimeElapsed / time.Second) % 60) + + switch style { + case ET_STYLE_GO: + str = fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second) + case ET_STYLE_HHMMSS: + str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) + case ET_STYLE_HHMM: + str = fmt.Sprintf("%02d:%02d", hours, minutes) + case ET_STYLE_MMSS: + str = fmt.Sprintf("%02d:%02d", minutes, seconds) + } + return wc0.formatMsg(str, widthAccumulator, widthDistributor) + }) } // Percentage returns percentage decorator. // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] -func Percentage(width, conf int) DecoratorFunc { - format := "%%" - if (conf & DidentRight) != 0 { - format += "-" - } - format += "%ds" - return func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { +// `wc` optional WC config +func Percentage(wc ...WC) Decorator { + var wc0 WC + if len(wc) > 0 { + wc0 = wc[0] + } + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { str := fmt.Sprintf("%d %%", CalcPercentage(s.Total, s.Current, 100)) - if (conf & DwidthSync) != 0 { - widthAccumulator <- utf8.RuneCountInString(str) - max := <-widthDistributor - if (conf & DextraSpace) != 0 { - max++ - } - return fmt.Sprintf(fmt.Sprintf(format, max), str) - } - return fmt.Sprintf(fmt.Sprintf(format, width), str) - } + return wc0.formatMsg(str, widthAccumulator, widthDistributor) + }) } // CalcPercentage is a helper function, to calculate percentage. @@ -274,59 +354,49 @@ // // `unitFormat` printf compatible verb for value, like "%f" or "%d" // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] +// `wc` optional WC config // // unitFormat example: // // "%.1f" = "1.0" or "% .1f" = "1.0" -func SpeedNoUnit(unitFormat string, width, conf int) DecoratorFunc { - return speed(unitFormat, 0, width, conf) +func SpeedNoUnit(unitFormat string, wc ...WC) Decorator { + return speed(unitFormat, 0, wc...) } // SpeedKibiByte returns human friendly I/O operation speed decorator, // // `unitFormat` printf compatible verb for value, like "%f" or "%d" // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] +// `wc` optional WC config // // unitFormat example: // // "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s" -func SpeedKibiByte(unitFormat string, width, conf int) DecoratorFunc { - return speed(unitFormat, unitKiB, width, conf) +func SpeedKibiByte(unitFormat string, wc ...WC) Decorator { + return speed(unitFormat, unitKiB, wc...) } // SpeedKiloByte returns human friendly I/O operation speed decorator, // // `unitFormat` printf compatible verb for value, like "%f" or "%d" // -// `width` width reservation to apply, ignored if `DwidthSync` bit is set -// -// `conf` bit set config, [DidentRight|DwidthSync|DextraSpace] +// `wc` optional WC config // // unitFormat example: // // "%.1f" = "1.0MB/s" or "% .1f" = "1.0 MB/s" -func SpeedKiloByte(unitFormat string, width, conf int) DecoratorFunc { - return speed(unitFormat, unitKB, width, conf) -} - -func speed(unitFormat string, unit, width, conf int) DecoratorFunc { - format := "%%" - if (conf & DidentRight) != 0 { - format += "-" - } - format += "%ds" - - return func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { +func SpeedKiloByte(unitFormat string, wc ...WC) Decorator { + return speed(unitFormat, unitKB, wc...) +} + +func speed(unitFormat string, unit int, wc ...WC) Decorator { + var wc0 WC + if len(wc) > 0 { + wc0 = wc[0] + } + return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { var str string - speed := float64(s.Current) / s.TimeElapsed.Seconds() - if math.IsNaN(speed) || math.IsInf(speed, 0) { speed = .0 } @@ -339,14 +409,6 @@ default: str = fmt.Sprintf(unitFormat, speed) } - if (conf & DwidthSync) != 0 { - widthAccumulator <- utf8.RuneCountInString(str) - max := <-widthDistributor - if (conf & DextraSpace) != 0 { - max++ - } - return fmt.Sprintf(fmt.Sprintf(format, max), str) - } - return fmt.Sprintf(fmt.Sprintf(format, width), str) - } -} + return wc0.formatMsg(str, widthAccumulator, widthDistributor) + }) +} diff --git a/decorators_test.go b/decorators_test.go index 64a0678..3c0d5b4 100644 --- a/decorators_test.go +++ b/decorators_test.go @@ -9,31 +9,31 @@ "github.com/vbauerster/mpb/decor" ) -func TestStaticName(t *testing.T) { +func TestNameDecorator(t *testing.T) { tests := []struct { - fn decor.DecoratorFunc - want string + decorator decor.Decorator + want string }{ { - fn: decor.StaticName("Test", 0, 0), - want: "Test", - }, - { - fn: decor.StaticName("Test", len("Test"), 0), - want: "Test", - }, - { - fn: decor.StaticName("Test", 10, 0), - want: " Test", - }, - { - fn: decor.StaticName("Test", 10, decor.DidentRight), - want: "Test ", + decorator: decor.Name("Test"), + want: "Test", + }, + { + decorator: decor.Name("Test", decor.WC{W: len("Test")}), + want: "Test", + }, + { + decorator: decor.Name("Test", decor.WC{W: 10}), + want: " Test", + }, + { + decorator: decor.Name("Test", decor.WC{W: 10, C: decor.DidentRight}), + want: "Test ", }, } for _, test := range tests { - got := test.fn(nil, nil, nil) + got := test.decorator.Decor(nil, nil, nil) if got != test.want { t.Errorf("Want: %q, Got: %q\n", test.want, got) } @@ -41,9 +41,9 @@ } type step struct { - stat *decor.Statistics - dfn decor.DecoratorFunc - want string + stat *decor.Statistics + decorator decor.Decorator + want string } func TestPercentageDwidthSync(t *testing.T) { @@ -52,36 +52,36 @@ []step{ { &decor.Statistics{Total: 100, Current: 8}, - decor.Percentage(0, decor.DwidthSync), + decor.Percentage(decor.WCSyncWidth), "8 %", }, { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DwidthSync), + decor.Percentage(decor.WCSyncWidth), "9 %", }, }, []step{ { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DwidthSync), + decor.Percentage(decor.WCSyncWidth), " 9 %", }, { &decor.Statistics{Total: 100, Current: 10}, - decor.Percentage(0, decor.DwidthSync), + decor.Percentage(decor.WCSyncWidth), "10 %", }, }, []step{ { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DwidthSync), + decor.Percentage(decor.WCSyncWidth), " 9 %", }, { &decor.Statistics{Total: 100, Current: 100}, - decor.Percentage(0, decor.DwidthSync), + decor.Percentage(decor.WCSyncWidth), "100 %", }, }, @@ -96,36 +96,36 @@ []step{ { &decor.Statistics{Total: 100, Current: 8}, - decor.Percentage(0, decor.DwidthSync|decor.DidentRight), + decor.Percentage(decor.WCSyncWidthR), "8 %", }, { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DwidthSync|decor.DidentRight), + decor.Percentage(decor.WCSyncWidthR), "9 %", }, }, []step{ { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DwidthSync|decor.DidentRight), + decor.Percentage(decor.WCSyncWidthR), "9 % ", }, { &decor.Statistics{Total: 100, Current: 10}, - decor.Percentage(0, decor.DwidthSync|decor.DidentRight), + decor.Percentage(decor.WCSyncWidthR), "10 %", }, }, []step{ { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DwidthSync|decor.DidentRight), + decor.Percentage(decor.WCSyncWidthR), "9 % ", }, { &decor.Statistics{Total: 100, Current: 100}, - decor.Percentage(0, decor.DwidthSync|decor.DidentRight), + decor.Percentage(decor.WCSyncWidthR), "100 %", }, }, @@ -140,36 +140,36 @@ []step{ { &decor.Statistics{Total: 100, Current: 8}, - decor.Percentage(0, decor.DSyncSpace), + decor.Percentage(decor.WCSyncSpace), " 8 %", }, { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DSyncSpace), + decor.Percentage(decor.WCSyncSpace), " 9 %", }, }, []step{ { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DSyncSpace), + decor.Percentage(decor.WCSyncSpace), " 9 %", }, { &decor.Statistics{Total: 100, Current: 10}, - decor.Percentage(0, decor.DSyncSpace), + decor.Percentage(decor.WCSyncSpace), " 10 %", }, }, []step{ { &decor.Statistics{Total: 100, Current: 9}, - decor.Percentage(0, decor.DSyncSpace), + decor.Percentage(decor.WCSyncSpace), " 9 %", }, { &decor.Statistics{Total: 100, Current: 100}, - decor.Percentage(0, decor.DSyncSpace), + decor.Percentage(decor.WCSyncSpace), " 100 %", }, }, @@ -197,7 +197,7 @@ res[i] = make(chan string, 1) go func(s step, ch chan string) { defer wg.Done() - ch <- s.dfn(s.stat, ws.Accumulator[0], ws.Distributor[0]) + ch <- s.decorator.Decor(s.stat, ws.Accumulator[0], ws.Distributor[0]) }(columnCase[i], res[i]) } wg.Wait() diff --git a/example_test.go b/example_test.go index 3da8963..65ae1e4 100644 --- a/example_test.go +++ b/example_test.go @@ -14,35 +14,40 @@ func Example() { p := mpb.New( // override default (80) width - mpb.WithWidth(100), + mpb.WithWidth(64), // override default "[=>-]" format mpb.WithFormat("╢▌▌░╟"), // override default 120ms refresh rate - mpb.WithRefreshRate(100*time.Millisecond), + mpb.WithRefreshRate(180*time.Millisecond), ) total := 100 name := "Single Bar:" + startBlock := make(chan time.Time) // adding a single bar bar := p.AddBar(int64(total), mpb.PrependDecorators( - // Display our static name with one space on the right - decor.StaticName(name, len(name)+1, decor.DidentRight), - // ETA decorator with width reservation of 3 runes - decor.ETA(3, 0), + // Display our name with one space on the right + decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), + // Replace ETA decorator with message, OnComplete event + decor.OnComplete( + // ETA decorator with default eta age, and width reservation of 4 + decor.ETA(decor.ET_STYLE_GO, 0, startBlock, decor.WC{W: 4}), + "done", + ), ), mpb.AppendDecorators( - // Percentage decorator with width reservation of 5 runes - decor.Percentage(5, 0), + decor.Percentage(), ), ) // simulating some work max := 100 * time.Millisecond for i := 0; i < total; i++ { - bar.StartBlock() // optional call, required for ETA + // update start block time, required for ETA calculation + startBlock <- time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) - // increment by 1 (there is bar.IncrBy(int) method, if needed) + // Increment by 1 (there is bar.IncrBy(int) method, if needed) bar.Increment() } // wait for our bar to complete and flush @@ -71,7 +76,7 @@ // Assuming ContentLength > 0 bar := p.AddBar(resp.ContentLength, mpb.AppendDecorators( - decor.CountersKibiByte("%6.1f / %6.1f", 12, 0), + decor.CountersKibiByte("%6.1f / %6.1f"), ), ) diff --git a/examples/cancel/main.go b/examples/cancel/main.go index 7ea7b8f..3ff0520 100644 --- a/examples/cancel/main.go +++ b/examples/cancel/main.go @@ -32,13 +32,14 @@ for i := 0; i < numBars; i++ { name := fmt.Sprintf("Bar#%d:", i) + startBlock := make(chan time.Time) bar := p.AddBar(int64(total), mpb.PrependDecorators( - decor.StaticName(name, 0, decor.DwidthSync|decor.DidentRight), - decor.ETA(4, decor.DSyncSpace), + decor.Name(name), + decor.ETA(decor.ET_STYLE_GO, 0, startBlock, decor.WCSyncSpace), ), mpb.AppendDecorators( - decor.Percentage(5, 0), + decor.Percentage(decor.WC{W: 5}), ), ) @@ -46,7 +47,7 @@ defer wg.Done() max := 100 * time.Millisecond for !bar.Completed() { - bar.StartBlock() + startBlock <- time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) bar.Increment() } diff --git a/examples/complex/main.go b/examples/complex/main.go index 9035d77..12a9afc 100644 --- a/examples/complex/main.go +++ b/examples/complex/main.go @@ -30,13 +30,13 @@ b := p.AddBar(rand.Int63n(201)+100, mpb.BarRemoveOnComplete(), mpb.PrependDecorators( - decor.StaticName(task, len(task)+1, decor.DidentRight), - decor.StaticName(job, 0, decor.DSyncSpaceR), - decor.CountersNoUnit("%d / %d", 0, decor.DwidthSync), + decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), + decor.Name(job, decor.WCSyncSpaceR), + decor.CountersNoUnit("%d / %d", decor.WCSyncWidth), ), - mpb.AppendDecorators(decor.Percentage(5, 0)), + mpb.AppendDecorators(decor.Percentage(decor.WC{W: 5})), ) - go newTask(wg, b, i+1) + go newTask(wg, b, i+1, nil) bars = append(bars, b) } @@ -44,6 +44,7 @@ doneWg.Add(1) i := i go func() { + startBlock := make(chan time.Time) task := fmt.Sprintf("Task#%02d:", i) job := "installing" // preparing delayed bars @@ -51,28 +52,34 @@ mpb.BarReplaceOnComplete(bars[i]), mpb.BarClearOnComplete(), mpb.PrependDecorators( - decor.StaticName(task, len(task)+1, decor.DidentRight), - decor.OnComplete(decor.StaticName(job, 0, decor.DSyncSpaceR), "done!", 0, decor.DSyncSpaceR), - decor.OnComplete(decor.ETA(0, decor.DwidthSync), "", 0, decor.DwidthSync), + decor.Name(task, decor.WC{W: len(task) + 1, C: decor.DidentRight}), + decor.OnComplete(decor.Name(job, decor.WCSyncSpaceR), "done!", decor.WCSyncSpaceR), + decor.OnComplete( + decor.ETA(decor.ET_STYLE_GO, 0, startBlock, decor.WCSyncWidth), + "", + decor.WCSyncSpace, + ), ), mpb.AppendDecorators( - decor.OnComplete(decor.Percentage(5, 0), "", 0, 0), + decor.OnComplete(decor.Percentage(decor.WC{W: 5}), ""), ), ) // waiting for download to complete, before starting install job downloadWgg[i].Wait() - go newTask(doneWg, b, numBars-i) + go newTask(doneWg, b, numBars-i, startBlock) }() } p.Wait() } -func newTask(wg *sync.WaitGroup, b *mpb.Bar, incrBy int) { +func newTask(wg *sync.WaitGroup, b *mpb.Bar, incrBy int, startBlock chan<- time.Time) { defer wg.Done() max := 100 * time.Millisecond for !b.Completed() { - b.StartBlock() + if startBlock != nil { + startBlock <- time.Now() + } time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) b.IncrBy(incrBy) } diff --git a/examples/dynTotal/main.go b/examples/dynTotal/main.go index 0a4bf7f..0244155 100644 --- a/examples/dynTotal/main.go +++ b/examples/dynTotal/main.go @@ -22,10 +22,10 @@ // trigger total auto increment by 1, when 18 % remains till bar completion mpb.BarAutoIncrTotal(18, 1), mpb.PrependDecorators( - decor.CountersNoUnit("%d / %d", 12, 0), + decor.CountersNoUnit("%d / %d", decor.WC{W: 12}), ), mpb.AppendDecorators( - decor.Percentage(4, 0), + decor.Percentage(), ), ) diff --git a/examples/io/multiple/main.go b/examples/io/multiple/main.go index b55a79a..689c449 100644 --- a/examples/io/multiple/main.go +++ b/examples/io/multiple/main.go @@ -8,6 +8,7 @@ "os" "path/filepath" "sync" + "time" "github.com/vbauerster/mpb" "github.com/vbauerster/mpb/decor" @@ -57,20 +58,22 @@ return } + startBlock := make(chan time.Time) + // create bar with appropriate decorators bar := p.AddBar(size, mpb.BarPriority(n), mpb.PrependDecorators( - decor.StaticName(name, len(name)+1, decor.DidentRight), - decor.CountersKibiByte("%6.1f / %6.1f", 0, decor.DwidthSync), + decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), + decor.CountersKibiByte("%6.1f / %6.1f", decor.WCSyncWidth), ), mpb.AppendDecorators( - decor.ETA(0, decor.DwidthSync), - decor.SpeedKibiByte("%6.1f", 18, 0), + decor.ETA(decor.ET_STYLE_HHMMSS, 60, startBlock, decor.WCSyncWidth), + decor.SpeedKibiByte("%6.1f", decor.WCSyncSpace), ), ) // create proxy reader - reader := bar.ProxyReader(resp.Body) + reader := bar.ProxyReader(resp.Body, startBlock) // and copy from reader _, err = io.Copy(dest, reader) diff --git a/examples/io/single/main.go b/examples/io/single/main.go index 15cd584..50d763f 100644 --- a/examples/io/single/main.go +++ b/examples/io/single/main.go @@ -6,13 +6,14 @@ "net/http" "os" "path/filepath" + "time" "github.com/vbauerster/mpb" "github.com/vbauerster/mpb/decor" ) func main() { - url := "https://homebrew.bintray.com/bottles/libtiff-4.0.7.sierra.bottle.tar.gz" + url := "https://github.com/onivim/oni/releases/download/v0.3.4/Oni-0.3.4-osx.dmg" resp, err := http.Get(url) if err != nil { @@ -38,18 +39,19 @@ p := mpb.New(mpb.WithWidth(64)) + startBlock := make(chan time.Time) bar := p.AddBar(size, mpb.PrependDecorators( - decor.CountersKibiByte("% 6.1f / % 6.1f", 18, 0), + decor.CountersKibiByte("% 6.1f / % 6.1f", decor.WC{W: 18}), ), mpb.AppendDecorators( - decor.ETA(0, 0), - decor.SpeedKibiByte("% 6.1f", 18, 0), + decor.ETA(decor.ET_STYLE_HHMMSS, 900, startBlock), + decor.SpeedKibiByte("% 6.1f", decor.WC{W: 14}), ), ) // create proxy reader - reader := bar.ProxyReader(resp.Body) + reader := bar.ProxyReader(resp.Body, startBlock) // and copy from reader, ignoring errors io.Copy(dest, reader) diff --git a/examples/panic/main.go b/examples/panic/main.go index 4d618b9..610d443 100644 --- a/examples/panic/main.go +++ b/examples/panic/main.go @@ -21,14 +21,14 @@ for i := 0; i < numBars; i++ { name := fmt.Sprintf("b#%02d:", i) bar := p.AddBar(100, mpb.BarID(i), mpb.PrependDecorators( - func(s *decor.Statistics, _ chan<- int, _ <-chan int) string { + decor.DecoratorFunc(func(s *decor.Statistics, _ chan<- int, _ <-chan int) string { // s.Current == 42 may never happen, if sleep btw increments is // too short, thus using s.Current >= 42 if s.ID == 1 && s.Current >= 42 { panic(wantPanic) } return name - }, + }), )) go func() { diff --git a/examples/prependETA/main.go b/examples/prependETA/main.go index 1f83e5c..191a625 100644 --- a/examples/prependETA/main.go +++ b/examples/prependETA/main.go @@ -26,20 +26,25 @@ if i != 1 { name = fmt.Sprintf("Bar#%d:", i) } + startBlock := make(chan time.Time) b := p.AddBar(int64(total), mpb.PrependDecorators( - decor.StaticName(name, 0, decor.DwidthSync), - decor.OnComplete(decor.ETA(4, 0), "Done", 0, decor.DSyncSpace), + decor.Name(name, decor.WCSyncWidth), + decor.OnComplete( + decor.ETA(decor.ET_STYLE_MMSS, 0, startBlock, decor.WC{W: 6}), + "Done", + decor.WCSyncSpace, + ), ), mpb.AppendDecorators( - decor.Percentage(5, 0), + decor.Percentage(decor.WC{W: 5}), ), ) go func() { defer wg.Done() max := 100 * time.Millisecond for i := 0; i < total; i++ { - b.StartBlock() + startBlock <- time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) b.Increment() } diff --git a/examples/prependElapsed/main.go b/examples/prependElapsed/main.go index df08d9b..5399ed3 100644 --- a/examples/prependElapsed/main.go +++ b/examples/prependElapsed/main.go @@ -28,11 +28,11 @@ } b := p.AddBar(int64(total), mpb.PrependDecorators( - decor.StaticName(name, 0, decor.DwidthSync|decor.DidentRight), - decor.Elapsed(3, decor.DSyncSpace), + decor.Name(name, decor.WCSyncWidthR), + decor.Elapsed(decor.ET_STYLE_GO, decor.WCSyncSpace), ), mpb.AppendDecorators( - decor.Percentage(5, 0), + decor.Percentage(decor.WC{W: 5}), ), ) go func() { diff --git a/examples/remove/main.go b/examples/remove/main.go index 226dc50..0de1f2d 100644 --- a/examples/remove/main.go +++ b/examples/remove/main.go @@ -22,29 +22,29 @@ wg.Add(numBars) for i := 0; i < numBars; i++ { - var name string - name = fmt.Sprintf("Bar#%d:", i) + name := fmt.Sprintf("Bar#%d:", i) var bOption mpb.BarOption if i == 0 { bOption = mpb.BarRemoveOnComplete() } + startBlock := make(chan time.Time) b := p.AddBar(int64(total), mpb.BarID(i), bOption, mpb.PrependDecorators( - decor.StaticName(name, 0, decor.DwidthSync|decor.DidentRight), - decor.ETA(4, decor.DSyncSpace), + decor.Name(name), + decor.ETA(decor.ET_STYLE_GO, 0, startBlock, decor.WCSyncSpace), ), mpb.AppendDecorators( - decor.Percentage(5, 0), + decor.Percentage(), ), ) go func() { defer wg.Done() max := 100 * time.Millisecond for i := 0; i < total; i++ { - b.StartBlock() + startBlock <- time.Now() if b.ID() == 2 && i == 42 { p.Abort(b) return diff --git a/examples/simple/main.go b/examples/simple/main.go index 63cf10d..03402c4 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -22,16 +22,21 @@ for i := 0; i < numBars; i++ { name := fmt.Sprintf("Bar#%d:", i) + startBlock := make(chan time.Time) bar := p.AddBar(int64(total), mpb.PrependDecorators( - // Display our static name with one space on the right - decor.StaticName(name, len(name)+1, decor.DidentRight), - // DwidthSync bit enables same column width synchronization - decor.Percentage(0, decor.DwidthSync), + // Display our name with one space on the right + decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), + // decor.DSyncWidth bit enables same column width synchronization + decor.Percentage(decor.WCSyncWidth), ), mpb.AppendDecorators( - // replace our ETA decorator with "done!", on bar completion event - decor.OnComplete(decor.ETA(3, 0), "done!", 0, 0), + // Replace ETA decorator with message, OnComplete event + decor.OnComplete( + // ETA decorator with default eta age, and width reservation of 3 + decor.ETA(decor.ET_STYLE_GO, 0, startBlock, decor.WC{W: 3}), + "done!", + ), ), ) // simulating some work @@ -39,13 +44,12 @@ defer wg.Done() max := 100 * time.Millisecond for i := 0; i < total; i++ { - bar.StartBlock() + startBlock <- time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) bar.Increment() } }() } - // first wait for provided wg, then // wait for all bars to complete and flush p.Wait() } diff --git a/examples/singleBar/main.go b/examples/singleBar/main.go index 55038c3..648ab40 100644 --- a/examples/singleBar/main.go +++ b/examples/singleBar/main.go @@ -20,25 +20,29 @@ total := 100 name := "Single Bar:" + startBlock := make(chan time.Time) // adding a single bar bar := p.AddBar(int64(total), mpb.PrependDecorators( - // Display our static name with one space on the right - decor.StaticName(name, len(name)+1, decor.DidentRight), - // ETA decorator with width reservation of 3 runes - // decor.ETA(3, 0), - decor.OnComplete(decor.ETA(4, 0), "done", 0, 0), + // Display our name with one space on the right + decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), + // Replace ETA decorator with message, OnComplete event + decor.OnComplete( + // ETA decorator with default eta age, and width reservation of 4 + decor.ETA(decor.ET_STYLE_GO, 0, startBlock, decor.WC{W: 4}), + "done", + ), ), mpb.AppendDecorators( - // Percentage decorator with width reservation of 5 runes - decor.Percentage(5, 0), + decor.Percentage(), ), ) // simulating some work max := 100 * time.Millisecond for i := 0; i < total; i++ { - bar.StartBlock() // optional call, required for ETA + // update start block time, required for ETA calculation + startBlock <- time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) // Increment by 1 (there is bar.IncrBy(int) method, if needed) bar.Increment() diff --git a/examples/sort/main.go b/examples/sort/main.go index 2c7dfc5..7178362 100644 --- a/examples/sort/main.go +++ b/examples/sort/main.go @@ -26,20 +26,21 @@ if i != 1 { name = fmt.Sprintf("Bar#%d:", i) } + startBlock := make(chan time.Time) b := p.AddBar(int64(total), mpb.PrependDecorators( - decor.StaticName(name, 0, decor.DwidthSync), - decor.CountersNoUnit("%d / %d", 10, decor.DSyncSpace), + decor.Name(name, decor.WCSyncWidth), + decor.CountersNoUnit("%d / %d", decor.WCSyncSpace), ), mpb.AppendDecorators( - decor.ETA(3, 0), + decor.ETA(decor.ET_STYLE_GO, 0, startBlock, decor.WC{W: 3}), ), ) go func() { defer wg.Done() max := 100 * time.Millisecond for i := 0; i < total; i++ { - b.StartBlock() + startBlock <- time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) if i&1 == 1 { priority := total - int(b.Current()) diff --git a/examples/stress/main.go b/examples/stress/main.go index dfdd92f..1e9364f 100644 --- a/examples/stress/main.go +++ b/examples/stress/main.go @@ -27,13 +27,14 @@ for i := 0; i < totalBars; i++ { name := fmt.Sprintf("Bar#%02d: ", i) total := rand.Intn(120) + 10 + startBlock := make(chan time.Time) bar := p.AddBar(int64(total), mpb.PrependDecorators( - decor.StaticName(name, len(name), 0), - decor.ETA(4, decor.DSyncSpace), + decor.Name(name), + decor.ETA(decor.ET_STYLE_GO, 0, startBlock, decor.WCSyncSpace), ), mpb.AppendDecorators( - decor.Percentage(5, 0), + decor.Percentage(decor.WC{W: 5}), ), ) @@ -41,7 +42,7 @@ defer wg.Done() max := 100 * time.Millisecond for i := 0; i < total; i++ { - bar.StartBlock() + startBlock <- time.Now() time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) bar.Increment() } diff --git a/proxyreader.go b/proxyreader.go index c333ad8..da9c2ca 100644 --- a/proxyreader.go +++ b/proxyreader.go @@ -1,15 +1,21 @@ package mpb -import "io" +import ( + "io" + "time" +) // Reader is io.Reader wrapper, for proxy read bytes type Reader struct { io.Reader - bar *Bar + bar *Bar + startBlockCh chan<- time.Time } func (r *Reader) Read(p []byte) (int, error) { - r.bar.StartBlock() + if r.startBlockCh != nil { + r.startBlockCh <- time.Now() + } n, err := r.Reader.Read(p) r.bar.IncrBy(n) return n, err