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.29"
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.mu.Lock()
163	defer pb.mu.Unlock()
164	pb.prefix = prefix
165	return pb
166}
167
168// Set postfix string
169func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
170	pb.mu.Lock()
171	defer pb.mu.Unlock()
172	pb.postfix = postfix
173	return pb
174}
175
176// Set custom format for bar
177// Example: bar.Format("[=>_]")
178// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
179func (pb *ProgressBar) Format(format string) *ProgressBar {
180	var formatEntries []string
181	if utf8.RuneCountInString(format) == 5 {
182		formatEntries = strings.Split(format, "")
183	} else {
184		formatEntries = strings.Split(format, "\x00")
185	}
186	if len(formatEntries) == 5 {
187		pb.BarStart = formatEntries[0]
188		pb.BarEnd = formatEntries[4]
189		pb.Empty = formatEntries[3]
190		pb.Current = formatEntries[1]
191		pb.CurrentN = formatEntries[2]
192	}
193	return pb
194}
195
196// Set bar refresh rate
197func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar {
198	pb.RefreshRate = rate
199	return pb
200}
201
202// Set units
203// bar.SetUnits(U_NO) - by default
204// bar.SetUnits(U_BYTES) - for Mb, Kb, etc
205func (pb *ProgressBar) SetUnits(units Units) *ProgressBar {
206	pb.Units = units
207	return pb
208}
209
210// Set max width, if width is bigger than terminal width, will be ignored
211func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar {
212	pb.Width = width
213	pb.ForceWidth = false
214	return pb
215}
216
217// Set bar width
218func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
219	pb.Width = width
220	pb.ForceWidth = true
221	return pb
222}
223
224// End print
225func (pb *ProgressBar) Finish() {
226	//Protect multiple calls
227	pb.finishOnce.Do(func() {
228		close(pb.finish)
229		pb.write(atomic.LoadInt64(&pb.Total), atomic.LoadInt64(&pb.current))
230		pb.mu.Lock()
231		defer pb.mu.Unlock()
232		switch {
233		case pb.Output != nil:
234			fmt.Fprintln(pb.Output)
235		case !pb.NotPrint:
236			fmt.Println()
237		}
238		pb.isFinish = true
239	})
240}
241
242// IsFinished return boolean
243func (pb *ProgressBar) IsFinished() bool {
244	pb.mu.Lock()
245	defer pb.mu.Unlock()
246	return pb.isFinish
247}
248
249// End print and write string 'str'
250func (pb *ProgressBar) FinishPrint(str string) {
251	pb.Finish()
252	if pb.Output != nil {
253		fmt.Fprintln(pb.Output, str)
254	} else {
255		fmt.Println(str)
256	}
257}
258
259// implement io.Writer
260func (pb *ProgressBar) Write(p []byte) (n int, err error) {
261	n = len(p)
262	pb.Add(n)
263	return
264}
265
266// implement io.Reader
267func (pb *ProgressBar) Read(p []byte) (n int, err error) {
268	n = len(p)
269	pb.Add(n)
270	return
271}
272
273// Create new proxy reader over bar
274// Takes io.Reader or io.ReadCloser
275func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
276	return &Reader{r, pb}
277}
278
279// Create new proxy writer over bar
280// Takes io.Writer or io.WriteCloser
281func (pb *ProgressBar) NewProxyWriter(r io.Writer) *Writer {
282	return &Writer{r, pb}
283}
284
285func (pb *ProgressBar) write(total, current int64) {
286	pb.mu.Lock()
287	defer pb.mu.Unlock()
288	width := pb.GetWidth()
289
290	var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string
291
292	// percents
293	if pb.ShowPercent {
294		var percent float64
295		if total > 0 {
296			percent = float64(current) / (float64(total) / float64(100))
297		} else {
298			percent = float64(current) / float64(100)
299		}
300		percentBox = fmt.Sprintf(" %6.02f%%", percent)
301	}
302
303	// counters
304	if pb.ShowCounters {
305		current := Format(current).To(pb.Units).Width(pb.UnitsWidth)
306		if total > 0 {
307			totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth)
308			countersBox = fmt.Sprintf(" %s / %s ", current, totalS)
309		} else {
310			countersBox = fmt.Sprintf(" %s / ? ", current)
311		}
312	}
313
314	// time left
315	currentFromStart := current - pb.startValue
316	fromStart := time.Now().Sub(pb.startTime)
317	lastChangeTime := pb.changeTime
318	fromChange := lastChangeTime.Sub(pb.startTime)
319
320	if pb.ShowElapsedTime {
321		timeSpentBox = fmt.Sprintf(" %s ", (fromStart/time.Second)*time.Second)
322	}
323
324	select {
325	case <-pb.finish:
326		if pb.ShowFinalTime {
327			var left time.Duration
328			left = (fromStart / time.Second) * time.Second
329			timeLeftBox = fmt.Sprintf(" %s", left.String())
330		}
331	default:
332		if pb.ShowTimeLeft && currentFromStart > 0 {
333			perEntry := fromChange / time.Duration(currentFromStart)
334			var left time.Duration
335			if total > 0 {
336				left = time.Duration(total-current) * perEntry
337				left -= time.Since(lastChangeTime)
338				left = (left / time.Second) * time.Second
339			}
340			if left > 0 {
341				timeLeft := Format(int64(left)).To(U_DURATION).String()
342				timeLeftBox = fmt.Sprintf(" %s", timeLeft)
343			}
344		}
345	}
346
347	if len(timeLeftBox) < pb.TimeBoxWidth {
348		timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox)
349	}
350
351	// speed
352	if pb.ShowSpeed && currentFromStart > 0 {
353		fromStart := time.Now().Sub(pb.startTime)
354		speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
355		speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String()
356	}
357
358	barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeSpentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix)
359	// bar
360	if pb.ShowBar {
361		size := width - barWidth
362		if size > 0 {
363			if total > 0 {
364				curSize := int(math.Ceil((float64(current) / float64(total)) * float64(size)))
365				emptySize := size - curSize
366				barBox = pb.BarStart
367				if emptySize < 0 {
368					emptySize = 0
369				}
370				if curSize > size {
371					curSize = size
372				}
373
374				cursorLen := escapeAwareRuneCountInString(pb.Current)
375				if emptySize <= 0 {
376					barBox += strings.Repeat(pb.Current, curSize/cursorLen)
377				} else if curSize > 0 {
378					cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN)
379					cursorRepetitions := (curSize - cursorEndLen) / cursorLen
380					barBox += strings.Repeat(pb.Current, cursorRepetitions)
381					barBox += pb.CurrentN
382				}
383
384				emptyLen := escapeAwareRuneCountInString(pb.Empty)
385				barBox += strings.Repeat(pb.Empty, emptySize/emptyLen)
386				barBox += pb.BarEnd
387			} else {
388				pos := size - int(current)%int(size)
389				barBox = pb.BarStart
390				if pos-1 > 0 {
391					barBox += strings.Repeat(pb.Empty, pos-1)
392				}
393				barBox += pb.Current
394				if size-pos-1 > 0 {
395					barBox += strings.Repeat(pb.Empty, size-pos-1)
396				}
397				barBox += pb.BarEnd
398			}
399		}
400	}
401
402	// check len
403	out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
404
405	if cl := escapeAwareRuneCountInString(out); cl < width {
406		end = strings.Repeat(" ", width-cl)
407	}
408
409	// and print!
410	pb.lastPrint = out + end
411	isFinish := pb.isFinish
412
413	switch {
414	case isFinish:
415		return
416	case pb.Output != nil:
417		fmt.Fprint(pb.Output, "\r"+out+end)
418	case pb.Callback != nil:
419		pb.Callback(out + end)
420	case !pb.NotPrint:
421		fmt.Print("\r" + out + end)
422	}
423}
424
425// GetTerminalWidth - returns terminal width for all platforms.
426func GetTerminalWidth() (int, error) {
427	return terminalWidth()
428}
429
430func (pb *ProgressBar) GetWidth() int {
431	if pb.ForceWidth {
432		return pb.Width
433	}
434
435	width := pb.Width
436	termWidth, _ := terminalWidth()
437	if width == 0 || termWidth <= width {
438		width = termWidth
439	}
440
441	return width
442}
443
444// Write the current state of the progressbar
445func (pb *ProgressBar) Update() {
446	c := atomic.LoadInt64(&pb.current)
447	p := atomic.LoadInt64(&pb.previous)
448	t := atomic.LoadInt64(&pb.Total)
449	if p != c {
450		pb.mu.Lock()
451		pb.changeTime = time.Now()
452		pb.mu.Unlock()
453		atomic.StoreInt64(&pb.previous, c)
454	}
455	pb.write(t, c)
456	if pb.AutoStat {
457		if c == 0 {
458			pb.startTime = time.Now()
459			pb.startValue = 0
460		} else if c >= t && pb.isFinish != true {
461			pb.Finish()
462		}
463	}
464}
465
466// String return the last bar print
467func (pb *ProgressBar) String() string {
468	pb.mu.Lock()
469	defer pb.mu.Unlock()
470	return pb.lastPrint
471}
472
473// SetTotal atomically sets new total count
474func (pb *ProgressBar) SetTotal(total int) *ProgressBar {
475	return pb.SetTotal64(int64(total))
476}
477
478// SetTotal64 atomically sets new total count
479func (pb *ProgressBar) SetTotal64(total int64) *ProgressBar {
480	atomic.StoreInt64(&pb.Total, total)
481	return pb
482}
483
484// Reset bar and set new total count
485// Does effect only on finished bar
486func (pb *ProgressBar) Reset(total int) *ProgressBar {
487	pb.mu.Lock()
488	defer pb.mu.Unlock()
489	if pb.isFinish {
490		pb.SetTotal(total).Set(0)
491		atomic.StoreInt64(&pb.previous, 0)
492	}
493	return pb
494}
495
496// Internal loop for refreshing the progressbar
497func (pb *ProgressBar) refresher() {
498	for {
499		select {
500		case <-pb.finish:
501			return
502		case <-time.After(pb.RefreshRate):
503			pb.Update()
504		}
505	}
506}
507