diff --git a/go.mod b/go.mod index bb64a5358c6d1..37e9113ef42c6 100644 --- a/go.mod +++ b/go.mod @@ -137,7 +137,7 @@ require ( github.com/prometheus/alertmanager v0.27.0 github.com/prometheus/common/sigv4 v0.1.0 github.com/richardartoul/molecule v1.0.0 - github.com/schollz/progressbar/v3 v3.14.6 + github.com/schollz/progressbar/v3 v3.17.0 github.com/shirou/gopsutil/v4 v4.24.10 github.com/thanos-io/objstore v0.0.0-20241015070247-5f04b8b0b52a github.com/twmb/franz-go v1.17.1 diff --git a/go.sum b/go.sum index 4acfdb9c7e48a..459090b348a3b 100644 --- a/go.sum +++ b/go.sum @@ -442,6 +442,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +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/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= @@ -1303,7 +1305,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= @@ -1408,6 +1409,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +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/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/apcupsd v0.0.0-20200608131503-2bf01da7bf1b/go.mod h1:WYK/Z/aXq9cbMFIL5ihcA4sX/r/3/WCas/Qvs/2fXcA= @@ -1710,8 +1713,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.29 h1:BkTk4gynLjguayxrYxZoMZjBnAOh7ntQvUkOFmkMqPU= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.29/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= -github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs= -github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0= +github.com/schollz/progressbar/v3 v3.17.0 h1:Fv+vG6O6jnJwdjCelvfyYO7sF2jaUGQVmdH4CxcZdsQ= +github.com/schollz/progressbar/v3 v3.17.0/go.mod h1:5H4fLgifX+KeQCsEJnZTOepgZLe1jFF1lpPXb68IJTA= github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= @@ -2324,7 +2327,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -2335,7 +2337,6 @@ golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/vendor/github.com/schollz/progressbar/v3/progressbar.go b/vendor/github.com/schollz/progressbar/v3/progressbar.go index 56c147e813e24..251e5833fe84f 100644 --- a/vendor/github.com/schollz/progressbar/v3/progressbar.go +++ b/vendor/github.com/schollz/progressbar/v3/progressbar.go @@ -45,17 +45,20 @@ type state struct { isAltSaucerHead bool lastShown time.Time - startTime time.Time + startTime time.Time // time when the progress bar start working counterTime time.Time counterNumSinceLast int64 counterLastTenRates []float64 + spinnerIdx int // the index of spinner maxLineWidth int currentBytes float64 finished bool exit bool // Progress bar exit halfway + details []string // details to show,only used when detail row is set to more than 0 + rendered string } @@ -80,6 +83,9 @@ type config struct { showIterationsPerSecond bool showIterationsCount bool + // whether the progress bar should show the total bytes (e.g. 23/24 or 23/-, vs. just 23). + showTotalBytes bool + // whether the progress bar should show elapsed time. // always enabled if predictTime is true. elapsedTime bool @@ -103,6 +109,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 @@ -123,6 +134,9 @@ type config struct { // showDescriptionAtLineEnd specifies whether description should be written at line end instead of line start showDescriptionAtLineEnd bool + // specifies how many rows of details to show,default value is 0 and no details will be shown + maxDetailRow int + stdBuffer bytes.Buffer } @@ -134,7 +148,45 @@ type Theme struct { SaucerPadding string BarStart string BarEnd string -} + + // BarStartFilled is used after the Bar starts filling, if set. Otherwise, it defaults to BarStart. + BarStartFilled string + + // BarEndFilled is used once the Bar finishes, if set. Otherwise, it defaults to BarEnd. + BarEndFilled string +} + +var ( + // ThemeDefault is given by default (if not changed with OptionSetTheme), and it looks like "|████ |". + ThemeDefault = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEnd: "|"} + + // ThemeASCII is a predefined Theme that uses ASCII symbols. It looks like "[===>...]". + // Configure it with OptionSetTheme(ThemeASCII). + ThemeASCII = Theme{ + Saucer: "=", + SaucerHead: ">", + SaucerPadding: ".", + BarStart: "[", + BarEnd: "]", + } + + // ThemeUnicode is a predefined Theme that uses Unicode characters, displaying a graphic bar. + // It looks like "" (rendering will depend on font being used). + // It requires special symbols usually found in "nerd fonts" [2], or in Fira Code [1], and other sources. + // Configure it with OptionSetTheme(ThemeUnicode). + // + // [1] https://github.com/tonsky/FiraCode + // [2] https://www.nerdfonts.com/ + ThemeUnicode = Theme{ + Saucer: "\uEE04", //  + SaucerHead: "\uEE04", //  + SaucerPadding: "\uEE01", //  + BarStart: "\uEE00", //  + BarStartFilled: "\uEE03", //  + BarEnd: "\uEE02", //  + BarEndFilled: "\uEE05", //  + } +) // Option is the type all options need to adhere to type Option func(p *ProgressBar) @@ -146,6 +198,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) { @@ -162,7 +226,8 @@ func OptionSpinnerCustom(spinner []string) Option { } } -// OptionSetTheme sets the elements the bar is constructed of +// OptionSetTheme sets the elements the bar is constructed with. +// There are two pre-defined themes you can use: ThemeASCII and ThemeUnicode. func OptionSetTheme(t Theme) Option { return func(p *ProgressBar) { p.config.theme = t @@ -240,13 +305,20 @@ func OptionShowIts() Option { } } -// OptionShowElapsedOnFinish will keep the display of elapsed time on finish +// OptionShowElapsedTimeOnFinish will keep the display of elapsed time on finish. func OptionShowElapsedTimeOnFinish() Option { return func(p *ProgressBar) { p.config.showElapsedTimeOnFinish = true } } +// OptionShowTotalBytes will keep the display of total bytes. +func OptionShowTotalBytes(flag bool) Option { + return func(p *ProgressBar) { + p.config.showTotalBytes = flag + } +} + // OptionSetItsString sets what's displayed for iterations a second. The default is "it" which would display: "it/s" func OptionSetItsString(iterationString string) Option { return func(p *ProgressBar) { @@ -262,7 +334,7 @@ func OptionThrottle(duration time.Duration) Option { } } -// OptionClearOnFinish will clear the bar once its finished +// OptionClearOnFinish will clear the bar once its finished. func OptionClearOnFinish() Option { return func(p *ProgressBar) { p.config.clearOnFinish = true @@ -308,7 +380,13 @@ func OptionShowDescriptionAtLineEnd() Option { } } -var defaultTheme = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEnd: "|"} +// OptionSetMaxDetailRow sets the max row of details +// the row count should be less than the terminal height, otherwise it will not give you the output you want +func OptionSetMaxDetailRow(row int) Option { + return func(p *ProgressBar) { + p.config.maxDetailRow = row + } +} // NewOptions constructs a new instance of ProgressBar, with any options you specify func NewOptions(max int, options ...Option) *ProgressBar { @@ -318,18 +396,24 @@ func NewOptions(max int, options ...Option) *ProgressBar { // NewOptions64 constructs a new instance of ProgressBar, with any options you specify func NewOptions64(max int64, options ...Option) *ProgressBar { b := ProgressBar{ - state: getBasicState(), + state: state{ + startTime: time.Time{}, + lastShown: time.Time{}, + 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: ThemeDefault, + iterationString: "it", + width: 40, + max: max, + throttleDuration: 0 * time.Nanosecond, + elapsedTime: max == -1, + predictTime: true, + spinnerType: 9, + invisible: false, + spinnerChangeInterval: 100 * time.Millisecond, + showTotalBytes: true, }, } @@ -341,11 +425,13 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { panic("invalid spinner type, must be between 0 and 75") } + if b.config.maxDetailRow < 0 { + panic("invalid max detail row, must be greater than 0") + } + // ignoreLength if max bytes not known if b.config.max == -1 { - b.config.ignoreLength = true - b.config.max = int64(b.config.width) - b.config.predictTime = false + b.lengthUnknown() } b.config.maxHumanized, b.config.maxHumanizedSuffix = humanizeBytes(float64(b.config.max), @@ -355,6 +441,27 @@ func NewOptions64(max int64, options ...Option) *ProgressBar { b.RenderBlank() } + // if the render time interval attribute is set + if b.config.spinnerChangeInterval != 0 && !b.config.invisible && b.config.ignoreLength { + 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() + } + } + } + }() + } + return &b } @@ -386,6 +493,7 @@ func DefaultBytes(maxBytes int64, description ...string) *ProgressBar { OptionSetDescription(desc), OptionSetWriter(os.Stderr), OptionShowBytes(true), + OptionShowTotalBytes(true), OptionSetWidth(10), OptionThrottle(65*time.Millisecond), OptionShowCount(), @@ -412,6 +520,7 @@ func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar { OptionSetDescription(desc), OptionSetWriter(io.Discard), OptionShowBytes(true), + OptionShowTotalBytes(true), OptionSetWidth(10), OptionThrottle(65*time.Millisecond), OptionShowCount(), @@ -432,6 +541,7 @@ func Default(max int64, description ...string) *ProgressBar { OptionSetDescription(desc), OptionSetWriter(os.Stderr), OptionSetWidth(10), + OptionShowTotalBytes(true), OptionThrottle(65*time.Millisecond), OptionShowCount(), OptionShowIts(), @@ -458,6 +568,7 @@ func DefaultSilent(max int64, description ...string) *ProgressBar { OptionSetDescription(desc), OptionSetWriter(io.Discard), OptionSetWidth(10), + OptionShowTotalBytes(true), OptionThrottle(65*time.Millisecond), OptionShowCount(), OptionShowIts(), @@ -486,6 +597,24 @@ func (p *ProgressBar) RenderBlank() error { return p.render() } +// StartWithoutRender will start the progress bar without rendering it +// this method is created for the use case where you want to start the progress +// but don't want to render it immediately. +// If you want to start the progress and render it immediately, use RenderBlank instead, +// or maybe you can use Add to start it automatically, but it will make the time calculation less precise. +func (p *ProgressBar) StartWithoutRender() { + p.lock.Lock() + defer p.lock.Unlock() + + if p.IsStarted() { + return + } + + p.state.startTime = time.Now() + // the counterTime should be set to the current time + p.state.counterTime = time.Now() +} + // Reset will reset the clock that is used // to calculate current time and the time left. func (p *ProgressBar) Reset() { @@ -567,6 +696,10 @@ func (p *ProgressBar) Add64(num int64) error { p.state.currentBytes += float64(num) + if p.state.counterTime.IsZero() { + p.state.counterTime = time.Now() + } + // reset the countdown timer every second to take rolling average p.state.counterNumSinceLast += num if time.Since(p.state.counterTime).Seconds() > 0.5 { @@ -596,6 +729,66 @@ func (p *ProgressBar) Add64(num int64) error { return nil } +// AddDetail adds a detail to the progress bar. Only used when maxDetailRow is set to a value greater than 0 +func (p *ProgressBar) AddDetail(detail string) error { + if p.config.maxDetailRow == 0 { + return errors.New("maxDetailRow is set to 0, cannot add detail") + } + if p.IsFinished() { + return errors.New("cannot add detail to a finished progress bar") + } + + p.lock.Lock() + defer p.lock.Unlock() + if p.state.details == nil { + // if we add a detail before the first add, it will be weird that we have detail but don't have the progress bar in the top. + // so when we add the first detail, we will render the progress bar first. + if err := p.render(); err != nil { + return err + } + } + p.state.details = append(p.state.details, detail) + if len(p.state.details) > p.config.maxDetailRow { + p.state.details = p.state.details[1:] + } + if err := p.renderDetails(); err != nil { + return err + } + return nil +} + +// renderDetails renders the details of the progress bar +func (p *ProgressBar) renderDetails() error { + if p.config.invisible { + return nil + } + if p.state.finished { + return nil + } + if p.config.maxDetailRow == 0 { + return nil + } + + b := strings.Builder{} + b.WriteString("\n") + + // render the details row + for _, detail := range p.state.details { + b.WriteString(fmt.Sprintf("\u001B[K\r%s\n", detail)) + } + // add empty lines to fill the maxDetailRow + for i := len(p.state.details); i < p.config.maxDetailRow; i++ { + b.WriteString("\u001B[K\n") + } + + // move the cursor up to the start of the details row + b.WriteString(fmt.Sprintf("\u001B[%dF", p.config.maxDetailRow+1)) + + writeString(p.config, b.String()) + + return nil +} + // Clear erases the progress bar from the current line func (p *ProgressBar) Clear() error { return clearProgressBar(p.config, p.state) @@ -656,6 +849,7 @@ func (p *ProgressBar) ChangeMax64(newMax int64) { p.config.useIECUnits) } + p.lengthKnown(newMax) p.lock.Unlock() // so p.Add can lock p.Add(0) // re-render @@ -669,22 +863,31 @@ func (p *ProgressBar) IsFinished() bool { return p.state.finished } +// IsStarted returns true if progress bar is started +func (p *ProgressBar) IsStarted() bool { + return !p.state.startTime.IsZero() +} + // render renders the progress bar, updating the maximum // rendered line width. this function is not thread-safe, // so it must be called with an acquired lock. func (p *ProgressBar) render() error { // make sure that the rendering is not happening too quickly // but always show if the currentNum reaches the max - if time.Since(p.state.lastShown).Nanoseconds() < p.config.throttleDuration.Nanoseconds() && + if !p.IsStarted() { + p.state.startTime = time.Now() + } else if time.Since(p.state.lastShown).Nanoseconds() < p.config.throttleDuration.Nanoseconds() && p.state.currentNum < p.config.max { return nil } if !p.config.useANSICodes { - // first, clear the existing progress bar - err := clearProgressBar(p.config, p.state) - if err != nil { - return err + // first, clear the existing progress bar, if not yet finished. + if !p.state.finished { + err := clearProgressBar(p.config, p.state) + if err != nil { + return err + } } } @@ -695,6 +898,11 @@ func (p *ProgressBar) render() error { io.Copy(p.config.writer, &p.config.stdBuffer) renderProgressBar(p.config, &p.state) } + 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]))) + } if p.config.onCompletion != nil { p.config.onCompletion() } @@ -726,6 +934,20 @@ func (p *ProgressBar) render() error { return nil } +// lengthUnknown sets the progress bar to ignore the length +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 func (p *ProgressBar) State() State { p.lock.Lock() @@ -738,7 +960,12 @@ func (p *ProgressBar) State() State { } s.CurrentPercent = float64(p.state.currentNum) / float64(p.config.max) s.CurrentBytes = p.state.currentBytes - s.SecondsSince = time.Since(p.state.startTime).Seconds() + if p.IsStarted() { + s.SecondsSince = time.Since(p.state.startTime).Seconds() + } else { + s.SecondsSince = 0 + } + if p.state.currentNum > 0 { s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * (float64(p.config.max) - float64(p.state.currentNum)) } @@ -798,21 +1025,32 @@ func renderProgressBar(c config, s *state) (int, error) { if c.showBytes { currentHumanize, currentSuffix := humanizeBytes(s.currentBytes, c.useIECUnits) if currentSuffix == c.maxHumanizedSuffix { - sb.WriteString(fmt.Sprintf("%s/%s%s", - currentHumanize, c.maxHumanized, c.maxHumanizedSuffix)) - } else { + if c.showTotalBytes { + sb.WriteString(fmt.Sprintf("%s/%s%s", + currentHumanize, c.maxHumanized, c.maxHumanizedSuffix)) + } else { + sb.WriteString(fmt.Sprintf("%s%s", + currentHumanize, c.maxHumanizedSuffix)) + } + } else if c.showTotalBytes { sb.WriteString(fmt.Sprintf("%s%s/%s%s", currentHumanize, currentSuffix, c.maxHumanized, c.maxHumanizedSuffix)) + } else { + sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix)) } - } else { + } else if c.showTotalBytes { sb.WriteString(fmt.Sprintf("%.0f/%d", s.currentBytes, c.max)) + } else { + sb.WriteString(fmt.Sprintf("%.0f", s.currentBytes)) } } else { if c.showBytes { currentHumanize, currentSuffix := humanizeBytes(s.currentBytes, c.useIECUnits) sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix)) - } else { + } else if c.showTotalBytes { sb.WriteString(fmt.Sprintf("%.0f/%s", s.currentBytes, "-")) + } else { + sb.WriteString(fmt.Sprintf("%.0f", s.currentBytes)) } } } @@ -848,6 +1086,10 @@ func renderProgressBar(c config, s *state) (int, error) { } leftBrac, rightBrac, saucer, saucerHead := "", "", "", "" + barStart, barEnd := c.theme.BarStart, c.theme.BarEnd + if s.finished && c.theme.BarEndFilled != "" { + barEnd = c.theme.BarEndFilled + } // show time prediction in "current/total" seconds format switch { @@ -884,6 +1126,9 @@ func renderProgressBar(c config, s *state) (int, error) { c.width = width - getStringWidth(c, c.description, true) - 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 != "" { + barStart = c.theme.BarStartFilled + } if s.currentSaucerSize > 0 { if c.ignoreLength { saucer = strings.Repeat(c.theme.SaucerPadding, s.currentSaucerSize-1) @@ -925,7 +1170,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 ", @@ -956,11 +1210,11 @@ func renderProgressBar(c config, s *state) (int, error) { } else if rightBrac == "" { str = fmt.Sprintf("%4d%% %s%s%s%s%s %s", s.currentPercent, - c.theme.BarStart, + barStart, saucer, saucerHead, strings.Repeat(c.theme.SaucerPadding, repeatAmount), - c.theme.BarEnd, + barEnd, sb.String()) if (s.currentPercent == 100 && c.showElapsedTimeOnFinish) || c.elapsedTime { str = fmt.Sprintf("%s [%s]", str, leftBrac) @@ -975,11 +1229,11 @@ func renderProgressBar(c config, s *state) (int, error) { if s.currentPercent == 100 { str = fmt.Sprintf("%4d%% %s%s%s%s%s %s", s.currentPercent, - c.theme.BarStart, + barStart, saucer, saucerHead, strings.Repeat(c.theme.SaucerPadding, repeatAmount), - c.theme.BarEnd, + barEnd, sb.String()) if c.showElapsedTimeOnFinish { @@ -994,11 +1248,11 @@ func renderProgressBar(c config, s *state) (int, error) { } else { str = fmt.Sprintf("%4d%% %s%s%s%s%s %s [%s:%s]", s.currentPercent, - c.theme.BarStart, + barStart, saucer, saucerHead, strings.Repeat(c.theme.SaucerPadding, repeatAmount), - c.theme.BarEnd, + barEnd, sb.String(), leftBrac, rightBrac) @@ -1153,6 +1407,8 @@ func shouldCacheOutput(pb *ProgressBar) bool { } func Bprintln(pb *ProgressBar, a ...interface{}) (int, error) { + pb.lock.Lock() + defer pb.lock.Unlock() if !shouldCacheOutput(pb) { return fmt.Fprintln(pb.config.writer, a...) } else { @@ -1161,6 +1417,8 @@ func Bprintln(pb *ProgressBar, a ...interface{}) (int, error) { } func Bprintf(pb *ProgressBar, format string, a ...interface{}) (int, error) { + pb.lock.Lock() + defer pb.lock.Unlock() if !shouldCacheOutput(pb) { return fmt.Fprintf(pb.config.writer, format, a...) } else { diff --git a/vendor/modules.txt b/vendor/modules.txt index 4ab7c446ea315..9b50cc3544026 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1539,8 +1539,8 @@ github.com/rivo/uniseg # github.com/rs/xid v1.6.0 ## explicit; go 1.16 github.com/rs/xid -# github.com/schollz/progressbar/v3 v3.14.6 -## explicit; go 1.13 +# github.com/schollz/progressbar/v3 v3.17.0 +## explicit; go 1.22 github.com/schollz/progressbar/v3 # github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 ## explicit