Move decorators to decor package
Vladimir Bauer
9 years ago
| 6 | 6 | "sync" |
| 7 | 7 | "time" |
| 8 | 8 | "unicode/utf8" |
| 9 | ||
| 10 | "github.com/vbauerster/mpb/decor" | |
| 9 | 11 | ) |
| 10 | 12 | |
| 11 | 13 | const ( |
| 37 | 39 | state state |
| 38 | 40 | } |
| 39 | 41 | |
| 40 | // Statistics represents statistics of the progress bar. | |
| 41 | // Cantains: Total, Current, TimeElapsed, TimePerItemEstimate | |
| 42 | type Statistics struct { | |
| 43 | ID int | |
| 44 | Completed bool | |
| 45 | Aborted bool | |
| 46 | Total int | |
| 47 | Current int | |
| 48 | StartTime time.Time | |
| 49 | TimeElapsed time.Duration | |
| 50 | TimePerItemEstimate time.Duration | |
| 51 | } | |
| 52 | ||
| 53 | 42 | // Refil is a struct for b.IncrWithReFill |
| 54 | 43 | type refill struct { |
| 55 | 44 | char rune |
| 56 | 45 | till int |
| 57 | } | |
| 58 | ||
| 59 | // Eta returns exponential-weighted-moving-average ETA estimator | |
| 60 | func (s *Statistics) Eta() time.Duration { | |
| 61 | return time.Duration(s.Total-s.Current) * s.TimePerItemEstimate | |
| 62 | 46 | } |
| 63 | 47 | |
| 64 | 48 | type ( |
| 81 | 65 | timeElapsed time.Duration |
| 82 | 66 | blockStartTime time.Time |
| 83 | 67 | timePerItem time.Duration |
| 84 | appendFuncs []DecoratorFunc | |
| 85 | prependFuncs []DecoratorFunc | |
| 68 | appendFuncs []decor.DecoratorFunc | |
| 69 | prependFuncs []decor.DecoratorFunc | |
| 86 | 70 | simpleSpinner func() byte |
| 87 | 71 | refill *refill |
| 88 | // flushed chan struct{} | |
| 89 | 72 | } |
| 90 | 73 | ) |
| 91 | 74 | |
| 205 | 188 | } |
| 206 | 189 | } |
| 207 | 190 | |
| 208 | // Statistics returs *Statistics, which contains information like | |
| 209 | // Tottal, Current, TimeElapsed and TimePerItemEstimate | |
| 210 | func (b *Bar) Statistics() *Statistics { | |
| 211 | result := make(chan *Statistics, 1) | |
| 212 | select { | |
| 213 | case b.ops <- func(s *state) { result <- newStatistics(s) }: | |
| 214 | return <-result | |
| 215 | case <-b.done: | |
| 216 | return newStatistics(&b.state) | |
| 217 | } | |
| 218 | } | |
| 219 | ||
| 220 | // GetID returs id of the bar | |
| 221 | func (b *Bar) GetID() int { | |
| 191 | // ID returs id of the bar | |
| 192 | func (b *Bar) ID() int { | |
| 222 | 193 | result := make(chan int, 1) |
| 223 | 194 | select { |
| 224 | 195 | case b.ops <- func(s *state) { result <- s.id }: |
| 246 | 217 | func (b *Bar) Complete() { |
| 247 | 218 | select { |
| 248 | 219 | case <-b.completeReqCh: |
| 249 | return | |
| 250 | 220 | default: |
| 251 | 221 | close(b.completeReqCh) |
| 252 | 222 | } |
| 253 | 223 | } |
| 254 | 224 | |
| 225 | func (b *Bar) complete() { | |
| 226 | select { | |
| 227 | case b.ops <- func(s *state) { | |
| 228 | if !s.completed { | |
| 229 | b.Complete() | |
| 230 | } | |
| 231 | }: | |
| 232 | default: | |
| 233 | } | |
| 234 | } | |
| 235 | ||
| 255 | 236 | func (b *Bar) server(s state, wg *sync.WaitGroup, cancel <-chan struct{}) { |
| 256 | 237 | |
| 257 | 238 | defer func() { |
| 258 | 239 | b.state = s |
| 259 | // <-s.flushed | |
| 260 | // fmt.Fprintf(os.Stderr, "Bar:%d flushed\n", s.id) | |
| 240 | close(b.done) | |
| 261 | 241 | wg.Done() |
| 262 | close(b.done) | |
| 263 | 242 | }() |
| 264 | 243 | |
| 265 | 244 | for { |
| 276 | 255 | } |
| 277 | 256 | } |
| 278 | 257 | } |
| 279 | ||
| 280 | // func (b *Bar) render(tw int, flushed chan struct{}, prependWs, appendWs *widthSync) <-chan []byte { | |
| 281 | // ch := make(chan []byte) | |
| 282 | ||
| 283 | // go func() { | |
| 284 | // defer func() { | |
| 285 | // // recovering if external decorators panic | |
| 286 | // if p := recover(); p != nil { | |
| 287 | // ch <- []byte(fmt.Sprintln(p)) | |
| 288 | // } | |
| 289 | // close(ch) | |
| 290 | // }() | |
| 291 | // result := make(chan []byte, 1) | |
| 292 | // select { | |
| 293 | // case b.ops <- func(s *state) { | |
| 294 | // buf := draw(s, tw, prependWs, appendWs) | |
| 295 | // buf = append(buf, '\n') | |
| 296 | // result <- buf | |
| 297 | // // wait for flushed | |
| 298 | // if s.completed { | |
| 299 | // <-flushed | |
| 300 | // b.Complete() | |
| 301 | // } | |
| 302 | // }: | |
| 303 | // ch <- <-result | |
| 304 | // case <-b.done: | |
| 305 | // buf := draw(&b.state, tw, prependWs, appendWs) | |
| 306 | // buf = append(buf, '\n') | |
| 307 | // ch <- buf | |
| 308 | // default: | |
| 309 | // ch <- []byte{} | |
| 310 | // } | |
| 311 | // }() | |
| 312 | ||
| 313 | // return ch | |
| 314 | // } | |
| 315 | 258 | |
| 316 | 259 | func (b *Bar) render(tw int, flushed chan struct{}, prependWs, appendWs *widthSync) <-chan []byte { |
| 317 | 260 | ch := make(chan []byte) |
| 472 | 415 | return buf |
| 473 | 416 | } |
| 474 | 417 | |
| 475 | func newStatistics(s *state) *Statistics { | |
| 476 | return &Statistics{ | |
| 418 | func newStatistics(s *state) *decor.Statistics { | |
| 419 | return &decor.Statistics{ | |
| 477 | 420 | ID: s.id, |
| 478 | 421 | Completed: s.completed, |
| 479 | 422 | Aborted: s.aborted, |
| 0 | package decor | |
| 1 | ||
| 2 | import ( | |
| 3 | "fmt" | |
| 4 | "math" | |
| 5 | "time" | |
| 6 | "unicode/utf8" | |
| 7 | ) | |
| 8 | ||
| 9 | const ( | |
| 10 | // DidentRight specifies identation direction. | |
| 11 | // | foo| b| Without DidentRight | |
| 12 | // |foo |b | With DidentRight | |
| 13 | DidentRight = 1 << iota | |
| 14 | ||
| 15 | // DwidthSync will auto sync max width | |
| 16 | DwidthSync | |
| 17 | ||
| 18 | // DextraSpace adds extra space, makes sence with DwidthSync only. | |
| 19 | // When DidentRight bit set, the space will be added to the right, | |
| 20 | // otherwise to the left. | |
| 21 | DextraSpace | |
| 22 | ) | |
| 23 | ||
| 24 | // Statistics represents statistics of the progress bar. | |
| 25 | // Cantains: Total, Current, TimeElapsed, TimePerItemEstimate | |
| 26 | type Statistics struct { | |
| 27 | ID int | |
| 28 | Completed bool | |
| 29 | Aborted bool | |
| 30 | Total int | |
| 31 | Current int | |
| 32 | StartTime time.Time | |
| 33 | TimeElapsed time.Duration | |
| 34 | TimePerItemEstimate time.Duration | |
| 35 | } | |
| 36 | ||
| 37 | // Eta returns exponential-weighted-moving-average ETA estimator | |
| 38 | func (s *Statistics) Eta() time.Duration { | |
| 39 | return time.Duration(s.Total-s.Current) * s.TimePerItemEstimate | |
| 40 | } | |
| 41 | ||
| 42 | // DecoratorFunc is a function that can be prepended and appended to the progress bar | |
| 43 | type DecoratorFunc func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string | |
| 44 | ||
| 45 | func Name(name string, minWidth int, conf byte) DecoratorFunc { | |
| 46 | format := "%%" | |
| 47 | if (conf & DidentRight) != 0 { | |
| 48 | format += "-" | |
| 49 | } | |
| 50 | format += "%ds" | |
| 51 | return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { | |
| 52 | if (conf & DwidthSync) != 0 { | |
| 53 | myWidth <- utf8.RuneCountInString(name) | |
| 54 | max := <-maxWidth | |
| 55 | if (conf & DextraSpace) != 0 { | |
| 56 | max++ | |
| 57 | } | |
| 58 | return fmt.Sprintf(fmt.Sprintf(format, max), name) | |
| 59 | } | |
| 60 | return fmt.Sprintf(fmt.Sprintf(format, minWidth), name) | |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | func Counters(pairFormat string, unit Units, minWidth int, conf byte) DecoratorFunc { | |
| 65 | format := "%%" | |
| 66 | if (conf & DidentRight) != 0 { | |
| 67 | format += "-" | |
| 68 | } | |
| 69 | format += "%ds" | |
| 70 | return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { | |
| 71 | current := Format(s.Current).To(unit) | |
| 72 | total := Format(s.Total).To(unit) | |
| 73 | str := fmt.Sprintf(pairFormat, current, total) | |
| 74 | if (conf & DwidthSync) != 0 { | |
| 75 | myWidth <- utf8.RuneCountInString(str) | |
| 76 | max := <-maxWidth | |
| 77 | if (conf & DextraSpace) != 0 { | |
| 78 | max++ | |
| 79 | } | |
| 80 | return fmt.Sprintf(fmt.Sprintf(format, max), str) | |
| 81 | } | |
| 82 | return fmt.Sprintf(fmt.Sprintf(format, minWidth), str) | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | func ETA(minWidth int, conf byte) DecoratorFunc { | |
| 87 | format := "%%" | |
| 88 | if (conf & DidentRight) != 0 { | |
| 89 | format += "-" | |
| 90 | } | |
| 91 | format += "%ds" | |
| 92 | return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { | |
| 93 | str := fmt.Sprint(time.Duration(s.Eta().Seconds()) * time.Second) | |
| 94 | if (conf & DwidthSync) != 0 { | |
| 95 | myWidth <- utf8.RuneCountInString(str) | |
| 96 | max := <-maxWidth | |
| 97 | if (conf & DextraSpace) != 0 { | |
| 98 | max++ | |
| 99 | } | |
| 100 | return fmt.Sprintf(fmt.Sprintf(format, max), str) | |
| 101 | } | |
| 102 | return fmt.Sprintf(fmt.Sprintf(format, minWidth), str) | |
| 103 | } | |
| 104 | } | |
| 105 | ||
| 106 | func Elapsed(minWidth int, conf byte) DecoratorFunc { | |
| 107 | format := "%%" | |
| 108 | if (conf & DidentRight) != 0 { | |
| 109 | format += "-" | |
| 110 | } | |
| 111 | format += "%ds" | |
| 112 | return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { | |
| 113 | str := fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second) | |
| 114 | if (conf & DwidthSync) != 0 { | |
| 115 | myWidth <- utf8.RuneCountInString(str) | |
| 116 | max := <-maxWidth | |
| 117 | if (conf & DextraSpace) != 0 { | |
| 118 | max++ | |
| 119 | } | |
| 120 | return fmt.Sprintf(fmt.Sprintf(format, max), str) | |
| 121 | } | |
| 122 | return fmt.Sprintf(fmt.Sprintf(format, minWidth), str) | |
| 123 | } | |
| 124 | } | |
| 125 | ||
| 126 | func Percentage(minWidth int, conf byte) DecoratorFunc { | |
| 127 | format := "%%" | |
| 128 | if (conf & DidentRight) != 0 { | |
| 129 | format += "-" | |
| 130 | } | |
| 131 | format += "%ds" | |
| 132 | return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { | |
| 133 | str := fmt.Sprintf("%d %%", percentage(s.Total, s.Current, 100)) | |
| 134 | if (conf & DwidthSync) != 0 { | |
| 135 | myWidth <- utf8.RuneCountInString(str) | |
| 136 | max := <-maxWidth | |
| 137 | if (conf & DextraSpace) != 0 { | |
| 138 | max++ | |
| 139 | } | |
| 140 | return fmt.Sprintf(fmt.Sprintf(format, max), str) | |
| 141 | } | |
| 142 | return fmt.Sprintf(fmt.Sprintf(format, minWidth), str) | |
| 143 | } | |
| 144 | } | |
| 145 | ||
| 146 | func percentage(total, current, ratio int) int { | |
| 147 | if total == 0 || current > total { | |
| 148 | return 0 | |
| 149 | } | |
| 150 | num := float64(ratio) * float64(current) / float64(total) | |
| 151 | ceil := math.Ceil(num) | |
| 152 | diff := ceil - num | |
| 153 | // num = 2.34 will return 2 | |
| 154 | // num = 2.44 will return 3 | |
| 155 | if math.Max(diff, 0.6) == diff { | |
| 156 | return int(num) | |
| 157 | } | |
| 158 | return int(ceil) | |
| 159 | } |
| 0 | package decor | |
| 1 | ||
| 2 | import "fmt" | |
| 3 | ||
| 4 | const ( | |
| 5 | _ = iota | |
| 6 | bytesInKiB = 1 << (iota * 10) | |
| 7 | bytesInMiB | |
| 8 | bytesInGiB | |
| 9 | bytesInTiB | |
| 10 | ) | |
| 11 | ||
| 12 | const ( | |
| 13 | bytesInKb = 1000 | |
| 14 | bytesInMB = bytesInKb * 1000 | |
| 15 | bytesInGB = bytesInMB * 1000 | |
| 16 | bytesInTB = bytesInGB * 1000 | |
| 17 | ) | |
| 18 | ||
| 19 | const ( | |
| 20 | // Kibibyte = 1024 b | |
| 21 | Unit_KiB = iota | |
| 22 | // Kilobyte = 1000 b | |
| 23 | Unit_kB | |
| 24 | ) | |
| 25 | ||
| 26 | type Units uint | |
| 27 | ||
| 28 | func Format(i int) *formatter { | |
| 29 | return &formatter{n: i} | |
| 30 | } | |
| 31 | ||
| 32 | type formatter struct { | |
| 33 | n int | |
| 34 | unit Units | |
| 35 | width int | |
| 36 | } | |
| 37 | ||
| 38 | func (f *formatter) To(unit Units) *formatter { | |
| 39 | f.unit = unit | |
| 40 | return f | |
| 41 | } | |
| 42 | ||
| 43 | func (f *formatter) Width(width int) *formatter { | |
| 44 | f.width = width | |
| 45 | return f | |
| 46 | } | |
| 47 | ||
| 48 | func (f *formatter) String() string { | |
| 49 | switch f.unit { | |
| 50 | case Unit_KiB: | |
| 51 | return formatKiB(f.n) | |
| 52 | case Unit_kB: | |
| 53 | return formatKB(f.n) | |
| 54 | default: | |
| 55 | return fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) | |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | func formatKiB(i int) (result string) { | |
| 60 | switch { | |
| 61 | case i >= bytesInTiB: | |
| 62 | result = fmt.Sprintf("%.1fTiB", float64(i)/bytesInTiB) | |
| 63 | case i >= bytesInGiB: | |
| 64 | result = fmt.Sprintf("%.1fGiB", float64(i)/bytesInGiB) | |
| 65 | case i >= bytesInMiB: | |
| 66 | result = fmt.Sprintf("%.1fMiB", float64(i)/bytesInMiB) | |
| 67 | case i >= bytesInKiB: | |
| 68 | result = fmt.Sprintf("%.1fKiB", float64(i)/bytesInKiB) | |
| 69 | default: | |
| 70 | result = fmt.Sprintf("%db", i) | |
| 71 | } | |
| 72 | return | |
| 73 | } | |
| 74 | ||
| 75 | func formatKB(i int) (result string) { | |
| 76 | switch { | |
| 77 | case i >= bytesInTB: | |
| 78 | result = fmt.Sprintf("%.1fTB", float64(i)/bytesInTB) | |
| 79 | case i >= bytesInGB: | |
| 80 | result = fmt.Sprintf("%.1fGB", float64(i)/bytesInGB) | |
| 81 | case i >= bytesInMB: | |
| 82 | result = fmt.Sprintf("%.1fMB", float64(i)/bytesInMB) | |
| 83 | case i >= bytesInKb: | |
| 84 | result = fmt.Sprintf("%.1fkB", float64(i)/bytesInKb) | |
| 85 | default: | |
| 86 | result = fmt.Sprintf("%db", i) | |
| 87 | } | |
| 88 | return | |
| 89 | } |
| 6 | 6 | "time" |
| 7 | 7 | |
| 8 | 8 | "github.com/vbauerster/mpb" |
| 9 | "github.com/vbauerster/mpb/decor" | |
| 9 | 10 | ) |
| 10 | 11 | |
| 11 | 12 | const ( |
| 26 | 27 | if i != 1 { |
| 27 | 28 | name = fmt.Sprintf("Bar#%d:", i) |
| 28 | 29 | } |
| 29 | b := p.AddBar(int64(total), | |
| 30 | b := p.AddBar(total, | |
| 30 | 31 | mpb.PrependDecorators( |
| 31 | mpb.Name(name, 0, mpb.DwidthSync|mpb.DidentRight), | |
| 32 | mpb.ETA(4, mpb.DwidthSync|mpb.DextraSpace), | |
| 32 | decor.Name(name, 0, decor.DwidthSync|decor.DidentRight), | |
| 33 | decor.ETA(4, decor.DwidthSync|decor.DextraSpace), | |
| 33 | 34 | ), |
| 34 | 35 | mpb.AppendDecorators( |
| 35 | mpb.Percentage(5, 0), | |
| 36 | decor.Percentage(5, 0), | |
| 36 | 37 | ), |
| 37 | 38 | ) |
| 38 | 39 | go func() { |