1// Package progress provides a simple terminal progress bar.
2package progress
3
4import (
5	"bytes"
6	"fmt"
7	"html/template"
8	"io"
9	"math"
10	"strings"
11)
12
13// Bar is a progress bar.
14type Bar struct {
15	StartDelimiter string  // StartDelimiter for the bar ("|").
16	EndDelimiter   string  // EndDelimiter for the bar ("|").
17	Filled         string  // Filled section representation ("█").
18	Empty          string  // Empty section representation ("░")
19	Total          float64 // Total value.
20	Width          int     // Width of the bar.
21
22	value float64
23	tmpl  *template.Template
24	text  string
25}
26
27// New returns a new bar with the given total.
28func New(total float64) *Bar {
29	b := &Bar{
30		StartDelimiter: "|",
31		EndDelimiter:   "|",
32		Filled:         "█",
33		Empty:          "░",
34		Total:          total,
35		Width:          60,
36	}
37
38	b.Template(`{{.Percent | printf "%3.0f"}}% {{.Bar}} {{.Text}}`)
39
40	return b
41}
42
43// NewInt returns a new bar with the given total.
44func NewInt(total int) *Bar {
45	return New(float64(total))
46}
47
48// Text sets the text value.
49func (b *Bar) Text(s string) {
50	b.text = s
51}
52
53// Value sets the value.
54func (b *Bar) Value(n float64) {
55	if n > b.Total {
56		panic("Bar update value cannot be greater than the total")
57	}
58	b.value = n
59}
60
61// ValueInt sets the value.
62func (b *Bar) ValueInt(n int) {
63	b.Value(float64(n))
64}
65
66// Percent returns the percentage
67func (b *Bar) percent() float64 {
68	return (b.value / b.Total) * 100
69}
70
71// Bar returns the progress bar string.
72func (b *Bar) bar() string {
73	p := b.value / b.Total
74	filled := math.Ceil(float64(b.Width) * p)
75	empty := math.Floor(float64(b.Width) - filled)
76	s := b.StartDelimiter
77	s += strings.Repeat(b.Filled, int(filled))
78	s += strings.Repeat(b.Empty, int(empty))
79	s += b.EndDelimiter
80	return s
81}
82
83// String returns the progress bar.
84func (b *Bar) String() string {
85	var buf bytes.Buffer
86
87	data := struct {
88		Value          float64
89		Total          float64
90		Percent        float64
91		StartDelimiter string
92		EndDelimiter   string
93		Bar            string
94		Text           string
95	}{
96		Value:          b.value,
97		Text:           b.text,
98		StartDelimiter: b.StartDelimiter,
99		EndDelimiter:   b.EndDelimiter,
100		Percent:        b.percent(),
101		Bar:            b.bar(),
102	}
103
104	if err := b.tmpl.Execute(&buf, data); err != nil {
105		panic(err)
106	}
107
108	return buf.String()
109}
110
111// WriteTo writes the progress bar to w.
112func (b *Bar) WriteTo(w io.Writer) (int64, error) {
113	s := fmt.Sprintf("\r   %s ", b.String())
114	_, err := io.WriteString(w, s)
115	return int64(len(s)), err
116}
117
118// Template for rendering. This method will panic if the template fails to parse.
119func (b *Bar) Template(s string) {
120	t, err := template.New("").Parse(s)
121	if err != nil {
122		panic(err)
123	}
124
125	b.tmpl = t
126}
127