1// Simple console progress bars
2package pb
3
4import (
5	"fmt"
6	"io"
7	"math"
8	"strings"
9	"sync"
10	"sync/atomic"
11	"time"
12	"unicode/utf8"
13)
14
15// Current version
16const Version = "1.0.10"
17
18const (
19	// Default refresh rate - 200ms
20	DEFAULT_REFRESH_RATE = time.Millisecond * 200
21	FORMAT               = "[=>-]"
22)
23
24// DEPRECATED
25// variables for backward compatibility, from now do not work
26// use pb.Format and pb.SetRefreshRate
27var (
28	DefaultRefreshRate                         = DEFAULT_REFRESH_RATE
29	BarStart, BarEnd, Empty, Current, CurrentN string
30)
31
32// Create new progress bar object
33func New(total int) *ProgressBar {
34	return New64(int64(total))
35}
36
37// Create new progress bar object using int64 as total
38func New64(total int64) *ProgressBar {
39	pb := &ProgressBar{
40		Total:         total,
41		RefreshRate:   DEFAULT_REFRESH_RATE,
42		ShowPercent:   true,
43		ShowCounters:  true,
44		ShowBar:       true,
45		ShowTimeLeft:  true,
46		ShowFinalTime: true,
47		Units:         U_NO,
48		ManualUpdate:  false,
49		finish:        make(chan struct{}),
50		currentValue:  -1,
51		mu:            new(sync.Mutex),
52	}
53	return pb.Format(FORMAT)
54}
55
56// Create new object and start
57func StartNew(total int) *ProgressBar {
58	return New(total).Start()
59}
60
61// Callback for custom output
62// For example:
63// bar.Callback = func(s string) {
64//     mySuperPrint(s)
65// }
66//
67type Callback func(out string)
68
69type ProgressBar struct {
70	current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278)
71
72	Total                            int64
73	RefreshRate                      time.Duration
74	ShowPercent, ShowCounters        bool
75	ShowSpeed, ShowTimeLeft, ShowBar bool
76	ShowFinalTime                    bool
77	Output                           io.Writer
78	Callback                         Callback
79	NotPrint                         bool
80	Units                            Units
81	Width                            int
82	ForceWidth                       bool
83	ManualUpdate                     bool
84	AutoStat                         bool
85
86	// Default width for the time box.
87	UnitsWidth   int
88	TimeBoxWidth int
89
90	finishOnce sync.Once //Guards isFinish
91	finish     chan struct{}
92	isFinish   bool
93
94	startTime    time.Time
95	startValue   int64
96	currentValue int64
97
98	prefix, postfix string
99
100	mu        *sync.Mutex
101	lastPrint string
102
103	BarStart string
104	BarEnd   string
105	Empty    string
106	Current  string
107	CurrentN string
108
109	AlwaysUpdate bool
110}
111
112// Start print
113func (pb *ProgressBar) Start() *ProgressBar {
114	pb.startTime = time.Now()
115	pb.startValue = pb.current
116	if pb.Total == 0 {
117		pb.ShowTimeLeft = false
118		pb.ShowPercent = false
119		pb.AutoStat = false
120	}
121	if !pb.ManualUpdate {
122		pb.Update() // Initial printing of the bar before running the bar refresher.
123		go pb.refresher()
124	}
125	return pb
126}
127
128// Increment current value
129func (pb *ProgressBar) Increment() int {
130	return pb.Add(1)
131}
132
133// Get current value
134func (pb *ProgressBar) Get() int64 {
135	c := atomic.LoadInt64(&pb.current)
136	return c
137}
138
139// Set current value
140func (pb *ProgressBar) Set(current int) *ProgressBar {
141	return pb.Set64(int64(current))
142}
143
144// Set64 sets the current value as int64
145func (pb *ProgressBar) Set64(current int64) *ProgressBar {
146	atomic.StoreInt64(&pb.current, current)
147	return pb
148}
149
150// Add to current value
151func (pb *ProgressBar) Add(add int) int {
152	return int(pb.Add64(int64(add)))
153}
154
155func (pb *ProgressBar) Add64(add int64) int64 {
156	return atomic.AddInt64(&pb.current, add)
157}
158
159// Set prefix string
160func (pb *ProgressBar) Prefix(prefix string) *ProgressBar {
161	pb.prefix = prefix
162	return pb
163}
164
165// Set postfix string
166func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
167	pb.postfix = postfix
168	return pb
169}
170
171// Set custom format for bar
172// Example: bar.Format("[=>_]")
173// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
174func (pb *ProgressBar) Format(format string) *ProgressBar {
175	var formatEntries []string
176	if utf8.RuneCountInString(format) == 5 {
177		formatEntries = strings.Split(format, "")
178	} else {
179		formatEntries = strings.Split(format, "\x00")
180	}
181	if len(formatEntries) == 5 {
182		pb.BarStart = formatEntries[0]
183		pb.BarEnd = formatEntries[4]
184		pb.Empty = formatEntries[3]
185		pb.Current = formatEntries[1]
186		pb.CurrentN = formatEntries[2]
187	}
188	return pb
189}
190
191// Set bar refresh rate
192func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar {
193	pb.RefreshRate = rate
194	return pb
195}
196
197// Set units
198// bar.SetUnits(U_NO) - by default
199// bar.SetUnits(U_BYTES) - for Mb, Kb, etc
200func (pb *ProgressBar) SetUnits(units Units) *ProgressBar {
201	pb.Units = units
202	return pb
203}
204
205// Set max width, if width is bigger than terminal width, will be ignored
206func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar {
207	pb.Width = width
208	pb.ForceWidth = false
209	return pb
210}
211
212// Set bar width
213func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
214	pb.Width = width
215	pb.ForceWidth = true
216	return pb
217}
218
219// End print
220func (pb *ProgressBar) Finish() {
221	//Protect multiple calls
222	pb.finishOnce.Do(func() {
223		close(pb.finish)
224		pb.write(atomic.LoadInt64(&pb.current))
225		switch {
226		case pb.Output != nil:
227			fmt.Fprintln(pb.Output)
228		case !pb.NotPrint:
229			fmt.Println()
230		}
231		pb.isFinish = true
232	})
233}
234
235// End print and write string 'str'
236func (pb *ProgressBar) FinishPrint(str string) {
237	pb.Finish()
238	if pb.Output != nil {
239		fmt.Fprintln(pb.Output, str)
240	} else {
241		fmt.Println(str)
242	}
243}
244
245// implement io.Writer
246func (pb *ProgressBar) Write(p []byte) (n int, err error) {
247	n = len(p)
248	pb.Add(n)
249	return
250}
251
252// implement io.Reader
253func (pb *ProgressBar) Read(p []byte) (n int, err error) {
254	n = len(p)
255	pb.Add(n)
256	return
257}
258
259// Create new proxy reader over bar
260// Takes io.Reader or io.ReadCloser
261func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
262	return &Reader{r, pb}
263}
264
265func (pb *ProgressBar) write(current int64) {
266	width := pb.GetWidth()
267
268	var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string
269
270	// percents
271	if pb.ShowPercent {
272		var percent float64
273		if pb.Total > 0 {
274			percent = float64(current) / (float64(pb.Total) / float64(100))
275		} else {
276			percent = float64(current) / float64(100)
277		}
278		percentBox = fmt.Sprintf(" %6.02f%%", percent)
279	}
280
281	// counters
282	if pb.ShowCounters {
283		current := Format(current).To(pb.Units).Width(pb.UnitsWidth)
284		if pb.Total > 0 {
285			total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth)
286			countersBox = fmt.Sprintf(" %s / %s ", current, total)
287		} else {
288			countersBox = fmt.Sprintf(" %s / ? ", current)
289		}
290	}
291
292	// time left
293	fromStart := time.Now().Sub(pb.startTime)
294	currentFromStart := current - pb.startValue
295	select {
296	case <-pb.finish:
297		if pb.ShowFinalTime {
298			var left time.Duration
299			left = (fromStart / time.Second) * time.Second
300			timeLeftBox = fmt.Sprintf(" %s", left.String())
301		}
302	default:
303		if pb.ShowTimeLeft && currentFromStart > 0 {
304			perEntry := fromStart / time.Duration(currentFromStart)
305			var left time.Duration
306			if pb.Total > 0 {
307				left = time.Duration(pb.Total-currentFromStart) * perEntry
308				left = (left / time.Second) * time.Second
309			} else {
310				left = time.Duration(currentFromStart) * perEntry
311				left = (left / time.Second) * time.Second
312			}
313			timeLeft := Format(int64(left)).To(U_DURATION).String()
314			timeLeftBox = fmt.Sprintf(" %s", timeLeft)
315		}
316	}
317
318	if len(timeLeftBox) < pb.TimeBoxWidth {
319		timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox)
320	}
321
322	// speed
323	if pb.ShowSpeed && currentFromStart > 0 {
324		fromStart := time.Now().Sub(pb.startTime)
325		speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
326		speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String()
327	}
328
329	barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix)
330	// bar
331	if pb.ShowBar {
332		size := width - barWidth
333		if size > 0 {
334			if pb.Total > 0 {
335				curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size)))
336				emptCount := size - curCount
337				barBox = pb.BarStart
338				if emptCount < 0 {
339					emptCount = 0
340				}
341				if curCount > size {
342					curCount = size
343				}
344				if emptCount <= 0 {
345					barBox += strings.Repeat(pb.Current, curCount)
346				} else if curCount > 0 {
347					barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN
348				}
349				barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd
350			} else {
351				barBox = pb.BarStart
352				pos := size - int(current)%int(size)
353				if pos-1 > 0 {
354					barBox += strings.Repeat(pb.Empty, pos-1)
355				}
356				barBox += pb.Current
357				if size-pos-1 > 0 {
358					barBox += strings.Repeat(pb.Empty, size-pos-1)
359				}
360				barBox += pb.BarEnd
361			}
362		}
363	}
364
365	// check len
366	out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
367	if cl := escapeAwareRuneCountInString(out); cl < width {
368		end = strings.Repeat(" ", width-cl)
369	}
370
371	// and print!
372	pb.mu.Lock()
373	pb.lastPrint = out + end
374	pb.mu.Unlock()
375	switch {
376	case pb.isFinish:
377		return
378	case pb.Output != nil:
379		fmt.Fprint(pb.Output, "\r"+out+end)
380	case pb.Callback != nil:
381		pb.Callback(out + end)
382	case !pb.NotPrint:
383		fmt.Print("\r" + out + end)
384	}
385}
386
387// GetTerminalWidth - returns terminal width for all platforms.
388func GetTerminalWidth() (int, error) {
389	return terminalWidth()
390}
391
392func (pb *ProgressBar) GetWidth() int {
393	if pb.ForceWidth {
394		return pb.Width
395	}
396
397	width := pb.Width
398	termWidth, _ := terminalWidth()
399	if width == 0 || termWidth <= width {
400		width = termWidth
401	}
402
403	return width
404}
405
406// Write the current state of the progressbar
407func (pb *ProgressBar) Update() {
408	c := atomic.LoadInt64(&pb.current)
409	if pb.AlwaysUpdate || c != pb.currentValue {
410		pb.write(c)
411		pb.currentValue = c
412	}
413	if pb.AutoStat {
414		if c == 0 {
415			pb.startTime = time.Now()
416			pb.startValue = 0
417		} else if c >= pb.Total && pb.isFinish != true {
418			pb.Finish()
419		}
420	}
421}
422
423func (pb *ProgressBar) String() string {
424	return pb.lastPrint
425}
426
427// Internal loop for refreshing the progressbar
428func (pb *ProgressBar) refresher() {
429	for {
430		select {
431		case <-pb.finish:
432			return
433		case <-time.After(pb.RefreshRate):
434			pb.Update()
435		}
436	}
437}
438
439type window struct {
440	Row    uint16
441	Col    uint16
442	Xpixel uint16
443	Ypixel uint16
444}
445