Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make progressbar could update according to an interval or updat… #199

Merged
merged 5 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
module github.com/schollz/progressbar/v3

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
github.com/rivo/uniseg v0.4.7
github.com/stretchr/testify v1.3.0
github.com/stretchr/testify v1.9.0
golang.org/x/term v0.24.0
)

go 1.13
require (
github.com/chengxilo/virtualterm v1.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.25.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

go 1.22
17 changes: 13 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/chengxilo/virtualterm v1.0.3 h1:Vycm/mKGeHuLXA4zK3XsaseOW7VMY6jJ88/9+XHSNcA=
github.com/chengxilo/virtualterm v1.0.3/go.mod h1:wjAbIDvnp6Vc8hQoM7tt6fcdk0NiSaQBSoSRwMIpphs=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
77 changes: 66 additions & 11 deletions progressbar.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type state struct {
counterTime time.Time
counterNumSinceLast int64
counterLastTenRates []float64
spinnerIdx int // the index of spinner

maxLineWidth int
currentBytes float64
Expand Down Expand Up @@ -105,6 +106,11 @@ type config struct {
// spinnerTypeOptionUsed remembers if the spinnerType was changed manually
spinnerTypeOptionUsed bool

// spinnerChangeInterval the change interval of spinner
// if set this attribute to 0, the spinner only change when renderProgressBar was called
// for example, each time when Add() was called,which will call renderProgressBar function
spinnerChangeInterval time.Duration

// spinner represents the spinner as a slice of string
spinner []string

Expand Down Expand Up @@ -151,6 +157,18 @@ func OptionSetWidth(s int) Option {
}
}

// OptionSetSpinnerChangeInterval sets the spinner change interval
// the spinner will change according to this value.
// By default, this value is 100 * time.Millisecond
// If you don't want to let this progressbar update by specified time interval
// you can set this value to zero, then the spinner will change each time rendered,
// such as when Add() or Describe() was called
func OptionSetSpinnerChangeInterval(interval time.Duration) Option {
return func(p *ProgressBar) {
p.config.spinnerChangeInterval = interval
}
}

// OptionSpinnerType sets the type of spinner used for indeterminate bars
func OptionSpinnerType(spinnerType int) Option {
return func(p *ProgressBar) {
Expand Down Expand Up @@ -337,16 +355,17 @@ func NewOptions64(max int64, options ...Option) *ProgressBar {
counterTime: time.Time{},
},
config: config{
writer: os.Stdout,
theme: defaultTheme,
iterationString: "it",
width: 40,
max: max,
throttleDuration: 0 * time.Nanosecond,
elapsedTime: max == -1,
predictTime: true,
spinnerType: 9,
invisible: false,
writer: os.Stdout,
theme: defaultTheme,
iterationString: "it",
width: 40,
max: max,
throttleDuration: 0 * time.Nanosecond,
elapsedTime: max == -1,
predictTime: true,
spinnerType: 9,
invisible: false,
spinnerChangeInterval: 100 * time.Millisecond,
},
}

Expand Down Expand Up @@ -374,6 +393,33 @@ func NewOptions64(max int64, options ...Option) *ProgressBar {
b.RenderBlank()
}

// if the render time interval attribute is set
if b.config.spinnerChangeInterval != 0 {
go func() {
if b.config.invisible {
return
}
if !b.config.ignoreLength {
return
}
ticker := time.NewTicker(b.config.spinnerChangeInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if b.IsFinished() {
return
}
if b.IsStarted() {
b.lock.Lock()
b.render()
b.lock.Unlock()
}
}
}
}()
}

return &b
}

Expand Down Expand Up @@ -1058,7 +1104,16 @@ func renderProgressBar(c config, s *state) (int, error) {
if len(c.spinner) > 0 {
selectedSpinner = c.spinner
}
spinner := selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(selectedSpinner)))))]

