| 5 | 5 |
"time"
|
| 6 | 6 |
)
|
| 7 | 7 |
|
| 8 | |
var (
|
| 9 | |
// Fill is the default character representing completed progress
|
| 10 | |
Fill byte = '='
|
| 11 | |
|
| 12 | |
// Head is the default character that moves when progress is updated
|
| 13 | |
Head byte = '>'
|
| 14 | |
|
| 15 | |
// Empty is the default character that represents the empty progress
|
| 16 | |
Empty byte = '-'
|
| 17 | |
|
| 18 | |
// LeftEnd is the default character in the left most part of the progress indicator
|
| 19 | |
LeftEnd byte = '['
|
| 20 | |
|
| 21 | |
// RightEnd is the default character in the right most part of the progress indicator
|
| 22 | |
RightEnd byte = ']'
|
| 23 | |
|
| 24 | |
// Width is the default width of the progress bar
|
| 25 | |
Width = 70
|
| 26 | |
)
|
| 27 | |
|
| 28 | |
// DecoratorFunc is a function that can be prepended and appended to the progress bar
|
| 29 | |
type DecoratorFunc func(s *Statistics) string
|
| 30 | |
|
| 31 | 8 |
type decoratorFuncType uint
|
| 32 | 9 |
|
| 33 | 10 |
const (
|
|
| 35 | 12 |
decoratorPrepend
|
| 36 | 13 |
)
|
| 37 | 14 |
|
|
15 |
// DecoratorFunc is a function that can be prepended and appended to the progress bar
|
|
16 |
type DecoratorFunc func(s *Statistics) string
|
|
17 |
|
| 38 | 18 |
type decorator struct {
|
| 39 | 19 |
kind decoratorFuncType
|
| 40 | 20 |
f DecoratorFunc
|
| 41 | 21 |
}
|
| 42 | 22 |
|
| 43 | |
// Bar represents a progress bar
|
|
23 |
// Bar represents a progress Bar
|
| 44 | 24 |
type Bar struct {
|
| 45 | |
// total of the total for the progress bar
|
| 46 | |
total int
|
| 47 | |
|
| 48 | |
// LeftEnd is character in the left most part of the progress indicator. Defaults to '['
|
| 49 | |
LeftEnd byte
|
| 50 | |
|
| 51 | |
// RightEnd is character in the right most part of the progress indicator. Defaults to ']'
|
| 52 | |
RightEnd byte
|
| 53 | |
|
| 54 | |
// Fill is the character representing completed progress. Defaults to '='
|
| 55 | |
Fill byte
|
| 56 | |
|
| 57 | |
// Head is the character that moves when progress is updated. Defaults to '>'
|
| 58 | |
Head byte
|
| 59 | |
|
| 60 | |
// Empty is the character that represents the empty progress. Default is '-'
|
| 61 | |
Empty byte
|
| 62 | |
|
| 63 | |
// Width is the width of the progress bar
|
| 64 | |
Width int
|
| 65 | |
|
| 66 | |
Alpha float64
|
| 67 | |
|
| 68 | |
incrRequestCh chan *incrRequest
|
| 69 | |
incrCh chan int
|
| 70 | |
|
|
25 |
total int
|
|
26 |
width int
|
|
27 |
alpha float64
|
|
28 |
stopped bool
|
|
29 |
|
|
30 |
fill byte
|
|
31 |
empty byte
|
|
32 |
tip byte
|
|
33 |
leftEnd byte
|
|
34 |
rightEnd byte
|
|
35 |
|
|
36 |
incrCh chan int
|
| 71 | 37 |
redrawRequestCh chan *redrawRequest
|
| 72 | |
|
| 73 | |
decoratorCh chan *decorator
|
|
38 |
decoratorCh chan *decorator
|
|
39 |
flushedCh chan struct{}
|
|
40 |
stopCh chan struct{}
|
|
41 |
done chan struct{}
|
| 74 | 42 |
|
| 75 | 43 |
timePerItemEstimate time.Duration
|
| 76 | |
|
| 77 | |
flushedCh chan struct{}
|
| 78 | |
|
| 79 | |
stopCh chan struct{}
|
| 80 | |
done chan struct{}
|
| 81 | |
|
| 82 | |
stopped bool
|
| 83 | 44 |
}
|
| 84 | 45 |
|
| 85 | 46 |
type Statistics struct {
|
|
| 91 | 52 |
bufCh chan []byte
|
| 92 | 53 |
}
|
| 93 | 54 |
|
| 94 | |
type incrRequest struct {
|
| 95 | |
amount int
|
| 96 | |
result chan bool
|
| 97 | |
}
|
| 98 | |
|
| 99 | |
// NewBar returns a new progress bar
|
| 100 | |
func newBar(total int, wg *sync.WaitGroup) *Bar {
|
|
55 |
func newBar(total, width int, wg *sync.WaitGroup) *Bar {
|
| 101 | 56 |
b := &Bar{
|
| 102 | |
Alpha: 0.25,
|
|
57 |
fill: '=',
|
|
58 |
empty: '-',
|
|
59 |
tip: '>',
|
|
60 |
leftEnd: '[',
|
|
61 |
rightEnd: ']',
|
|
62 |
alpha: 0.25,
|
| 103 | 63 |
total: total,
|
| 104 | |
Width: Width,
|
| 105 | |
LeftEnd: LeftEnd,
|
| 106 | |
RightEnd: RightEnd,
|
| 107 | |
Head: Head,
|
| 108 | |
Fill: Fill,
|
| 109 | |
Empty: Empty,
|
| 110 | |
incrRequestCh: make(chan *incrRequest),
|
|
64 |
width: width,
|
|
65 |
incrCh: make(chan int),
|
| 111 | 66 |
redrawRequestCh: make(chan *redrawRequest),
|
| 112 | 67 |
decoratorCh: make(chan *decorator),
|
| 113 | 68 |
flushedCh: make(chan struct{}),
|
| 114 | 69 |
stopCh: make(chan struct{}),
|
| 115 | 70 |
done: make(chan struct{}),
|
| 116 | |
incrCh: make(chan int),
|
| 117 | 71 |
}
|
| 118 | 72 |
go b.server(wg)
|
| 119 | 73 |
return b
|
|
74 |
}
|
|
75 |
|
|
76 |
// SetWidth sets width of the bar
|
|
77 |
func (b *Bar) SetWidth(n int) *Bar {
|
|
78 |
if n <= 0 {
|
|
79 |
return b
|
|
80 |
}
|
|
81 |
b.width = n
|
|
82 |
return b
|
|
83 |
}
|
|
84 |
|
|
85 |
// SetFill sets character representing completed progress.
|
|
86 |
// Defaults to '='
|
|
87 |
func (b *Bar) SetFill(c byte) *Bar {
|
|
88 |
b.fill = c
|
|
89 |
return b
|
|
90 |
}
|
|
91 |
|
|
92 |
// SetTip sets character representing tip of progress.
|
|
93 |
// Defaults to '>'
|
|
94 |
func (b *Bar) SetTip(c byte) *Bar {
|
|
95 |
b.tip = c
|
|
96 |
return b
|
|
97 |
}
|
|
98 |
|
|
99 |
// SetEmpty sets character representing the empty progress
|
|
100 |
// Defaults to '-'
|
|
101 |
func (b *Bar) SetEmpty(c byte) *Bar {
|
|
102 |
b.empty = c
|
|
103 |
return b
|
|
104 |
}
|
|
105 |
|
|
106 |
// SetLeftEnd sets character representing the left most border
|
|
107 |
// Defaults to '['
|
|
108 |
func (b *Bar) SetLeftEnd(c byte) *Bar {
|
|
109 |
b.leftEnd = c
|
|
110 |
return b
|
|
111 |
}
|
|
112 |
|
|
113 |
// SetRightEnd sets character representing the right most border
|
|
114 |
// Defaults to ']'
|
|
115 |
func (b *Bar) SetRightEnd(c byte) *Bar {
|
|
116 |
b.rightEnd = c
|
|
117 |
return b
|
|
118 |
}
|
|
119 |
|
|
120 |
// SetEtaAlpha sets alfa for exponential-weighted-moving-average ETA estimator
|
|
121 |
// Defaults to 0.25
|
|
122 |
// Normally you shouldn't touch this
|
|
123 |
func (b *Bar) SetEtaAlpha(a float64) *Bar {
|
|
124 |
b.alpha = a
|
|
125 |
return b
|
|
126 |
}
|
|
127 |
|
|
128 |
// String returns the string representation of the bar
|
|
129 |
func (b *Bar) String() string {
|
|
130 |
bufCh := make(chan []byte)
|
|
131 |
b.redrawRequestCh <- &redrawRequest{bufCh}
|
|
132 |
return string(<-bufCh)
|
|
133 |
}
|
|
134 |
|
|
135 |
func (b *Bar) Incr(n int) {
|
|
136 |
if !b.IsCompleted() {
|
|
137 |
b.incrCh <- n
|
|
138 |
}
|
|
139 |
}
|
|
140 |
|
|
141 |
func (b *Bar) Stop() {
|
|
142 |
if !b.stopped {
|
|
143 |
b.stopCh <- struct{}{}
|
|
144 |
b.stopped = true
|
|
145 |
}
|
|
146 |
}
|
|
147 |
|
|
148 |
func (b *Bar) IsCompleted() bool {
|
|
149 |
select {
|
|
150 |
case <-b.done:
|
|
151 |
return true
|
|
152 |
default:
|
|
153 |
return false
|
|
154 |
}
|
| 120 | 155 |
}
|
| 121 | 156 |
|
| 122 | 157 |
func (b *Bar) PrependFunc(f DecoratorFunc) *Bar {
|
|
| 153 | 188 |
return b
|
| 154 | 189 |
}
|
| 155 | 190 |
|
| 156 | |
// String returns the string representation of the bar
|
| 157 | |
func (b *Bar) String() string {
|
| 158 | |
bufCh := make(chan []byte)
|
| 159 | |
b.redrawRequestCh <- &redrawRequest{bufCh}
|
| 160 | |
return string(<-bufCh)
|
| 161 | |
}
|
| 162 | |
|
| 163 | |
func (b *Bar) flushed() {
|
| 164 | |
b.flushedCh <- struct{}{}
|
| 165 | |
}
|
| 166 | |
|
| 167 | |
func (b *Bar) Incr(n int) {
|
| 168 | |
if !b.IsCompleted() {
|
| 169 | |
b.incrCh <- n
|
| 170 | |
}
|
| 171 | |
}
|
| 172 | |
|
| 173 | 191 |
func (b *Bar) server(wg *sync.WaitGroup) {
|
| 174 | 192 |
var completed int
|
| 175 | 193 |
blockStartTime := time.Now()
|
| 176 | |
buf := make([]byte, b.Width, b.Width+24)
|
|
194 |
buf := make([]byte, b.width, b.width+24)
|
| 177 | 195 |
var appendFuncs []DecoratorFunc
|
| 178 | 196 |
var prependFuncs []DecoratorFunc
|
| 179 | 197 |
var done bool
|
|
| 220 | 238 |
}
|
| 221 | 239 |
}
|
| 222 | 240 |
|
| 223 | |
func (b *Bar) Stop() {
|
| 224 | |
if !b.stopped {
|
| 225 | |
b.stopCh <- struct{}{}
|
| 226 | |
b.stopped = true
|
| 227 | |
}
|
| 228 | |
}
|
| 229 | |
|
| 230 | |
func (b *Bar) IsCompleted() bool {
|
| 231 | |
select {
|
| 232 | |
case <-b.done:
|
| 233 | |
return true
|
| 234 | |
default:
|
| 235 | |
return false
|
| 236 | |
}
|
| 237 | |
}
|
| 238 | |
|
| 239 | 241 |
func (b *Bar) draw(buf []byte, current int, appendFuncs, prependFuncs []DecoratorFunc) []byte {
|
| 240 | |
completedWidth := current * b.Width / b.total
|
|
242 |
completedWidth := current * b.width / b.total
|
| 241 | 243 |
|
| 242 | 244 |
for i := 0; i < completedWidth; i++ {
|
| 243 | |
buf[i] = b.Fill
|
| 244 | |
}
|
| 245 | |
for i := completedWidth; i < b.Width; i++ {
|
| 246 | |
buf[i] = b.Empty
|
| 247 | |
}
|
| 248 | |
// set head bit
|
| 249 | |
if completedWidth > 0 && completedWidth < b.Width {
|
| 250 | |
buf[completedWidth-1] = b.Head
|
|
245 |
buf[i] = b.fill
|
|
246 |
}
|
|
247 |
for i := completedWidth; i < b.width; i++ {
|
|
248 |
buf[i] = b.empty
|
|
249 |
}
|
|
250 |
// set tip bit
|
|
251 |
if completedWidth > 0 && completedWidth < b.width {
|
|
252 |
buf[completedWidth-1] = b.tip
|
| 251 | 253 |
}
|
| 252 | 254 |
|
| 253 | 255 |
// set left and right ends bits
|
| 254 | |
buf[0], buf[len(buf)-1] = b.LeftEnd, b.RightEnd
|
|
256 |
buf[0], buf[len(buf)-1] = b.leftEnd, b.rightEnd
|
| 255 | 257 |
|
| 256 | 258 |
s := &Statistics{b.total, current, b.timePerItemEstimate}
|
| 257 | 259 |
|
|
| 270 | 272 |
return buf
|
| 271 | 273 |
}
|
| 272 | 274 |
|
|
275 |
func (b *Bar) flushed() {
|
|
276 |
b.flushedCh <- struct{}{}
|
|
277 |
}
|
|
278 |
|
| 273 | 279 |
func (b *Bar) updateTimePerItemEstimate(items int, blockStartTime time.Time) {
|
| 274 | 280 |
lastBlockTime := time.Since(blockStartTime)
|
| 275 | 281 |
lastItemEstimate := float64(lastBlockTime) / float64(items)
|
| 276 | |
b.timePerItemEstimate = time.Duration((b.Alpha * lastItemEstimate) + (1-b.Alpha)*float64(b.timePerItemEstimate))
|
| 277 | |
}
|
|
282 |
b.timePerItemEstimate = time.Duration((b.alpha * lastItemEstimate) + (1-b.alpha)*float64(b.timePerItemEstimate))
|
|
283 |
}
|