diff --git a/go.mod b/go.mod index 17ab729f..223d6f30 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-ole/go-ole v1.3.0 github.com/onsi/ginkgo/v2 v2.27.2 github.com/onsi/gomega v1.38.2 - github.com/schollz/progressbar/v3 v3.18.0 + github.com/schollz/progressbar/v3 v3.19.0 github.com/sirupsen/logrus v1.9.3 github.com/ulikunitz/xz v0.5.15 go.podman.io/common v0.0.0-20250901164813-7046ad001ce8 diff --git a/go.sum b/go.sum index f0c06305..08ad1397 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA= -github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= +github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc= +github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/vendor/github.com/schollz/progressbar/v3/progressbar.go b/vendor/github.com/schollz/progressbar/v3/progressbar.go index 0ccec7d3..99222a3c 100644 --- a/vendor/github.com/schollz/progressbar/v3/progressbar.go +++ b/vendor/github.com/schollz/progressbar/v3/progressbar.go @@ -6,11 +6,11 @@ import ( "errors" "fmt" "io" - "log" "math" "net/http" "os" "regexp" + "runtime" "strings" "sync" "time" @@ -79,6 +79,8 @@ type config struct { // whether the output is expected to contain color codes colorCodes bool + // custom colors to use for colorCodes + customColors map[string]string // show rate of change in kB/sec or MB/sec showBytes bool @@ -280,6 +282,14 @@ func OptionEnableColorCodes(colorCodes bool) Option { } } +// OptionSetCutomColorCodes overrides DefaultColors for color codes +// using mitchellh/colorstring.Colorize in func customColorstringColor +func OptionSetCustomColorCodes(customColors map[string]string) Option { + return func(p *ProgressBar) { + p.config.customColors = customColors + } +} + // OptionSetElapsedTime will enable elapsed time. Always enabled if OptionSetPredictTime is true. func OptionSetElapsedTime(elapsedTime bool) Option { return func(p *ProgressBar) { @@ -449,17 +459,15 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { go func() { 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() - } + + for range ticker.C { + if b.IsFinished() { + return + } + if b.IsStarted() { + b.lock.Lock() + b.render() + b.lock.Unlock() } } }() @@ -806,6 +814,13 @@ func (p *ProgressBar) Describe(description string) { if p.config.invisible { return } + // if already finished, re-render with the new description + if p.state.finished && !p.config.clearOnFinish && !p.config.useANSICodes { + clearProgressBar(p.config, p.state) + io.Copy(p.config.writer, &p.config.stdBuffer) + renderProgressBar(p.config, &p.state) + return + } p.render() } @@ -939,7 +954,13 @@ func (p *ProgressBar) render() error { if p.config.maxDetailRow > 0 { p.renderDetails() // put the cursor back to the last line of the details - writeString(p.config, fmt.Sprintf("\u001B[%dB\r\u001B[%dC", p.config.maxDetailRow, len(p.state.details[len(p.state.details)-1]))) + var lastDetailLength int + if len(p.state.details) == 0 { + lastDetailLength = 0 + } else { + lastDetailLength = len(p.state.details[len(p.state.details)-1]) + } + writeString(p.config, fmt.Sprintf("\u001B[%dB\r\u001B[%dC", p.config.maxDetailRow, lastDetailLength)) } if p.config.onCompletion != nil { p.config.onCompletion() @@ -976,14 +997,12 @@ func (p *ProgressBar) render() error { func (p *ProgressBar) lengthUnknown() { p.config.ignoreLength = true p.config.max = int64(p.config.width) - p.config.predictTime = false } // lengthKnown sets the progress bar to do not ignore the length func (p *ProgressBar) lengthKnown(max int64) { p.config.ignoreLength = false p.config.max = max - p.config.predictTime = true } // State returns the current state @@ -1014,36 +1033,68 @@ func (p *ProgressBar) State() State { // StartHTTPServer starts an HTTP server dedicated to serving progress bar updates. This allows you to // display the status in various UI elements, such as an OS status bar with an `xbar` extension. -// It is recommended to run this function in a separate goroutine to avoid blocking the main thread. +// When the progress bar is finished, call `server.Shutdown()` or `server.Close()` to shut it down manually. // // hostPort specifies the address and port to bind the server to, for example, "0.0.0.0:19999". -func (p *ProgressBar) StartHTTPServer(hostPort string) { - // for advanced users, we can return the data as json - http.HandleFunc("/state", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/json") - // since the state is a simple struct, we can just ignore the error +func (p *ProgressBar) StartHTTPServer(hostPort string) *http.Server { + mux := http.NewServeMux() + + // register routes + mux.HandleFunc("/state", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") bs, _ := json.Marshal(p.State()) w.Write(bs) }) - // for others, we just return the description in a plain text format - http.HandleFunc("/desc", func(w http.ResponseWriter, r *http.Request) { + + mux.HandleFunc("/desc", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") + state := p.State() fmt.Fprintf(w, "%d/%d, %.2f%%, %s left", - p.State().CurrentNum, p.State().Max, p.State().CurrentPercent*100, - (time.Second * time.Duration(p.State().SecondsLeft)).String(), + state.CurrentNum, state.Max, state.CurrentPercent*100, + (time.Second * time.Duration(state.SecondsLeft)).String(), ) }) - log.Fatal(http.ListenAndServe(hostPort, nil)) + + // create the server instance + server := &http.Server{ + Addr: hostPort, + Handler: mux, + } + + // start the server in a goroutine and ignore errors + go func() { + defer func() { + if err := recover(); err != nil { + fmt.Println("encounter panic: ", err) + } + }() + + _ = server.ListenAndServe() + }() + + // return the server instance for use by the caller + return server +} + +// use customstring.Colorize to customize color palette +func customColorstringColor(col map[string]string, str string) string { + cs := colorstring.Colorize{Colors: col, Reset: true} + return cs.Color(str) } // regex matching ansi escape codes var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`) -func getStringWidth(c config, str string, colorize bool) int { +func getStringWidth(c config, str string) int { if c.colorCodes { // convert any color codes in the progress bar into the respective ANSI codes - str = colorstring.Color(str) + // if customColors have been option set - create cs with Colorize + if len(c.customColors) > 0 { + str = customColorstringColor(c.customColors, str) + } else { + str = colorstring.Color(str) + } } // the width of the string, if printed to the console @@ -1168,7 +1219,7 @@ func renderProgressBar(c config, s *state) (int, error) { } if c.fullWidth && !c.ignoreLength { - width, err := termWidth() + width, err := termWidth(c.writer) if err != nil { width = 80 } @@ -1186,7 +1237,7 @@ func renderProgressBar(c config, s *state) (int, error) { amend += 1 // another space } - c.width = width - getStringWidth(c, c.description, true) - 10 - amend - sb.Len() - len(leftBrac) - len(rightBrac) + c.width = width - getStringWidth(c, c.description) - 10 - amend - sb.Len() - len(leftBrac) - len(rightBrac) s.currentSaucerSize = int(float64(s.currentPercent) / 100.0 * float64(c.width)) } if (s.currentSaucerSize > 0 || s.currentPercent > 0) && c.theme.BarStartFilled != "" { @@ -1330,12 +1381,21 @@ func renderProgressBar(c config, s *state) (int, error) { if c.colorCodes { // convert any color codes in the progress bar into the respective ANSI codes - str = colorstring.Color(str) + // if customColors have been option set - create cs with Colorize + if len(c.customColors) > 0 { + str = customColorstringColor(c.customColors, str) + } else { + str = colorstring.Color(str) + } + } + + if c.useANSICodes { + str = str + "\033[0K" } s.rendered = str - return getStringWidth(c, str, false), writeString(c, str) + return getStringWidth(c, str), writeString(c, str) } func clearProgressBar(c config, s state) error { @@ -1349,6 +1409,9 @@ func clearProgressBar(c config, s state) error { // fill the empty content // to overwrite the progress bar and jump // back to the beginning of the line + if runtime.GOOS == "windows" { + return writeString(c, "\r") + } str := fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth)) return writeString(c, str) // the following does not show correctly if the previous line is longer than subsequent line @@ -1394,7 +1457,7 @@ func (r *Reader) Read(p []byte) (n int, err error) { // Close the reader when it implements io.Closer func (r *Reader) Close() (err error) { if closer, ok := r.Reader.(io.Closer); ok { - return closer.Close() + err = closer.Close() } r.bar.Finish() return @@ -1456,12 +1519,15 @@ func logn(n, b float64) float64 { // termWidth function returns the visible width of the current terminal // and can be redefined for testing -var termWidth = func() (width int, err error) { - width, _, err = term.GetSize(int(os.Stdout.Fd())) - if err == nil { - return width, nil +var termWidth = func(w io.Writer) (width int, err error) { + if f, ok := w.(*os.File); ok { + width, _, err = term.GetSize(int(f.Fd())) + if err == nil { + return width, nil + } + } else { + err = errors.New("output is not a *os.File") } - return 0, err } diff --git a/vendor/modules.txt b/vendor/modules.txt index 66693df9..85bb5edb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -64,7 +64,7 @@ github.com/onsi/gomega/types # github.com/rivo/uniseg v0.4.7 ## explicit; go 1.18 github.com/rivo/uniseg -# github.com/schollz/progressbar/v3 v3.18.0 +# github.com/schollz/progressbar/v3 v3.19.0 ## explicit; go 1.22 github.com/schollz/progressbar/v3 # github.com/sirupsen/logrus v1.9.3