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