1package pb
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"os"
8	"strconv"
9	"strings"
10	"sync"
11	"sync/atomic"
12	"text/template"
13	"time"
14
15	"github.com/fatih/color"
16
17	"github.com/mattn/go-colorable"
18	"github.com/mattn/go-isatty"
19
20	"github.com/cheggaaa/pb/v3/termutil"
21)
22
23// Version of ProgressBar library
24const Version = "3.0.8"
25
26type key int
27
28const (
29	// Bytes means we're working with byte sizes. Numbers will print as Kb, Mb, etc
30	// bar.Set(pb.Bytes, true)
31	Bytes key = 1 << iota
32
33	// Use SI bytes prefix names (kB, MB, etc) instead of IEC prefix names (KiB, MiB, etc)
34	SIBytesPrefix
35
36	// Terminal means we're will print to terminal and can use ascii sequences
37	// Also we're will try to use terminal width
38	Terminal
39
40	// Static means progress bar will not update automaticly
41	Static
42
43	// ReturnSymbol - by default in terminal mode it's '\r'
44	ReturnSymbol
45
46	// Color by default is true when output is tty, but you can set to false for disabling colors
47	Color
48
49	// Hide the progress bar when finished, rather than leaving it up. By default it's false.
50	CleanOnFinish
51
52	// Round elapsed time to this precision. Defaults to time.Second.
53	TimeRound
54)
55
56const (
57	defaultBarWidth    = 100
58	defaultRefreshRate = time.Millisecond * 200
59)
60
61// New creates new ProgressBar object
62func New(total int) *ProgressBar {
63	return New64(int64(total))
64}
65
66// New64 creates new ProgressBar object using int64 as total
67func New64(total int64) *ProgressBar {
68	pb := new(ProgressBar)
69	return pb.SetTotal(total)
70}
71
72// StartNew starts new ProgressBar with Default template
73func StartNew(total int) *ProgressBar {
74	return New(total).Start()
75}
76
77// Start64 starts new ProgressBar with Default template. Using int64 as total.
78func Start64(total int64) *ProgressBar {
79	return New64(total).Start()
80}
81
82var (
83	terminalWidth    = termutil.TerminalWidth
84	isTerminal       = isatty.IsTerminal
85	isCygwinTerminal = isatty.IsCygwinTerminal
86)
87
88// ProgressBar is the main object of bar
89type ProgressBar struct {
90	current, total int64
91	width          int
92	maxWidth       int
93	mu             sync.RWMutex
94	rm             sync.Mutex
95	vars           map[interface{}]interface{}
96	elements       map[string]Element
97	output         io.Writer
98	coutput        io.Writer
99	nocoutput      io.Writer
100	startTime      time.Time
101	refreshRate    time.Duration
102	tmpl           *template.Template
103	state          *State
104	buf            *bytes.Buffer
105	ticker         *time.Ticker
106	finish         chan struct{}
107	finished       bool
108	configured     bool
109	err            error
110}
111
112func (pb *ProgressBar) configure() {
113	if pb.configured {
114		return
115	}
116	pb.configured = true
117
118	if pb.vars == nil {
119		pb.vars = make(map[interface{}]interface{})
120	}
121	if pb.output == nil {
122		pb.output = os.Stderr
123	}
124
125	if pb.tmpl == nil {
126		pb.tmpl, pb.err = getTemplate(string(Default))
127		if pb.err != nil {
128			return
129		}
130	}
131	if pb.vars[Terminal] == nil {
132		if f, ok := pb.output.(*os.File); ok {
133			if isTerminal(f.Fd()) || isCygwinTerminal(f.Fd()) {
134				pb.vars[Terminal] = true
135			}
136		}
137	}
138	if pb.vars[ReturnSymbol] == nil {
139		if tm, ok := pb.vars[Terminal].(bool); ok && tm {
140			pb.vars[ReturnSymbol] = "\r"
141		}
142	}
143	if pb.vars[Color] == nil {
144		if tm, ok := pb.vars[Terminal].(bool); ok && tm {
145			pb.vars[Color] = true
146		}
147	}
148	if pb.refreshRate == 0 {
149		pb.refreshRate = defaultRefreshRate
150	}
151	if pb.vars[CleanOnFinish] == nil {
152		pb.vars[CleanOnFinish] = false
153	}
154	if f, ok := pb.output.(*os.File); ok {
155		pb.coutput = colorable.NewColorable(f)
156	} else {
157		pb.coutput = pb.output
158	}
159	pb.nocoutput = colorable.NewNonColorable(pb.output)
160}
161
162// Start starts the bar
163func (pb *ProgressBar) Start() *ProgressBar {
164	pb.mu.Lock()
165	defer pb.mu.Unlock()
166	if pb.finish != nil {
167		return pb
168	}
169	pb.configure()
170	pb.finished = false
171	pb.state = nil
172	pb.startTime = time.Now()
173	if st, ok := pb.vars[Static].(bool); ok && st {
174		return pb
175	}
176	pb.finish = make(chan struct{})
177	pb.ticker = time.NewTicker(pb.refreshRate)
178	go pb.writer(pb.finish)
179	return pb
180}
181
182func (pb *ProgressBar) writer(finish chan struct{}) {
183	for {
184		select {
185		case <-pb.ticker.C:
186			pb.write(false)
187		case <-finish:
188			pb.ticker.Stop()
189			pb.write(true)
190			finish <- struct{}{}
191			return
192		}
193	}
194}
195
196// Write performs write to the output
197func (pb *ProgressBar) Write() *ProgressBar {
198	pb.mu.RLock()
199	finished := pb.finished
200	pb.mu.RUnlock()
201	pb.write(finished)
202	return pb
203}
204
205func (pb *ProgressBar) write(finish bool) {
206	result, width := pb.render()
207	if pb.Err() != nil {
208		return
209	}
210	if pb.GetBool(Terminal) {
211		if r := (width - CellCount(result)); r > 0 {
212			result += strings.Repeat(" ", r)
213		}
214	}
215	if ret, ok := pb.Get(ReturnSymbol).(string); ok {
216		result = ret + result
217		if finish && ret == "\r" {
218			if pb.GetBool(CleanOnFinish) {
219				// "Wipe out" progress bar by overwriting one line with blanks
220				result = "\r" + color.New(color.Reset).Sprintf(strings.Repeat(" ", width)) + "\r"
221			} else {
222				result += "\n"
223			}
224		}
225	}
226	if pb.GetBool(Color) {
227		pb.coutput.Write([]byte(result))
228	} else {
229		pb.nocoutput.Write([]byte(result))
230	}
231}
232
233// Total return current total bar value
234func (pb *ProgressBar) Total() int64 {
235	return atomic.LoadInt64(&pb.total)
236}
237
238// SetTotal sets the total bar value
239func (pb *ProgressBar) SetTotal(value int64) *ProgressBar {
240	atomic.StoreInt64(&pb.total, value)
241	return pb
242}
243
244// AddTotal adds to the total bar value
245func (pb *ProgressBar) AddTotal(value int64) *ProgressBar {
246	atomic.AddInt64(&pb.total, value)
247	return pb
248}
249
250// SetCurrent sets the current bar value
251func (pb *ProgressBar) SetCurrent(value int64) *ProgressBar {
252	atomic.StoreInt64(&pb.current, value)
253	return pb
254}
255
256// Current return current bar value
257func (pb *ProgressBar) Current() int64 {
258	return atomic.LoadInt64(&pb.current)
259}
260
261// Add adding given int64 value to bar value
262func (pb *ProgressBar) Add64(value int64) *ProgressBar {
263	atomic.AddInt64(&pb.current, value)
264	return pb
265}
266
267// Add adding given int value to bar value
268func (pb *ProgressBar) Add(value int) *ProgressBar {
269	return pb.Add64(int64(value))
270}
271
272// Increment atomically increments the progress
273func (pb *ProgressBar) Increment() *ProgressBar {
274	return pb.Add64(1)
275}
276
277// Set sets any value by any key
278func (pb *ProgressBar) Set(key, value interface{}) *ProgressBar {
279	pb.mu.Lock()
280	defer pb.mu.Unlock()
281	if pb.vars == nil {
282		pb.vars = make(map[interface{}]interface{})
283	}
284	pb.vars[key] = value
285	return pb
286}
287
288// Get return value by key
289func (pb *ProgressBar) Get(key interface{}) interface{} {
290	pb.mu.RLock()
291	defer pb.mu.RUnlock()
292	if pb.vars == nil {
293		return nil
294	}
295	return pb.vars[key]
296}
297
298// GetBool return value by key and try to convert there to boolean
299// If value doesn't set or not boolean - return false
300func (pb *ProgressBar) GetBool(key interface{}) bool {
301	if v, ok := pb.Get(key).(bool); ok {
302		return v
303	}
304	return false
305}
306
307// SetWidth sets the bar width
308// When given value <= 0 would be using the terminal width (if possible) or default value.
309func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
310	pb.mu.Lock()
311	pb.width = width
312	pb.mu.Unlock()
313	return pb
314}
315
316// SetMaxWidth sets the bar maximum width
317// When given value <= 0 would be using the terminal width (if possible) or default value.
318func (pb *ProgressBar) SetMaxWidth(maxWidth int) *ProgressBar {
319	pb.mu.Lock()
320	pb.maxWidth = maxWidth
321	pb.mu.Unlock()
322	return pb
323}
324
325// Width return the bar width
326// It's current terminal width or settled over 'SetWidth' value.
327func (pb *ProgressBar) Width() (width int) {
328	defer func() {
329		if r := recover(); r != nil {
330			width = defaultBarWidth
331		}
332	}()
333	pb.mu.RLock()
334	width = pb.width
335	maxWidth := pb.maxWidth
336	pb.mu.RUnlock()
337	if width <= 0 {
338		var err error
339		if width, err = terminalWidth(); err != nil {
340			return defaultBarWidth
341		}
342	}
343	if maxWidth > 0 && width > maxWidth {
344		width = maxWidth
345	}
346	return
347}
348
349func (pb *ProgressBar) SetRefreshRate(dur time.Duration) *ProgressBar {
350	pb.mu.Lock()
351	if dur > 0 {
352		pb.refreshRate = dur
353	}
354	pb.mu.Unlock()
355	return pb
356}
357
358// SetWriter sets the io.Writer. Bar will write in this writer
359// By default this is os.Stderr
360func (pb *ProgressBar) SetWriter(w io.Writer) *ProgressBar {
361	pb.mu.Lock()
362	pb.output = w
363	pb.configured = false
364	pb.configure()
365	pb.mu.Unlock()
366	return pb
367}
368
369// StartTime return the time when bar started
370func (pb *ProgressBar) StartTime() time.Time {
371	pb.mu.RLock()
372	defer pb.mu.RUnlock()
373	return pb.startTime
374}
375
376// Format convert int64 to string according to the current settings
377func (pb *ProgressBar) Format(v int64) string {
378	if pb.GetBool(Bytes) {
379		return formatBytes(v, pb.GetBool(SIBytesPrefix))
380	}
381	return strconv.FormatInt(v, 10)
382}
383
384// Finish stops the bar
385func (pb *ProgressBar) Finish() *ProgressBar {
386	pb.mu.Lock()
387	if pb.finished {
388		pb.mu.Unlock()
389		return pb
390	}
391	finishChan := pb.finish
392	pb.finished = true
393	pb.mu.Unlock()
394	if finishChan != nil {
395		finishChan <- struct{}{}
396		<-finishChan
397		pb.mu.Lock()
398		pb.finish = nil
399		pb.mu.Unlock()
400	}
401	return pb
402}
403
404// IsStarted indicates progress bar state
405func (pb *ProgressBar) IsStarted() bool {
406	pb.mu.RLock()
407	defer pb.mu.RUnlock()
408	return pb.finish != nil
409}
410
411// SetTemplateString sets ProgressBar tempate string and parse it
412func (pb *ProgressBar) SetTemplateString(tmpl string) *ProgressBar {
413	pb.mu.Lock()
414	defer pb.mu.Unlock()
415	pb.tmpl, pb.err = getTemplate(tmpl)
416	return pb
417}
418
419// SetTemplateString sets ProgressBarTempate and parse it
420func (pb *ProgressBar) SetTemplate(tmpl ProgressBarTemplate) *ProgressBar {
421	return pb.SetTemplateString(string(tmpl))
422}
423
424// NewProxyReader creates a wrapper for given reader, but with progress handle
425// Takes io.Reader or io.ReadCloser
426// Also, it automatically switches progress bar to handle units as bytes
427func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
428	pb.Set(Bytes, true)
429	return &Reader{r, pb}
430}
431
432// NewProxyWriter creates a wrapper for given writer, but with progress handle
433// Takes io.Writer or io.WriteCloser
434// Also, it automatically switches progress bar to handle units as bytes
435func (pb *ProgressBar) NewProxyWriter(r io.Writer) *Writer {
436	pb.Set(Bytes, true)
437	return &Writer{r, pb}
438}
439
440func (pb *ProgressBar) render() (result string, width int) {
441	defer func() {
442		if r := recover(); r != nil {
443			pb.SetErr(fmt.Errorf("render panic: %v", r))
444		}
445	}()
446	pb.rm.Lock()
447	defer pb.rm.Unlock()
448	pb.mu.Lock()
449	pb.configure()
450	if pb.state == nil {
451		pb.state = &State{ProgressBar: pb}
452		pb.buf = bytes.NewBuffer(nil)
453	}
454	if pb.startTime.IsZero() {
455		pb.startTime = time.Now()
456	}
457	pb.state.id++
458	pb.state.finished = pb.finished
459	pb.state.time = time.Now()
460	pb.mu.Unlock()
461
462	pb.state.width = pb.Width()
463	width = pb.state.width
464	pb.state.total = pb.Total()
465	pb.state.current = pb.Current()
466	pb.buf.Reset()
467
468	if e := pb.tmpl.Execute(pb.buf, pb.state); e != nil {
469		pb.SetErr(e)
470		return "", 0
471	}
472
473	result = pb.buf.String()
474
475	aec := len(pb.state.recalc)
476	if aec == 0 {
477		// no adaptive elements
478		return
479	}
480
481	staticWidth := CellCount(result) - (aec * adElPlaceholderLen)
482
483	if pb.state.Width()-staticWidth <= 0 {
484		result = strings.Replace(result, adElPlaceholder, "", -1)
485		result = StripString(result, pb.state.Width())
486	} else {
487		pb.state.adaptiveElWidth = (width - staticWidth) / aec
488		for _, el := range pb.state.recalc {
489			result = strings.Replace(result, adElPlaceholder, el.ProgressElement(pb.state), 1)
490		}
491	}
492	pb.state.recalc = pb.state.recalc[:0]
493	return
494}
495
496// SetErr sets error to the ProgressBar
497// Error will be available over Err()
498func (pb *ProgressBar) SetErr(err error) *ProgressBar {
499	pb.mu.Lock()
500	pb.err = err
501	pb.mu.Unlock()
502	return pb
503}
504
505// Err return possible error
506// When all ok - will be nil
507// May contain template.Execute errors
508func (pb *ProgressBar) Err() error {
509	pb.mu.RLock()
510	defer pb.mu.RUnlock()
511	return pb.err
512}
513
514// String return currrent string representation of ProgressBar
515func (pb *ProgressBar) String() string {
516	res, _ := pb.render()
517	return res
518}
519
520// ProgressElement implements Element interface
521func (pb *ProgressBar) ProgressElement(s *State, args ...string) string {
522	if s.IsAdaptiveWidth() {
523		pb.SetWidth(s.AdaptiveElWidth())
524	}
525	return pb.String()
526}
527
528// State represents the current state of bar
529// Need for bar elements
530type State struct {
531	*ProgressBar
532
533	id                     uint64
534	total, current         int64
535	width, adaptiveElWidth int
536	finished, adaptive     bool
537	time                   time.Time
538
539	recalc []Element
540}
541
542// Id it's the current state identifier
543// - incremental
544// - starts with 1
545// - resets after finish/start
546func (s *State) Id() uint64 {
547	return s.id
548}
549
550// Total it's bar int64 total
551func (s *State) Total() int64 {
552	return s.total
553}
554
555// Value it's current value
556func (s *State) Value() int64 {
557	return s.current
558}
559
560// Width of bar
561func (s *State) Width() int {
562	return s.width
563}
564
565// AdaptiveElWidth - adaptive elements must return string with given cell count (when AdaptiveElWidth > 0)
566func (s *State) AdaptiveElWidth() int {
567	return s.adaptiveElWidth
568}
569
570// IsAdaptiveWidth returns true when element must be shown as adaptive
571func (s *State) IsAdaptiveWidth() bool {
572	return s.adaptive
573}
574
575// IsFinished return true when bar is finished
576func (s *State) IsFinished() bool {
577	return s.finished
578}
579
580// IsFirst return true only in first render
581func (s *State) IsFirst() bool {
582	return s.id == 1
583}
584
585// Time when state was created
586func (s *State) Time() time.Time {
587	return s.time
588}
589