diff --git a/bar.go b/bar.go index 353f2ba..03d7a9a 100644 --- a/bar.go +++ b/bar.go @@ -43,6 +43,7 @@ operateState chan func(*bState) cmdValue chan int frameReaderCh chan io.Reader + bufNL *bytes.Buffer // done is closed by Bar's goroutine, after cacheState is written done chan struct{} @@ -70,6 +71,7 @@ refill *refill bufP, bufB, bufA *bytes.Buffer panicMsg string + newLineExtendFn func(bool, io.Writer) // following options are assigned to the *Bar priority int @@ -119,6 +121,10 @@ if b.runningBar != nil { b.priority = b.runningBar.priority + } + + if s.newLineExtendFn != nil { + b.bufNL = bytes.NewBuffer(make([]byte, 0, s.width)) } go b.serve(wg, s, cancel) @@ -283,39 +289,41 @@ } func (b *Bar) render(debugOut io.Writer, tw int, pSyncer, aSyncer *widthSyncer) { - var r io.Reader select { case b.operateState <- func(s *bState) { defer func() { - // recovering if external decorators panic + // recovering if user defined decorator panics for example if p := recover(); p != nil { s.panicMsg = fmt.Sprintf("panic: %v", p) - s.pDecorators = nil - s.aDecorators = nil - s.toComplete = true - // truncate panic msg to one tw line, if necessary - r = strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", tw), s.panicMsg)) fmt.Fprintf(debugOut, "%s %s bar id %02d %v\n", "[mpb]", time.Now(), s.id, s.panicMsg) + b.frameReaderCh <- &frameReader{ + Reader: strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", tw), s.panicMsg)), + toShutdown: true, + } } - b.frameReaderCh <- &frameReader{ - Reader: r, - toShutdown: s.toComplete && !s.completeFlushed, - removeOnComplete: s.removeOnComplete, - } - s.completeFlushed = s.toComplete }() - r = s.draw(tw, pSyncer, aSyncer) + r := s.draw(tw, pSyncer, aSyncer) + if s.newLineExtendFn != nil { + b.bufNL.Reset() + s.newLineExtendFn(s.completeFlushed, b.bufNL) + r = io.MultiReader(r, b.bufNL) + } + b.frameReaderCh <- &frameReader{ + Reader: r, + toShutdown: s.toComplete && !s.completeFlushed, + removeOnComplete: s.removeOnComplete, + } + s.completeFlushed = s.toComplete }: case <-b.done: s := b.cacheState - if s.panicMsg != "" { - r = strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", tw), s.panicMsg)) - } else { - r = s.draw(tw, pSyncer, aSyncer) - } - b.frameReaderCh <- &frameReader{ - Reader: r, - } + r := s.draw(tw, pSyncer, aSyncer) + if s.newLineExtendFn != nil { + b.bufNL.Reset() + s.newLineExtendFn(s.completeFlushed, b.bufNL) + r = io.MultiReader(r, b.bufNL) + } + b.frameReaderCh <- &frameReader{Reader: r} } } @@ -324,6 +332,10 @@ if termWidth <= 0 { termWidth = s.width + } + + if s.panicMsg != "" { + return strings.NewReader(fmt.Sprintf(fmt.Sprintf("%%.%ds\n", termWidth), s.panicMsg)) } stat := newStatistics(s) diff --git a/bar_option.go b/bar_option.go index 77b67e7..b64ed0c 100644 --- a/bar_option.go +++ b/bar_option.go @@ -1,6 +1,8 @@ package mpb import ( + "io" + "github.com/vbauerster/mpb/decor" ) @@ -101,6 +103,14 @@ } } +// BarNewLineExtend takes user defined efn, which is called each render cycle. +// Any write to provided writer w of efn, will appear on new line of respective bar. +func BarNewLineExtend(efn func(completed bool, w io.Writer)) BarOption { + return func(s *bState) { + s.newLineExtendFn = efn + } +} + func barWidth(w int) BarOption { return func(s *bState) { s.width = w diff --git a/examples/barNewLineExtend/main.go b/examples/barNewLineExtend/main.go new file mode 100644 index 0000000..b9f9f29 --- /dev/null +++ b/examples/barNewLineExtend/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "io" + "math/rand" + "sync" + "time" + + "github.com/vbauerster/mpb" + "github.com/vbauerster/mpb/decor" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func main() { + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg)) + total, numBars := 100, 3 + wg.Add(numBars) + + for i := 0; i < numBars; i++ { + name := fmt.Sprintf("Bar#%d:", i) + efn := func(completed bool, w io.Writer) { + if completed { + io.WriteString(w, name+" is completed!\n") + } + } + bar := p.AddBar(int64(total), mpb.BarNewLineExtend(efn), + mpb.PrependDecorators( + // simple name decorator + decor.Name(name), + // decor.DSyncWidth bit enables column width synchronization + decor.Percentage(decor.WCSyncSpace), + ), + mpb.AppendDecorators( + // replace ETA decorator with "done" message, OnComplete event + decor.OnComplete( + // ETA decorator with ewma age of 60 + decor.EwmaETA(decor.ET_STYLE_GO, 60), "done", + ), + ), + ) + // simulating some work + go func() { + defer wg.Done() + max := 100 * time.Millisecond + for i := 0; i < total; i++ { + start := time.Now() + time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) + // ewma based decorators require work duration measurement + bar.IncrBy(1, time.Since(start)) + } + }() + } + // wait for all bars to complete and flush + p.Wait() +} diff --git a/options.go b/options.go index b13bdbd..cb36bbd 100644 --- a/options.go +++ b/options.go @@ -87,12 +87,3 @@ s.debugOut = w } } - -// WithInterceptors provides a way to write to the underlying progress pool's -// writer. Could be useful if you want to output something below the bars, while -// they're rendering. -func WithInterceptors(interseptors ...func(io.Writer)) ProgressOption { - return func(s *pState) { - s.interceptors = interseptors - } -} diff --git a/progress.go b/progress.go index fecdb8a..5f1366e 100644 --- a/progress.go +++ b/progress.go @@ -47,7 +47,6 @@ uwg *sync.WaitGroup cancel <-chan struct{} shutdownNotifier chan struct{} - interceptors []func(io.Writer) waitBars map[*Bar]*Bar debugOut io.Writer } @@ -250,10 +249,6 @@ }() } - for _, interceptor := range s.interceptors { - interceptor(s.cw) - } - if e := s.cw.Flush(); err == nil { err = e }