master
1package progress_bar
2
3import (
4 "fmt"
5 "os"
6 "strings"
7 "sync"
8 "sync/atomic"
9 "time"
10)
11
12type ProgressBar struct {
13 label string
14 total int64
15 current int64
16 stop chan struct{}
17 wg sync.WaitGroup
18}
19
20func NewProgressBar(label string, total int) *ProgressBar {
21 p := &ProgressBar{label: label, total: int64(total), stop: make(chan struct{})}
22 p.wg.Add(1)
23 go func() {
24 defer p.wg.Done()
25 ticker := time.NewTicker(100 * time.Millisecond)
26 defer ticker.Stop()
27 // initial draw
28 p.draw(atomic.LoadInt64(&p.current))
29 for {
30 select {
31 case <-p.stop:
32 return
33 case <-ticker.C:
34 cur := atomic.LoadInt64(&p.current)
35 if cur > p.total {
36 cur = p.total
37 }
38 p.draw(cur)
39 }
40 }
41 }()
42 return p
43}
44
45func (p *ProgressBar) Inc() {
46 for {
47 cur := atomic.LoadInt64(&p.current)
48 if cur >= p.total {
49 return
50 }
51 if atomic.CompareAndSwapInt64(&p.current, cur, cur+1) {
52 return
53 }
54 // retry on race
55 }
56}
57
58func (p *ProgressBar) Done() {
59 atomic.StoreInt64(&p.current, p.total)
60 close(p.stop)
61 p.wg.Wait()
62 p.draw(p.total)
63 _, _ = fmt.Fprintln(os.Stderr)
64}
65
66func (p *ProgressBar) draw(current int64) {
67 if p.total <= 0 {
68 return
69 }
70 percent := 0
71 if p.total > 0 {
72 percent = int(current * 100 / p.total)
73 }
74 barLen := 24
75 filled := 0
76 if p.total > 0 {
77 filled = int(current * int64(barLen) / p.total)
78 }
79 if filled > barLen {
80 filled = barLen
81 }
82 bar := strings.Repeat("#", filled) + strings.Repeat(" ", barLen-filled)
83 _, _ = fmt.Fprintf(os.Stderr, "\r[%s] %4d/%-4d (%3d%%) %s", bar, current, p.total, percent, p.label)
84}