var spinner string
if c.spinnerChangeInterval != 0 {
// if the spinner is changed according to an interval, calculate it
spinner = selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Nanoseconds()/c.spinnerChangeInterval.Nanoseconds()), float64(len(selectedSpinner)))))]
} else {
// if the spinner is changed according to the number render was called
spinner = selectedSpinner[s.spinnerIdx]
s.spinnerIdx = (s.spinnerIdx + 1) % len(selectedSpinner)
}
if c.elapsedTime {
if c.showDescriptionAtLineEnd {
str = fmt.Sprintf("\r%s %s [%s] %s ",
Expand Down
95 changes: 83 additions & 12 deletions progressbar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/chengxilo/virtualterm"
"io"
"log"
"net/http"
"os"
"strings"
Expand Down Expand Up @@ -101,10 +103,8 @@ func TestSpinnerClearOnFinish(t *testing.T) {
bar.Add(10)
time.Sleep(1 * time.Second)
bar.Finish()
result := buf.String()
expect := "" +
"\r- (10 B, 10 B/s, 10 it/s) [1s] " +
"\r \r"
result, _ := virtualterm.Process(buf.String())
expect := " "
if result != expect {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
Expand All @@ -127,11 +127,12 @@ func TestSpinnerFinish(t *testing.T) {
bar.Add(10)
time.Sleep(1 * time.Second)
bar.Finish()
result := buf.String()
expect := "" +
"\r- (10 B, 10 B/s, 10 it/s) [1s] " +
"\r \r" +
"\r| (10 B, 5 B/s, 5 it/s) [2s] "
result, err := virtualterm.Process(buf.String())
if err != nil {
t.Error(err)
}
// the "\r \r"
expect := "| (10 B, 5 B/s, 5 it/s) [2s] "
if result != expect {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
Expand Down Expand Up @@ -214,15 +215,21 @@ func ExampleOptionShowIts_spinner() {
/*
Spinner test with iteration count and iteration rate
*/
vt := virtualterm.NewDefault()
bar := NewOptions(-1,
OptionSetWidth(10),
OptionShowIts(),
OptionShowCount(),
OptionSetWriter(&vt),
)
bar.Reset()
time.Sleep(1 * time.Second)
bar.Add(5)

s, err := vt.String()
if err != nil {
log.Fatal(err)
}
fmt.Print(s)
// Output:
// - (5/-, 5 it/s) [1s]
}
Expand Down Expand Up @@ -319,17 +326,20 @@ func ExampleOptionShowBytes_spinner() {
/*
Spinner test with iterations and count
*/
buf := strings.Builder{}
bar := NewOptions(-1,
OptionSetWidth(10),
OptionShowBytes(true),
OptionSetWriter(&buf),
)

bar.Reset()
time.Sleep(1 * time.Second)
// since 10 is the width and we don't know the max bytes
// it will do a infinite scrolling.
bar.Add(11)

result, _ := virtualterm.Process(buf.String())
fmt.Print(result)
// Output:
// - (11 B/s) [1s]
}
Expand Down Expand Up @@ -495,7 +505,11 @@ func TestOptionSetElapsedTime_spinner(t *testing.T) {
bar.Reset()
time.Sleep(1 * time.Second)
bar.Add(5)
result := strings.TrimSpace(buf.String())
result, err := virtualterm.Process(buf.String())
result = strings.TrimSpace(result)
if err != nil {
t.Fatal(err)
}
expect := "- (5/-, 5 it/s)"
if result != expect {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
Expand Down Expand Up @@ -929,3 +943,60 @@ func TestProgressBar_StartWithoutRender(t *testing.T) {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
}

func TestOptionSetSpinnerChangeInterval(t *testing.T) {
interval := 1000 * time.Millisecond
vt := virtualterm.NewDefault()
actuals := make([]string, 0, 8)
expecteds := []string{
"◐ test [0s]",
"◓ test [1s]",
"◑ test [2s]",
"◒ test [3s]",
"◐ test [4s]",
"◓ test [5s]",
"◑ test [6s]",
"◒ test [7s]",
}
bar := NewOptions(-1,
OptionSetDescription("test"),
OptionSpinnerType(7),
OptionSetWriter(&vt),
OptionSetSpinnerChangeInterval(interval))
bar.Add(1)
for i := 0; i < 8; i++ {
s, _ := vt.String()
s = strings.TrimSpace(s)
actuals = append(actuals, s)
// sleep 50 ms more to make sure to go to next interval each time
time.Sleep(1050 * time.Millisecond)
}
for i := range actuals {
assert.Equal(t, expecteds[i], actuals[i])
}
}

func TestOptionSetSpinnerChangeIntervalZero(t *testing.T) {
vt := virtualterm.NewDefault()
bar := NewOptions(-1,
OptionSetDescription("test"),
OptionSpinnerType(7),
OptionSetWriter(&vt),
OptionSetSpinnerChangeInterval(0))
actuals := make([]string, 0, 5)
expected := []string{
"◐ test [0s]",
"◓ test [1s]",
"◑ test [2s]",
"◒ test [3s]",
"◐ test [4s]",
}
for i := 0; i < 5; i++ {
bar.Add(1)
s, _ := vt.String()
s = strings.TrimSpace(s)
}
for i := range actuals {
assert.Equal(t, expected[i], actuals[i])
}
}
Loading