diff --git a/bar.go b/bar.go index e7b1956..567dd69 100644 --- a/bar.go +++ b/bar.go @@ -7,6 +7,8 @@ "sync" "time" "unicode/utf8" + + "github.com/vbauerster/mpb/decor" ) const ( @@ -38,28 +40,10 @@ state state } -// Statistics represents statistics of the progress bar. -// Cantains: Total, Current, TimeElapsed, TimePerItemEstimate -type Statistics struct { - ID int - Completed bool - Aborted bool - Total int - Current int - StartTime time.Time - TimeElapsed time.Duration - TimePerItemEstimate time.Duration -} - // Refil is a struct for b.IncrWithReFill type refill struct { char rune till int -} - -// Eta returns exponential-weighted-moving-average ETA estimator -func (s *Statistics) Eta() time.Duration { - return time.Duration(s.Total-s.Current) * s.TimePerItemEstimate } type ( @@ -82,11 +66,10 @@ timeElapsed time.Duration blockStartTime time.Time timePerItem time.Duration - appendFuncs []DecoratorFunc - prependFuncs []DecoratorFunc + appendFuncs []decor.DecoratorFunc + prependFuncs []decor.DecoratorFunc simpleSpinner func() byte refill *refill - // flushed chan struct{} } ) @@ -206,20 +189,8 @@ } } -// Statistics returs *Statistics, which contains information like -// Tottal, Current, TimeElapsed and TimePerItemEstimate -func (b *Bar) Statistics() *Statistics { - result := make(chan *Statistics, 1) - select { - case b.ops <- func(s *state) { result <- newStatistics(s) }: - return <-result - case <-b.done: - return newStatistics(&b.state) - } -} - -// GetID returs id of the bar -func (b *Bar) GetID() int { +// ID returs id of the bar +func (b *Bar) ID() int { result := make(chan int, 1) select { case b.ops <- func(s *state) { result <- s.id }: @@ -247,20 +218,28 @@ func (b *Bar) Complete() { select { case <-b.completeReqCh: - return default: close(b.completeReqCh) } } +func (b *Bar) complete() { + select { + case b.ops <- func(s *state) { + if !s.completed { + b.Complete() + } + }: + default: + } +} + func (b *Bar) server(s state, wg *sync.WaitGroup, cancel <-chan struct{}) { defer func() { b.state = s - // <-s.flushed - // fmt.Fprintf(os.Stderr, "Bar:%d flushed\n", s.id) + close(b.done) wg.Done() - close(b.done) }() for { @@ -277,42 +256,6 @@ } } } - -// func (b *Bar) render(tw int, flushed chan struct{}, prependWs, appendWs *widthSync) <-chan []byte { -// ch := make(chan []byte) - -// go func() { -// defer func() { -// // recovering if external decorators panic -// if p := recover(); p != nil { -// ch <- []byte(fmt.Sprintln(p)) -// } -// close(ch) -// }() -// result := make(chan []byte, 1) -// select { -// case b.ops <- func(s *state) { -// buf := draw(s, tw, prependWs, appendWs) -// buf = append(buf, '\n') -// result <- buf -// // wait for flushed -// if s.completed { -// <-flushed -// b.Complete() -// } -// }: -// ch <- <-result -// case <-b.done: -// buf := draw(&b.state, tw, prependWs, appendWs) -// buf = append(buf, '\n') -// ch <- buf -// default: -// ch <- []byte{} -// } -// }() - -// return ch -// } func (b *Bar) render(tw int, flushed chan struct{}, prependWs, appendWs *widthSync) <-chan []byte { ch := make(chan []byte) @@ -473,8 +416,8 @@ return buf } -func newStatistics(s *state) *Statistics { - return &Statistics{ +func newStatistics(s *state) *decor.Statistics { + return &decor.Statistics{ ID: s.id, Completed: s.completed, Aborted: s.aborted, diff --git a/decor/decorators.go b/decor/decorators.go new file mode 100644 index 0000000..6356da3 --- /dev/null +++ b/decor/decorators.go @@ -0,0 +1,160 @@ +package decor + +import ( + "fmt" + "math" + "time" + "unicode/utf8" +) + +const ( + // DidentRight specifies identation direction. + // | foo| b| Without DidentRight + // |foo |b | With DidentRight + DidentRight = 1 << iota + + // DwidthSync will auto sync max width + DwidthSync + + // DextraSpace adds extra space, makes sence with DwidthSync only. + // When DidentRight bit set, the space will be added to the right, + // otherwise to the left. + DextraSpace +) + +// Statistics represents statistics of the progress bar. +// Cantains: Total, Current, TimeElapsed, TimePerItemEstimate +type Statistics struct { + ID int + Completed bool + Aborted bool + Total int + Current int + StartTime time.Time + TimeElapsed time.Duration + TimePerItemEstimate time.Duration +} + +// Eta returns exponential-weighted-moving-average ETA estimator +func (s *Statistics) Eta() time.Duration { + return time.Duration(s.Total-s.Current) * s.TimePerItemEstimate +} + +// DecoratorFunc is a function that can be prepended and appended to the progress bar +type DecoratorFunc func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string + +func Name(name string, minWidth int, conf byte) DecoratorFunc { + format := "%%" + if (conf & DidentRight) != 0 { + format += "-" + } + format += "%ds" + return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { + if (conf & DwidthSync) != 0 { + myWidth <- utf8.RuneCountInString(name) + max := <-maxWidth + if (conf & DextraSpace) != 0 { + max++ + } + return fmt.Sprintf(fmt.Sprintf(format, max), name) + } + return fmt.Sprintf(fmt.Sprintf(format, minWidth), name) + } +} + +func Counters(pairFormat string, unit Units, minWidth int, conf byte) DecoratorFunc { + format := "%%" + if (conf & DidentRight) != 0 { + format += "-" + } + format += "%ds" + return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { + current := Format(s.Current).To(unit) + total := Format(s.Total).To(unit) + str := fmt.Sprintf(pairFormat, current, total) + if (conf & DwidthSync) != 0 { + myWidth <- utf8.RuneCountInString(str) + max := <-maxWidth + if (conf & DextraSpace) != 0 { + max++ + } + return fmt.Sprintf(fmt.Sprintf(format, max), str) + } + return fmt.Sprintf(fmt.Sprintf(format, minWidth), str) + } +} + +func ETA(minWidth int, conf byte) DecoratorFunc { + format := "%%" + if (conf & DidentRight) != 0 { + format += "-" + } + format += "%ds" + return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { + str := fmt.Sprint(time.Duration(s.Eta().Seconds()) * time.Second) + if (conf & DwidthSync) != 0 { + myWidth <- utf8.RuneCountInString(str) + max := <-maxWidth + if (conf & DextraSpace) != 0 { + max++ + } + return fmt.Sprintf(fmt.Sprintf(format, max), str) + } + return fmt.Sprintf(fmt.Sprintf(format, minWidth), str) + } +} + +func Elapsed(minWidth int, conf byte) DecoratorFunc { + format := "%%" + if (conf & DidentRight) != 0 { + format += "-" + } + format += "%ds" + return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { + str := fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second) + if (conf & DwidthSync) != 0 { + myWidth <- utf8.RuneCountInString(str) + max := <-maxWidth + if (conf & DextraSpace) != 0 { + max++ + } + return fmt.Sprintf(fmt.Sprintf(format, max), str) + } + return fmt.Sprintf(fmt.Sprintf(format, minWidth), str) + } +} + +func Percentage(minWidth int, conf byte) DecoratorFunc { + format := "%%" + if (conf & DidentRight) != 0 { + format += "-" + } + format += "%ds" + return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { + str := fmt.Sprintf("%d %%", percentage(s.Total, s.Current, 100)) + if (conf & DwidthSync) != 0 { + myWidth <- utf8.RuneCountInString(str) + max := <-maxWidth + if (conf & DextraSpace) != 0 { + max++ + } + return fmt.Sprintf(fmt.Sprintf(format, max), str) + } + return fmt.Sprintf(fmt.Sprintf(format, minWidth), str) + } +} + +func percentage(total, current, ratio int) int { + if total == 0 || current > total { + return 0 + } + num := float64(ratio) * float64(current) / float64(total) + ceil := math.Ceil(num) + diff := ceil - num + // num = 2.34 will return 2 + // num = 2.44 will return 3 + if math.Max(diff, 0.6) == diff { + return int(num) + } + return int(ceil) +} diff --git a/decor/format.go b/decor/format.go new file mode 100644 index 0000000..97d361a --- /dev/null +++ b/decor/format.go @@ -0,0 +1,90 @@ +package decor + +import "fmt" + +const ( + _ = iota + bytesInKiB = 1 << (iota * 10) + bytesInMiB + bytesInGiB + bytesInTiB +) + +const ( + bytesInKb = 1000 + bytesInMB = bytesInKb * 1000 + bytesInGB = bytesInMB * 1000 + bytesInTB = bytesInGB * 1000 +) + +const ( + // Kibibyte = 1024 b + Unit_KiB = iota + // Kilobyte = 1000 b + Unit_kB +) + +type Units uint + +func Format(i int) *formatter { + return &formatter{n: i} +} + +type formatter struct { + n int + unit Units + width int +} + +func (f *formatter) To(unit Units) *formatter { + f.unit = unit + return f +} + +func (f *formatter) Width(width int) *formatter { + f.width = width + return f +} + +func (f *formatter) String() string { + switch f.unit { + case Unit_KiB: + return formatKiB(f.n) + case Unit_kB: + return formatKB(f.n) + default: + return fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) + } +} + +func formatKiB(i int) (result string) { + switch { + case i >= bytesInTiB: + result = fmt.Sprintf("%.1fTiB", float64(i)/bytesInTiB) + case i >= bytesInGiB: + result = fmt.Sprintf("%.1fGiB", float64(i)/bytesInGiB) + case i >= bytesInMiB: + result = fmt.Sprintf("%.1fMiB", float64(i)/bytesInMiB) + case i >= bytesInKiB: + result = fmt.Sprintf("%.1fKiB", float64(i)/bytesInKiB) + default: + result = fmt.Sprintf("%db", i) + } + return +} + +func formatKB(i int) (result string) { + switch { + case i >= bytesInTB: + result = fmt.Sprintf("%.1fTB", float64(i)/bytesInTB) + case i >= bytesInGB: + result = fmt.Sprintf("%.1fGB", float64(i)/bytesInGB) + case i >= bytesInMB: + result = fmt.Sprintf("%.1fMB", float64(i)/bytesInMB) + case i >= bytesInKb: + result = fmt.Sprintf("%.1fkB", float64(i)/bytesInKb) + default: + result = fmt.Sprintf("%db", i) + } + return +} diff --git a/example/prependETA/main.go b/example/prependETA/main.go index ae8cebd..b4595dd 100644 --- a/example/prependETA/main.go +++ b/example/prependETA/main.go @@ -7,6 +7,7 @@ "time" "github.com/vbauerster/mpb" + "github.com/vbauerster/mpb/decor" ) const ( @@ -27,13 +28,13 @@ if i != 1 { name = fmt.Sprintf("Bar#%d:", i) } - b := p.AddBar(int64(total), + b := p.AddBar(total, mpb.PrependDecorators( - mpb.Name(name, 0, mpb.DwidthSync|mpb.DidentRight), - mpb.ETA(4, mpb.DwidthSync|mpb.DextraSpace), + decor.Name(name, 0, decor.DwidthSync|decor.DidentRight), + decor.ETA(4, decor.DwidthSync|decor.DextraSpace), ), mpb.AppendDecorators( - mpb.Percentage(5, 0), + decor.Percentage(5, 0), ), ) go func() { diff --git a/progress.go b/progress.go index 2944182..9c40923 100644 --- a/progress.go +++ b/progress.go @@ -150,10 +150,7 @@ // complete Total unknown bars p.ops <- func(c *pConf) { for _, b := range c.bars { - s := b.Statistics() - if !s.Completed && !s.Aborted { - b.Complete() - } + b.complete() } } // wait for all bars to quit