|
0 |
package uiprogress
|
|
1 |
|
|
2 |
import (
|
|
3 |
"bytes"
|
|
4 |
"errors"
|
|
5 |
"fmt"
|
|
6 |
"sync"
|
|
7 |
"time"
|
|
8 |
|
|
9 |
"github.com/gosuri/uiprogress/util/strutil"
|
|
10 |
)
|
|
11 |
|
|
12 |
var (
|
|
13 |
// Fill is the default character representing completed progress
|
|
14 |
Fill byte = '='
|
|
15 |
|
|
16 |
// Head is the default character that moves when progress is updated
|
|
17 |
Head byte = '>'
|
|
18 |
|
|
19 |
// Empty is the default character that represents the empty progress
|
|
20 |
Empty byte = '-'
|
|
21 |
|
|
22 |
// LeftEnd is the default character in the left most part of the progress indicator
|
|
23 |
LeftEnd byte = '['
|
|
24 |
|
|
25 |
// RightEnd is the default character in the right most part of the progress indicator
|
|
26 |
RightEnd byte = ']'
|
|
27 |
|
|
28 |
// Width is the default width of the progress bar
|
|
29 |
Width = 70
|
|
30 |
|
|
31 |
// ErrMaxCurrentReached is error when trying to set current value that exceeds the total value
|
|
32 |
ErrMaxCurrentReached = errors.New("errors: current value is greater total value")
|
|
33 |
)
|
|
34 |
|
|
35 |
// Bar represents a progress bar
|
|
36 |
type Bar struct {
|
|
37 |
// Total of the total for the progress bar
|
|
38 |
Total int
|
|
39 |
|
|
40 |
// LeftEnd is character in the left most part of the progress indicator. Defaults to '['
|
|
41 |
LeftEnd byte
|
|
42 |
|
|
43 |
// RightEnd is character in the right most part of the progress indicator. Defaults to ']'
|
|
44 |
RightEnd byte
|
|
45 |
|
|
46 |
// Fill is the character representing completed progress. Defaults to '='
|
|
47 |
Fill byte
|
|
48 |
|
|
49 |
// Head is the character that moves when progress is updated. Defaults to '>'
|
|
50 |
Head byte
|
|
51 |
|
|
52 |
// Empty is the character that represents the empty progress. Default is '-'
|
|
53 |
Empty byte
|
|
54 |
|
|
55 |
// TimeStated is time progress began
|
|
56 |
TimeStarted time.Time
|
|
57 |
|
|
58 |
// Width is the width of the progress bar
|
|
59 |
Width int
|
|
60 |
|
|
61 |
// timeElased is the time elapsed for the progress
|
|
62 |
timeElapsed time.Duration
|
|
63 |
current int
|
|
64 |
|
|
65 |
mtx *sync.RWMutex
|
|
66 |
|
|
67 |
appendFuncs []DecoratorFunc
|
|
68 |
prependFuncs []DecoratorFunc
|
|
69 |
}
|
|
70 |
|
|
71 |
// DecoratorFunc is a function that can be prepended and appended to the progress bar
|
|
72 |
type DecoratorFunc func(b *Bar) string
|
|
73 |
|
|
74 |
// NewBar returns a new progress bar
|
|
75 |
func NewBar(total int) *Bar {
|
|
76 |
return &Bar{
|
|
77 |
Total: total,
|
|
78 |
Width: Width,
|
|
79 |
LeftEnd: LeftEnd,
|
|
80 |
RightEnd: RightEnd,
|
|
81 |
Head: Head,
|
|
82 |
Fill: Fill,
|
|
83 |
Empty: Empty,
|
|
84 |
|
|
85 |
mtx: &sync.RWMutex{},
|
|
86 |
}
|
|
87 |
}
|
|
88 |
|
|
89 |
// Set the current count of the bar. It returns ErrMaxCurrentReached when trying n exceeds the total value. This is atomic operation and concurancy safe.
|
|
90 |
func (b *Bar) Set(n int) error {
|
|
91 |
b.mtx.Lock()
|
|
92 |
defer b.mtx.Unlock()
|
|
93 |
|
|
94 |
if n > b.Total {
|
|
95 |
return ErrMaxCurrentReached
|
|
96 |
}
|
|
97 |
b.current = n
|
|
98 |
return nil
|
|
99 |
}
|
|
100 |
|
|
101 |
// Incr increments the current value by 1, time elapsed to current time and returns true. It returns false if the cursor has reached or exceeds total value.
|
|
102 |
func (b *Bar) Incr() bool {
|
|
103 |
b.mtx.Lock()
|
|
104 |
defer b.mtx.Unlock()
|
|
105 |
|
|
106 |
n := b.current + 1
|
|
107 |
if n > b.Total {
|
|
108 |
return false
|
|
109 |
}
|
|
110 |
var t time.Time
|
|
111 |
if b.TimeStarted == t {
|
|
112 |
b.TimeStarted = time.Now()
|
|
113 |
}
|
|
114 |
b.timeElapsed = time.Since(b.TimeStarted)
|
|
115 |
b.current = n
|
|
116 |
return true
|
|
117 |
}
|
|
118 |
|
|
119 |
// Current returns the current progress of the bar
|
|
120 |
func (b *Bar) Current() int {
|
|
121 |
b.mtx.RLock()
|
|
122 |
defer b.mtx.RUnlock()
|
|
123 |
return b.current
|
|
124 |
}
|
|
125 |
|
|
126 |
// AppendFunc runs the decorator function and renders the output on the right of the progress bar
|
|
127 |
func (b *Bar) AppendFunc(f DecoratorFunc) *Bar {
|
|
128 |
b.mtx.Lock()
|
|
129 |
defer b.mtx.Unlock()
|
|
130 |
b.appendFuncs = append(b.appendFuncs, f)
|
|
131 |
return b
|
|
132 |
}
|
|
133 |
|
|
134 |
// AppendCompleted appends the completion percent to the progress bar
|
|
135 |
func (b *Bar) AppendCompleted() *Bar {
|
|
136 |
b.AppendFunc(func(b *Bar) string {
|
|
137 |
return b.CompletedPercentString()
|
|
138 |
})
|
|
139 |
return b
|
|
140 |
}
|
|
141 |
|
|
142 |
// AppendElapsed appends the time elapsed the be progress bar
|
|
143 |
func (b *Bar) AppendElapsed() *Bar {
|
|
144 |
b.AppendFunc(func(b *Bar) string {
|
|
145 |
return strutil.PadLeft(b.TimeElapsedString(), 5, ' ')
|
|
146 |
})
|
|
147 |
return b
|
|
148 |
}
|
|
149 |
|
|
150 |
// PrependFunc runs decorator function and render the output left the progress bar
|
|
151 |
func (b *Bar) PrependFunc(f DecoratorFunc) *Bar {
|
|
152 |
b.mtx.Lock()
|
|
153 |
defer b.mtx.Unlock()
|
|
154 |
b.prependFuncs = append(b.prependFuncs, f)
|
|
155 |
return b
|
|
156 |
}
|
|
157 |
|
|
158 |
// PrependCompleted prepends the precent completed to the progress bar
|
|
159 |
func (b *Bar) PrependCompleted() *Bar {
|
|
160 |
b.PrependFunc(func(b *Bar) string {
|
|
161 |
return b.CompletedPercentString()
|
|
162 |
})
|
|
163 |
return b
|
|
164 |
}
|
|
165 |
|
|
166 |
// PrependElapsed prepends the time elapsed to the begining of the bar
|
|
167 |
func (b *Bar) PrependElapsed() *Bar {
|
|
168 |
b.PrependFunc(func(b *Bar) string {
|
|
169 |
return strutil.PadLeft(b.TimeElapsedString(), 5, ' ')
|
|
170 |
})
|
|
171 |
return b
|
|
172 |
}
|
|
173 |
|
|
174 |
// Bytes returns the byte presentation of the progress bar
|
|
175 |
func (b *Bar) Bytes() []byte {
|
|
176 |
completedWidth := int(float64(b.Width) * (b.CompletedPercent() / 100.00))
|
|
177 |
|
|
178 |
// add fill and empty bits
|
|
179 |
var buf bytes.Buffer
|
|
180 |
for i := 0; i < completedWidth; i++ {
|
|
181 |
buf.WriteByte(b.Fill)
|
|
182 |
}
|
|
183 |
for i := 0; i < b.Width-completedWidth; i++ {
|
|
184 |
buf.WriteByte(b.Empty)
|
|
185 |
}
|
|
186 |
|
|
187 |
// set head bit
|
|
188 |
pb := buf.Bytes()
|
|
189 |
if completedWidth > 0 && completedWidth < b.Width {
|
|
190 |
pb[completedWidth-1] = b.Head
|
|
191 |
}
|
|
192 |
|
|
193 |
// set left and right ends bits
|
|
194 |
pb[0], pb[len(pb)-1] = b.LeftEnd, b.RightEnd
|
|
195 |
|
|
196 |
// render append functions to the right of the bar
|
|
197 |
for _, f := range b.appendFuncs {
|
|
198 |
pb = append(pb, ' ')
|
|
199 |
pb = append(pb, []byte(f(b))...)
|
|
200 |
}
|
|
201 |
|
|
202 |
// render prepend functions to the left of the bar
|
|
203 |
for _, f := range b.prependFuncs {
|
|
204 |
args := []byte(f(b))
|
|
205 |
args = append(args, ' ')
|
|
206 |
pb = append(args, pb...)
|
|
207 |
}
|
|
208 |
return pb
|
|
209 |
}
|
|
210 |
|
|
211 |
// String returns the string representation of the bar
|
|
212 |
func (b *Bar) String() string {
|
|
213 |
return string(b.Bytes())
|
|
214 |
}
|
|
215 |
|
|
216 |
// CompletedPercent return the percent completed
|
|
217 |
func (b *Bar) CompletedPercent() float64 {
|
|
218 |
return (float64(b.Current()) / float64(b.Total)) * 100.00
|
|
219 |
}
|
|
220 |
|
|
221 |
// CompletedPercentString returns the formatted string representation of the completed percent
|
|
222 |
func (b *Bar) CompletedPercentString() string {
|
|
223 |
return fmt.Sprintf("%3.f%%", b.CompletedPercent())
|
|
224 |
}
|
|
225 |
|
|
226 |
// TimeElapsed returns the time elapsed
|
|
227 |
func (b *Bar) TimeElapsed() time.Duration {
|
|
228 |
b.mtx.RLock()
|
|
229 |
defer b.mtx.RUnlock()
|
|
230 |
return b.timeElapsed
|
|
231 |
}
|
|
232 |
|
|
233 |
// TimeElapsedString returns the formatted string represenation of the time elapsed
|
|
234 |
func (b *Bar) TimeElapsedString() string {
|
|
235 |
return strutil.PrettyTime(b.TimeElapsed())
|
|
236 |
}
|