Skip to content

Commit 4f520db

Browse files
committed
add progress ui component WIP
1 parent d3fcc6c commit 4f520db

File tree

6 files changed

+240
-0
lines changed

6 files changed

+240
-0
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ require github.com/stretchr/testify v1.9.0
77
require (
88
github.com/davecgh/go-spew v1.1.1 // indirect
99
github.com/pmezard/go-difflib v1.0.0 // indirect
10+
golang.org/x/sys v0.22.0 // indirect
11+
golang.org/x/term v0.22.0 // indirect
1012
gopkg.in/yaml.v3 v3.0.1 // indirect
1113
)

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
44
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
66
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7+
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
8+
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
9+
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
10+
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
711
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
812
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
913
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

progress/example/main.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"time"
6+
7+
"github.com/dGilli/terminal-ui/progress"
8+
)
9+
10+
func main() {
11+
p := progress.New(progress.Config{})
12+
13+
log.Println("Starting the progress bar")
14+
15+
p.Update(50)
16+
17+
time.Sleep(time.Second * 1)
18+
p.Update(100)
19+
20+
log.Println("Progress complete")
21+
}

progress/progress.go

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package progress
2+
3+
import (
4+
"context"
5+
"io"
6+
"math"
7+
"os"
8+
"sync"
9+
"time"
10+
11+
"golang.org/x/term"
12+
)
13+
14+
type Progress struct {
15+
writer io.Writer
16+
glyph rune
17+
cols int
18+
current int
19+
cancelFunc context.CancelFunc
20+
doneCh chan struct{}
21+
lock sync.RWMutex
22+
}
23+
24+
type Config struct {
25+
Writer io.Writer
26+
Glyph *rune
27+
Cols *int
28+
}
29+
30+
func New(cfg Config) *Progress {
31+
p := &Progress{
32+
writer: os.Stderr,
33+
glyph: '#',
34+
cols: 80,
35+
}
36+
37+
if cfg.Writer != nil {
38+
p.writer = cfg.Writer
39+
}
40+
41+
if cfg.Glyph != nil {
42+
p.glyph = *cfg.Glyph
43+
}
44+
45+
if cfg.Cols != nil {
46+
p.cols = *cfg.Cols
47+
} else if term.IsTerminal(0) {
48+
width, _, err := term.GetSize(0)
49+
if err == nil {
50+
p.cols = width
51+
}
52+
}
53+
54+
return p
55+
}
56+
57+
type Bar struct {
58+
cols int32
59+
}
60+
61+
func (b *Bar) Update(current float64) {
62+
numGlyphs := int(math.Floor(current * float64(b.cols / 100)))
63+
64+
ticker := time.NewTicker(500 * time.Millisecond)
65+
done := make(chan bool)
66+
67+
go func() {
68+
time.Sleep(5 * time.Second)
69+
done <- true
70+
}()
71+
72+
for {
73+
select {
74+
case t := <-ticker.C:
75+
p.writer.Write([]byte("#"))
76+
case <-done:
77+
ticker.Stop()
78+
return
79+
}
80+
}
81+
82+
for i := 0; i < numGlyphs; i++ {
83+
b.writer.Write([]byte("#"))
84+
time.Sleep(time.Millisecond * 100)
85+
if i == 10 {
86+
p.writer.Write([]byte("\n"))
87+
}
88+
}
89+
}
90+
91+
func (p *Progress) Update(current int) {
92+
93+
/*
94+
if p.isComplete() {
95+
return
96+
}
97+
98+
complete := 80
99+
if current > complete {
100+
current = complete
101+
}
102+
103+
p.current = current
104+
*/
105+
106+
//repeatedRune := strings.Repeat(string(p.glyph), current / 100 * 80)
107+
108+
/*
109+
for i := 0; i < 10; i++ {
110+
p.writer.Write([]byte("#"))
111+
time.Sleep(time.Millisecond * 100)
112+
if i == 10 {
113+
p.writer.Write([]byte("\n"))
114+
}
115+
}
116+
p.writer.Write([]byte("\n"))
117+
*/
118+
119+
/*
120+
######################################################### / 100%
121+
*/
122+
123+
ticker := time.NewTicker(1 * time.Second)
124+
done := make(chan bool)
125+
126+
go func() {
127+
time.Sleep(5 * time.Second)
128+
done <- true
129+
}()
130+
131+
for {
132+
select {
133+
case t := <-ticker.C:
134+
p.writer.Write([]byte("#"))
135+
case <-done:
136+
ticker.Stop()
137+
return
138+
}
139+
}
140+
141+
//repeatedRune := strings.Repeat(string(p.glyph), 10)
142+
//p.writer.Write([]byte(repeatedRune))
143+
}
144+
145+
func (p *Progress) isComplete() bool {
146+
p.lock.RLock()
147+
defer p.lock.RUnlock()
148+
149+
return p.doneCh != nil
150+
}

progress/progress_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package progress_test
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"strings"
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/assert"
11+
12+
"github.com/dGilli/terminal-ui/progress"
13+
)
14+
15+
func TestProgressUpdate(t *testing.T) {
16+
testCases := []struct {
17+
name string
18+
progress int
19+
expects int // progress bar length relative to terminal wdith
20+
}{
21+
{
22+
name: "should update progress correctly",
23+
progress: 50,
24+
expects: 50,
25+
},
26+
{
27+
name: "should complete correctly",
28+
progress: 100,
29+
expects: 100,
30+
},
31+
{
32+
name: "should clamp progress to total",
33+
progress: 150,
34+
expects: 100,
35+
},
36+
}
37+
38+
for _, tc := range testCases {
39+
t.Run(tc.name, func(t *testing.T) {
40+
buf := &bytes.Buffer{}
41+
42+
p := progress.New(progress.Config{
43+
Writer: buf,
44+
Width: 100,
45+
})
46+
47+
p.Update(tc.progress)
48+
49+
time.Sleep(time.Second * 1)
50+
51+
data, err := io.ReadAll(buf)
52+
assert.NoError(t, err)
53+
54+
assert.Equal(t, strings.Repeat("#", tc.expects), string(data))
55+
})
56+
}
57+
}
58+
59+
func TestProgressWorksAsync(t *testing.T) {
60+
}
61+
62+
func TestAdjustsToTerminalSize(t *testing.T) {
63+
}

test.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)