├── LICENSE
├── Readme.md
├── _example
└── main.go
├── ci.yml
├── progress.go
├── progress_example_test.go
└── progress_test.go
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2017 TJ Holowaychuk tj@tjholowaychuk.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
24 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Examples
4 |
5 | 
6 |
7 | ---
8 |
9 | [](https://godoc.org/github.com/tj/go-progress)
10 | 
11 | 
12 |
13 |
14 |
--------------------------------------------------------------------------------
/_example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "math/rand"
5 | "time"
6 |
7 | "github.com/tj/go-progress"
8 | "github.com/tj/go/term"
9 |
10 | color "github.com/aybabtme/rgbterm"
11 | )
12 |
13 | // gray string.
14 | func gray(s string) string {
15 | return color.FgString(s, 150, 150, 150)
16 | }
17 |
18 | // purple string.
19 | func purple(s string) string {
20 | return color.FgString(s, 96, 97, 190)
21 | }
22 |
23 | func main() {
24 | term.HideCursor()
25 | defer term.ShowCursor()
26 |
27 | b := progress.NewInt(10)
28 | b.Width = 40
29 | b.StartDelimiter = gray("|")
30 | b.EndDelimiter = gray("|")
31 | b.Filled = purple("█")
32 | b.Empty = gray("░")
33 |
34 | render := term.Renderer()
35 |
36 | for i := 0; i <= 10; i++ {
37 | b.ValueInt(i)
38 | time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
39 | render(term.CenterLine(b.String()))
40 | }
41 |
42 | term.ClearAll()
43 | }
44 |
--------------------------------------------------------------------------------
/ci.yml:
--------------------------------------------------------------------------------
1 | version: 0.2
2 |
3 | phases:
4 | install:
5 | commands:
6 | - go get -t ./...
7 | build:
8 | commands:
9 | - go test -cover -v ./...
10 |
--------------------------------------------------------------------------------
/progress.go:
--------------------------------------------------------------------------------
1 | // Package progress provides a simple terminal progress bar.
2 | package progress
3 |
4 | import (
5 | "bytes"
6 | "fmt"
7 | "html/template"
8 | "io"
9 | "math"
10 | "strings"
11 | )
12 |
13 | // Bar is a progress bar.
14 | type 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.
28 | func 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.
44 | func NewInt(total int) *Bar {
45 | return New(float64(total))
46 | }
47 |
48 | // Text sets the text value.
49 | func (b *Bar) Text(s string) {
50 | b.text = s
51 | }
52 |
53 | // Value sets the value.
54 | func (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.
62 | func (b *Bar) ValueInt(n int) {
63 | b.Value(float64(n))
64 | }
65 |
66 | // Percent returns the percentage
67 | func (b *Bar) percent() float64 {
68 | return (b.value / b.Total) * 100
69 | }
70 |
71 | // Bar returns the progress bar string.
72 | func (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.
84 | func (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.
112 | func (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.
119 | func (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 |
--------------------------------------------------------------------------------
/progress_example_test.go:
--------------------------------------------------------------------------------
1 | package progress_test
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/tj/go-progress"
8 | )
9 |
10 | func Example() {
11 | b := progress.NewInt(50)
12 |
13 | for i := 0; i <= 50; i++ {
14 | b.ValueInt(i)
15 | b.Text(fmt.Sprintf("iteration %d", i))
16 | b.WriteTo(os.Stdout)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/progress_test.go:
--------------------------------------------------------------------------------
1 | package progress
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "math/rand"
7 | "os"
8 | "testing"
9 | "time"
10 |
11 | "github.com/tj/assert"
12 | )
13 |
14 | var preview = flag.Bool("preview", false, "Preview output rendering.")
15 |
16 | func TestBar_previewWidth(t *testing.T) {
17 | if !*preview {
18 | t.SkipNow()
19 | }
20 |
21 | b := NewInt(10)
22 | b.Width = 25
23 | b.Empty = " "
24 |
25 | for i := 0; i <= 10; i++ {
26 | b.ValueInt(i)
27 | time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
28 | b.WriteTo(os.Stdout)
29 | }
30 | }
31 |
32 | func TestBar_previewDefaults(t *testing.T) {
33 | if !*preview {
34 | t.SkipNow()
35 | }
36 |
37 | b := NewInt(20)
38 |
39 | for i := 0; i <= 20; i++ {
40 | b.ValueInt(i)
41 | b.Text(fmt.Sprintf("iteration %d", i))
42 | time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
43 | b.WriteTo(os.Stdout)
44 | }
45 | }
46 |
47 | func TestBarString(t *testing.T) {
48 | b := NewInt(1000)
49 | assert.Equal(t, ` 0% |░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░| `, b.String())
50 |
51 | b.ValueInt(250)
52 | assert.Equal(t, ` 25% |███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░| `, b.String())
53 |
54 | b.ValueInt(750)
55 | assert.Equal(t, ` 75% |█████████████████████████████████████████████░░░░░░░░░░░░░░░| `, b.String())
56 |
57 | b.ValueInt(1000)
58 | assert.Equal(t, `100% |████████████████████████████████████████████████████████████| `, b.String())
59 | }
60 |
61 | func TestBarText(t *testing.T) {
62 | b := NewInt(1000)
63 |
64 | b.Text("Building")
65 | assert.Equal(t, ` 0% |░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░| Building`, b.String())
66 |
67 | b.Text("Installing")
68 | b.ValueInt(250)
69 | assert.Equal(t, ` 25% |███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░| Installing`, b.String())
70 | }
71 |
72 | func TestBarTemplate(t *testing.T) {
73 | b := NewInt(1000)
74 | b.Template(`{{.Bar}} {{.Percent}}%`)
75 |
76 | assert.Equal(t, `|░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░| 0%`, b.String())
77 |
78 | b.ValueInt(250)
79 | assert.Equal(t, `|███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░| 25%`, b.String())
80 |
81 | b.ValueInt(750)
82 | assert.Equal(t, `|█████████████████████████████████████████████░░░░░░░░░░░░░░░| 75%`, b.String())
83 |
84 | b.ValueInt(1000)
85 | assert.Equal(t, `|████████████████████████████████████████████████████████████| 100%`, b.String())
86 | }
87 |
88 | func TestBarDelimiters(t *testing.T) {
89 | b := NewInt(1000)
90 | b.StartDelimiter = "["
91 | b.EndDelimiter = "]"
92 | assert.Equal(t, ` 0% [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] `, b.String())
93 | }
94 |
--------------------------------------------------------------------------------