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.2"
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
85	// Default width for the time box.
86	UnitsWidth   int
87	TimeBoxWidth int
88
89	finishOnce sync.Once //Guards isFinish
90	finish     chan struct{}
91	isFinish   bool
92
93	startTime    time.Time
94	startValue   int64
95	currentValue int64
96
97	prefix, postfix string
98
99	mu        *sync.Mutex
100	lastPrint string
101
102	BarStart string
103	BarEnd   string
104	Empty    string
105	Current  string
106	CurrentN string
107
108	AlwaysUpdate bool
109}
110
111// Start print
112func (pb *ProgressBar) Start() *ProgressBar {
113	pb.startTime = time.Now()
114	pb.startValue = pb.current
115	if pb.Total == 0 {
116		pb.ShowTimeLeft = false
117		pb.ShowPercent = false
118	}
119	if !pb.ManualUpdate {
120		pb.Update() // Initial printing of the bar before running the bar refresher.
121		go pb.refresher()
122	}
123	return pb
124}
125
126// Increment current value
127func (pb *ProgressBar) Increment() int {
128	return pb.Add(1)
129}
130
131// Set current value
132func (pb *ProgressBar) Set(current int) *ProgressBar {
133	return pb.Set64(int64(current))
134}
135
136// Set64 sets the current value as int64
137func (pb *ProgressBar) Set64(current int64) *ProgressBar {
138	atomic.StoreInt64(&pb.current, current)
139	return pb
140}
141
142// Add to current value
143func (pb *ProgressBar) Add(add int) int {
144	return int(pb.Add64(int64(add)))
145}
146
147func (pb *ProgressBar) Add64(add int64) int64 {
148	return atomic.AddInt64(&pb.current, add)
149}
150
151// Set prefix string
152func (pb *ProgressBar) Prefix(prefix string) *ProgressBar {
153	pb.prefix = prefix
154	return pb
155}
156
157// Set postfix string
158func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
159	pb.postfix = postfix
160	return pb
161}
162
163// Set custom format for bar
164// Example: bar.Format("[=>_]")
165// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
166func (pb *ProgressBar) Format(format string) *ProgressBar {
167	var formatEntries []string
168	if len(format) == 5 {
169		formatEntries = strings.Split(format, "")
170	} else {
171		formatEntries = strings.Split(format, "\x00")
172	}
173	if len(formatEntries) == 5 {
174		pb.BarStart = formatEntries[0]
175		pb.BarEnd = formatEntries[4]
176		pb.Empty = formatEntries[3]
177		pb.Current = formatEntries[1]
178		pb.CurrentN = formatEntries[2]
179	}
180	return pb
181}
182
183// Set bar refresh rate
184func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar {
185	pb.RefreshRate = rate
186	return pb
187}
188
189// Set units
190// bar.SetUnits(U_NO) - by default
191// bar.SetUnits(U_BYTES) - for Mb, Kb, etc
192func (pb *ProgressBar) SetUnits(units Units) *ProgressBar {
193	pb.Units = units
194	return pb
195}
196
197// Set max width, if width is bigger than terminal width, will be ignored
198func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar {
199	pb.Width = width
200	pb.ForceWidth = false
201	return pb
202}
203
204// Set bar width
205func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
206	pb.Width = width
207	pb.ForceWidth = true
208	return pb
209}
210
211// End print
212func (pb *ProgressBar) Finish() {
213	//Protect multiple calls
214	pb.finishOnce.Do(func() {
215		close(pb.finish)
216		pb.write(atomic.LoadInt64(&pb.current))
217		if !pb.NotPrint {
218			fmt.Println()
219		}
220		pb.isFinish = true
221	})
222}
223
224// End print and write string 'str'
225func (pb *ProgressBar) FinishPrint(str string) {
226	pb.Finish()
227	fmt.Println(str)
228}
229
230// implement io.Writer
231func (pb *ProgressBar) Write(p []byte) (n int, err error) {
232	n = len(p)
233	pb.Add(n)
234	return
235}
236
237// implement io.Reader
238func (pb *ProgressBar) Read(p []byte) (n int, err error) {
239	n = len(p)
240	pb.Add(n)
241	return
242}
243
244// Create new proxy reader over bar
245func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
246	return &Reader{r, pb}
247}
248
249func (pb *ProgressBar) write(current int64) {
250	width := pb.GetWidth()
251
252	var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string
253
254	// percents
255	if pb.ShowPercent {
256		var percent float64
257		if pb.Total > 0 {
258			percent = float64(current) / (float64(pb.Total) / float64(100))
259		} else {
260			percent = float64(current) / float64(100)
261		}
262		percentBox = fmt.Sprintf(" %6.02f%%", percent)
263	}
264
265	// counters
266	if pb.ShowCounters {
267		current := Format(current).To(pb.Units).Width(pb.UnitsWidth)
268		if pb.Total > 0 {
269			total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth)
270			countersBox = fmt.Sprintf(" %s / %s ", current, total)
271		} else {
272			countersBox = fmt.Sprintf(" %s / ? ", current)
273		}
274	}
275
276	// time left
277	fromStart := time.Now().Sub(pb.startTime)
278	currentFromStart := current - pb.startValue
279	select {
280	case <-pb.finish:
281		if pb.ShowFinalTime {
282			var left time.Duration
283			if pb.Total > 0 {
284				left = (fromStart / time.Second) * time.Second
285			} else {
286				left = (time.Duration(currentFromStart) / time.Second) * time.Second
287			}
288			timeLeftBox = left.String()
289		}
290	default:
291		if pb.ShowTimeLeft && currentFromStart > 0 {
292			perEntry := fromStart / time.Duration(currentFromStart)
293			var left time.Duration
294			if pb.Total > 0 {
295				left = time.Duration(pb.Total-currentFromStart) * perEntry
296				left = (left / time.Second) * time.Second
297			} else {
298				left = time.Duration(currentFromStart) * perEntry
299				left = (left / time.Second) * time.Second
300			}
301			timeLeft := Format(int64(left)).To(U_DURATION).String()
302			timeLeftBox = fmt.Sprintf(" %s", timeLeft)
303		}
304	}
305
306	if len(timeLeftBox) < pb.TimeBoxWidth {
307		timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox)
308	}
309
310	// speed
311	if pb.ShowSpeed && currentFromStart > 0 {
312		fromStart := time.Now().Sub(pb.startTime)
313		speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
314		speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String()
315	}
316
317	barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix)
318	// bar
319	if pb.ShowBar {
320		size := width - barWidth
321		if size > 0 {
322			if pb.Total > 0 {
323				curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size)))
324				emptCount := size - curCount
325				barBox = pb.BarStart
326				if emptCount < 0 {
327					emptCount = 0
328				}
329				if curCount > size {
330					curCount = size
331				}
332				if emptCount <= 0 {
333					barBox += strings.Repeat(pb.Current, curCount)
334				} else if curCount > 0 {
335					barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN
336				}
337				barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd
338			} else {
339				barBox = pb.BarStart
340				pos := size - int(current)%int(size)
341				if pos-1 > 0 {
342					barBox += strings.Repeat(pb.Empty, pos-1)
343				}
344				barBox += pb.Current
345				if size-pos-1 > 0 {
346					barBox += strings.Repeat(pb.Empty, size-pos-1)
347				}
348				barBox += pb.BarEnd
349			}
350		}
351	}
352
353	// check len
354	out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
355	if escapeAwareRuneCountInString(out) < width {
356		end = strings.Repeat(" ", width-utf8.RuneCountInString(out))
357	}
358
359	// and print!
360	pb.mu.Lock()
361	pb.lastPrint = out + end
362	pb.mu.Unlock()
363	switch {
364	case pb.isFinish:
365		return
366	case pb.Output != nil:
367		fmt.Fprint(pb.Output, "\r"+out+end)
368	case pb.Callback != nil:
369		pb.Callback(out + end)
370	case !pb.NotPrint:
371		fmt.Print("\r" + out + end)
372	}
373}
374
375// GetTerminalWidth - returns terminal width for all platforms.
376func GetTerminalWidth() (int, error) {
377	return terminalWidth()
378}
379
380func (pb *ProgressBar) GetWidth() int {
381	if pb.ForceWidth {
382		return pb.Width
383	}
384
385	width := pb.Width
386	termWidth, _ := terminalWidth()
387	if width == 0 || termWidth <= width {
388		width = termWidth
389	}
390
391	return width
392}
393
394// Write the current state of the progressbar
395func (pb *ProgressBar) Update() {
396	c := atomic.LoadInt64(&pb.current)
397	if pb.AlwaysUpdate || c != pb.currentValue {
398		pb.write(c)
399		pb.currentValue = c
400	}
401}
402
403func (pb *ProgressBar) String() string {
404	return pb.lastPrint
405}
406
407// Internal loop for refreshing the progressbar
408func (pb *ProgressBar) refresher() {
409	for {
410		select {
411		case <-pb.finish:
412			return
413		case <-time.After(pb.RefreshRate):
414			pb.Update()
415		}
416	}
417}
418
419type window struct {
420	Row    uint16
421	Col    uint16
422	Xpixel uint16
423	Ypixel uint16
424}
425