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.25"
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		ShowElapsedTime: false,
47		ShowFinalTime:   true,
48		Units:           U_NO,
49		ManualUpdate:    false,
50		finish:          make(chan struct{}),
51	}
52	return pb.Format(FORMAT)
53}
54
55// Create new object and start
56func StartNew(total int) *ProgressBar {
57	return New(total).Start()
58}
59
60// Callback for custom output
61// For example:
62// bar.Callback = func(s string) {
63//     mySuperPrint(s)
64// }
65//
66type Callback func(out string)
67
68type ProgressBar struct {
69	current  int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278)
70	previous int64
71
72	Total                            int64
73	RefreshRate                      time.Duration
74	ShowPercent, ShowCounters        bool
75	ShowSpeed, ShowTimeLeft, ShowBar bool
76	ShowFinalTime, ShowElapsedTime   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
97	changeTime time.Time
98
99	prefix, postfix string
100
101	mu        sync.Mutex
102	lastPrint string
103
104	BarStart string
105	BarEnd   string
106	Empty    string
107	Current  string
108	CurrentN string
109
110	AlwaysUpdate bool
111}
112
113// Start print
114func (pb *ProgressBar) Start() *ProgressBar {
115	pb.startTime = time.Now()
116	pb.startValue = atomic.LoadInt64(&pb.current)
117	if atomic.LoadInt64(&pb.Total) == 0 {
118		pb.ShowTimeLeft = false
119		pb.ShowPercent = false
120		pb.AutoStat = false
121	}
122	if !pb.ManualUpdate {
123		pb.Update() // Initial printing of the bar before running the bar refresher.
124		go pb.refresher()
125	}
126	return pb
127}
128
129// Increment current value
130func (pb *ProgressBar) Increment() int {
131	return pb.Add(1)
132}
133
134// Get current value
135func (pb *ProgressBar) Get() int64 {
136	c := atomic.LoadInt64(&pb.current)
137	return c
138}
139
140// Set current value
141func (pb *ProgressBar) Set(current int) *ProgressBar {
142	return pb.Set64(int64(current))
143}
144
145// Set64 sets the current value as int64
146func (pb *ProgressBar) Set64(current int64) *ProgressBar {
147	atomic.StoreInt64(&pb.current, current)
148	return pb
149}
150
151// Add to current value
152func (pb *ProgressBar) Add(add int) int {
153	return int(pb.Add64(int64(add)))
154}
155
156func (pb *ProgressBar) Add64(add int64) int64 {
157	return atomic.AddInt64(&pb.current, add)
158}
159
160// Set prefix string
161func (pb *ProgressBar) Prefix(prefix string) *ProgressBar {
162	pb.prefix = prefix
163	return pb
164}
165
166// Set postfix string
167func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
168	pb.postfix = postfix
169	return pb
170}
171
172// Set custom format for bar
173// Example: bar.Format("[=>_]")
174// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
175func (pb *ProgressBar) Format(format string) *ProgressBar {
176	var formatEntries []string
177	if utf8.RuneCountInString(format) == 5 {
178		formatEntries = strings.Split(format, "")
179	} else {
180		formatEntries = strings.Split(format, "\x00")
181	}
182	if len(formatEntries) == 5 {
183		pb.BarStart = formatEntries[0]
184		pb.BarEnd = formatEntries[4]
185		pb.Empty = formatEntries[3]
186		pb.Current = formatEntries[1]
187		pb.CurrentN = formatEntries[2]
188	}
189	return pb
190}
191
192// Set bar refresh rate
193func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar {
194	pb.RefreshRate = rate
195	return pb
196}
197
198// Set units
199// bar.SetUnits(U_NO) - by default
200// bar.SetUnits(U_BYTES) - for Mb, Kb, etc
201func (pb *ProgressBar) SetUnits(units Units) *ProgressBar {
202	pb.Units = units
203	return pb
204}
205
206// Set max width, if width is bigger than terminal width, will be ignored
207func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar {
208	pb.Width = width
209	pb.ForceWidth = false
210	return pb
211}
212
213// Set bar width
214func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
215	pb.Width = width
216	pb.ForceWidth = true
217	return pb
218}
219
220// End print
221func (pb *ProgressBar) Finish() {
222	//Protect multiple calls
223	pb.finishOnce.Do(func() {
224		close(pb.finish)
225		pb.write(atomic.LoadInt64(&pb.Total), atomic.LoadInt64(&pb.current))
226		pb.mu.Lock()
227		defer pb.mu.Unlock()
228		switch {
229		case pb.Output != nil:
230			fmt.Fprintln(pb.Output)
231		case !pb.NotPrint:
232			fmt.Println()
233		}
234		pb.isFinish = true
235	})
236}
237
238// IsFinished return boolean
239func (pb *ProgressBar) IsFinished() bool {
240	pb.mu.Lock()
241	defer pb.mu.Unlock()
242	return pb.isFinish
243}
244
245// End print and write string 'str'
246func (pb *ProgressBar) FinishPrint(str string) {
247	pb.Finish()
248	if pb.Output != nil {
249		fmt.Fprintln(pb.Output, str)
250	} else {
251		fmt.Println(str)
252	}
253}
254
255// implement io.Writer
256func (pb *ProgressBar) Write(p []byte) (n int, err error) {
257	n = len(p)
258	pb.Add(n)
259	return
260}
261
262// implement io.Reader
263func (pb *ProgressBar) Read(p []byte) (n int, err error) {
264	n = len(p)
265	pb.Add(n)
266	return
267}
268
269// Create new proxy reader over bar
270// Takes io.Reader or io.ReadCloser
271func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
272	return &Reader{r, pb}
273}
274
275func (pb *ProgressBar) write(total, current int64) {
276	width := pb.GetWidth()
277
278	var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string
279
280	// percents
281	if pb.ShowPercent {
282		var percent float64
283		if total > 0 {
284			percent = float64(current) / (float64(total) / float64(100))
285		} else {
286			percent = float64(current) / float64(100)
287		}
288		percentBox = fmt.Sprintf(" %6.02f%%", percent)
289	}
290
291	// counters
292	if pb.ShowCounters {
293		current := Format(current).To(pb.Units).Width(pb.UnitsWidth)
294		if total > 0 {
295			totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth)
296			countersBox = fmt.Sprintf(" %s / %s ", current, totalS)
297		} else {
298			countersBox = fmt.Sprintf(" %s / ? ", current)
299		}
300	}
301
302	// time left
303	pb.mu.Lock()
304	currentFromStart := current - pb.startValue
305	fromStart := time.Now().Sub(pb.startTime)
306	lastChangeTime := pb.changeTime
307	fromChange := lastChangeTime.Sub(pb.startTime)
308	pb.mu.Unlock()
309
310	if pb.ShowElapsedTime {
311		timeSpentBox = fmt.Sprintf(" %s ", (fromStart/time.Second)*time.Second)
312	}
313
314	select {
315	case <-pb.finish:
316		if pb.ShowFinalTime {
317			var left time.Duration
318			left = (fromStart / time.Second) * time.Second
319			timeLeftBox = fmt.Sprintf(" %s", left.String())
320		}
321	default:
322		if pb.ShowTimeLeft && currentFromStart > 0 {
323			perEntry := fromChange / time.Duration(currentFromStart)
324			var left time.Duration
325			if total > 0 {
326				left = time.Duration(total-currentFromStart) * perEntry
327				left -= time.Since(lastChangeTime)
328				left = (left / time.Second) * time.Second
329			} else {
330				left = time.Duration(currentFromStart) * perEntry
331				left = (left / time.Second) * time.Second
332			}
333			if left > 0 {
334				timeLeft := Format(int64(left)).To(U_DURATION).String()
335				timeLeftBox = fmt.Sprintf(" %s", timeLeft)
336			}
337		}
338	}
339
340	if len(timeLeftBox) < pb.TimeBoxWidth {
341		timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox)
342	}
343
344	// speed
345	if pb.ShowSpeed && currentFromStart > 0 {
346		fromStart := time.Now().Sub(pb.startTime)
347		speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
348		speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String()
349	}
350
351	barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeSpentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix)
352	// bar
353	if pb.ShowBar {
354		size := width - barWidth
355		if size > 0 {
356			if total > 0 {
357				curSize := int(math.Ceil((float64(current) / float64(total)) * float64(size)))
358				emptySize := size - curSize
359				barBox = pb.BarStart
360				if emptySize < 0 {
361					emptySize = 0
362				}
363				if curSize > size {
364					curSize = size
365				}
366
367				cursorLen := escapeAwareRuneCountInString(pb.Current)
368				if emptySize <= 0 {
369					barBox += strings.Repeat(pb.Current, curSize/cursorLen)
370				} else if curSize > 0 {
371					cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN)
372					cursorRepetitions := (curSize - cursorEndLen) / cursorLen
373					barBox += strings.Repeat(pb.Current, cursorRepetitions)
374					barBox += pb.CurrentN
375				}
376
377				emptyLen := escapeAwareRuneCountInString(pb.Empty)
378				barBox += strings.Repeat(pb.Empty, emptySize/emptyLen)
379				barBox += pb.BarEnd
380			} else {
381				pos := size - int(current)%int(size)
382				barBox = pb.BarStart
383				if pos-1 > 0 {
384					barBox += strings.Repeat(pb.Empty, pos-1)
385				}
386				barBox += pb.Current
387				if size-pos-1 > 0 {
388					barBox += strings.Repeat(pb.Empty, size-pos-1)
389				}
390				barBox += pb.BarEnd
391			}
392		}
393	}
394
395	// check len
396	out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
397	if cl := escapeAwareRuneCountInString(out); cl < width {
398		end = strings.Repeat(" ", width-cl)
399	}
400
401	// and print!
402	pb.mu.Lock()
403	defer pb.mu.Unlock()
404	pb.lastPrint = out + end
405	isFinish := pb.isFinish
406
407	switch {
408	case isFinish:
409		return
410	case pb.Output != nil:
411		fmt.Fprint(pb.Output, "\r"+out+end)
412	case pb.Callback != nil:
413		pb.Callback(out + end)
414	case !pb.NotPrint:
415		fmt.Print("\r" + out + end)
416	}
417}
418
419// GetTerminalWidth - returns terminal width for all platforms.
420func GetTerminalWidth() (int, error) {
421	return terminalWidth()
422}
423
424func (pb *ProgressBar) GetWidth() int {
425	if pb.ForceWidth {
426		return pb.Width
427	}
428
429	width := pb.Width
430	termWidth, _ := terminalWidth()
431	if width == 0 || termWidth <= width {
432		width = termWidth
433	}
434
435	return width
436}
437
438// Write the current state of the progressbar
439func (pb *ProgressBar) Update() {
440	c := atomic.LoadInt64(&pb.current)
441	p := atomic.LoadInt64(&pb.previous)
442	t := atomic.LoadInt64(&pb.Total)
443	if p != c {
444		pb.mu.Lock()
445		pb.changeTime = time.Now()
446		pb.mu.Unlock()
447		atomic.StoreInt64(&pb.previous, c)
448	}
449	pb.write(t, c)
450	if pb.AutoStat {
451		if c == 0 {
452			pb.startTime = time.Now()
453			pb.startValue = 0
454		} else if c >= t && pb.isFinish != true {
455			pb.Finish()
456		}
457	}
458}
459
460// String return the last bar print
461func (pb *ProgressBar) String() string {
462	pb.mu.Lock()
463	defer pb.mu.Unlock()
464	return pb.lastPrint
465}
466
467// SetTotal atomically sets new total count
468func (pb *ProgressBar) SetTotal(total int) *ProgressBar {
469	return pb.SetTotal64(int64(total))
470}
471
472// SetTotal64 atomically sets new total count
473func (pb *ProgressBar) SetTotal64(total int64) *ProgressBar {
474	atomic.StoreInt64(&pb.Total, total)
475	return pb
476}
477
478// Reset bar and set new total count
479// Does effect only on finished bar
480func (pb *ProgressBar) Reset(total int) *ProgressBar {
481	pb.mu.Lock()
482	defer pb.mu.Unlock()
483	if pb.isFinish {
484		pb.SetTotal(total).Set(0)
485		atomic.StoreInt64(&pb.previous, 0)
486	}
487	return pb
488}
489
490// Internal loop for refreshing the progressbar
491func (pb *ProgressBar) refresher() {
492	for {
493		select {
494		case <-pb.finish:
495			return
496		case <-time.After(pb.RefreshRate):
497			pb.Update()
498		}
499	}
500}
501