diff --git a/cmd/demo-progress/demo.go b/cmd/demo-progress/demo.go index b95abf3..1bc458c 100644 --- a/cmd/demo-progress/demo.go +++ b/cmd/demo-progress/demo.go @@ -18,9 +18,9 @@ var ( flagHidePercentage = flag.Bool("hide-percentage", false, "Hide the progress percent?") flagHideTime = flag.Bool("hide-time", false, "Hide the time taken?") flagHideValue = flag.Bool("hide-value", false, "Hide the tracker value?") - flagHideSpeed = flag.Bool("hide-speed", false, "Hide the tracker speed?") - flagHideSpeedOverall = flag.Bool("hide-speed-overall", false, "Hide the overall tracker speed?") flagNumTrackers = flag.Int("num-trackers", 13, "Number of Trackers") + flagShowSpeed = flag.Bool("show-speed", false, "Show the tracker speed?") + flagShowSpeedOverall = flag.Bool("show-speed-overall", false, "Show the overall tracker speed?") flagRandomFail = flag.Bool("rnd-fail", false, "Introduce random failures in tracking") flagRandomLogs = flag.Bool("rnd-logs", false, "Output random logs in the middle of tracking") @@ -115,15 +115,14 @@ func main() { pw.SetUpdateFrequency(time.Millisecond * 100) pw.Style().Colors = progress.StyleColorsExample pw.Style().Options.PercentFormat = "%4.1f%%" - pw.Style().Options.SpeedOverallFormatter = progress.FormatNumber pw.Style().Visibility.ETA = !*flagHideETA pw.Style().Visibility.ETAOverall = !*flagHideETAOverall pw.Style().Visibility.Percentage = !*flagHidePercentage + pw.Style().Visibility.Speed = *flagShowSpeed + pw.Style().Visibility.SpeedOverall = *flagShowSpeedOverall pw.Style().Visibility.Time = !*flagHideTime pw.Style().Visibility.TrackerOverall = !*flagHideOverallTracker pw.Style().Visibility.Value = !*flagHideValue - pw.Style().Visibility.Speed = !*flagHideSpeed - pw.Style().Visibility.SpeedOverall = !*flagHideSpeedOverall // call Render() in async mode; yes we don't have any trackers at the moment go pw.Render() diff --git a/progress/images/demo.gif b/progress/images/demo.gif index 5f09e75..6024bf0 100644 Binary files a/progress/images/demo.gif and b/progress/images/demo.gif differ diff --git a/progress/progress.go b/progress/progress.go index 0e5c0a4..838f729 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -267,6 +267,9 @@ func (p *Progress) Style() *Style { func (p *Progress) initForRender() { // pick a default style p.Style() + if p.style.Options.SpeedOverallFormatter == nil { + p.style.Options.SpeedOverallFormatter = FormatNumber + } // reset the signals p.done = make(chan bool, 1) diff --git a/progress/render.go b/progress/render.go index 63ae333..24385fc 100644 --- a/progress/render.go +++ b/progress/render.go @@ -323,8 +323,9 @@ func (p *Progress) renderTrackerStats(out *strings.Builder, t *Tracker, hint ren var outStats strings.Builder outStats.WriteString(" [") - p.renderTrackerSpeed(&outStats, t, hint) - + if p.style.Options.SpeedPosition == PositionLeft { + p.renderTrackerStatsSpeed(&outStats, t, hint) + } if !hint.hideValue { outStats.WriteString(p.style.Colors.Value.Sprint(t.Units.Sprint(t.Value()))) } @@ -334,12 +335,55 @@ func (p *Progress) renderTrackerStats(out *strings.Builder, t *Tracker, hint ren if !hint.hideTime { p.renderTrackerStatsTime(&outStats, t, hint) } + if p.style.Options.SpeedPosition == PositionRight { + p.renderTrackerStatsSpeed(&outStats, t, hint) + } outStats.WriteRune(']') out.WriteString(p.style.Colors.Stats.Sprint(outStats.String())) } } +func (p *Progress) renderTrackerStatsSpeed(out *strings.Builder, t *Tracker, hint renderHint) { + if hint.isOverallTracker && !p.style.Visibility.SpeedOverall { + return + } + if !hint.isOverallTracker && !p.style.Visibility.Speed { + return + } + + speedPrecision := p.style.Options.SpeedPrecision + writeSpeed := func(speed string) { + if p.style.Options.SpeedPosition == PositionRight { + out.WriteString("; ") + } + out.WriteString(p.style.Colors.Speed.Sprint(speed)) + out.WriteString(p.style.Options.SpeedSuffix) + if p.style.Options.SpeedPosition == PositionLeft { + out.WriteString("; ") + } + } + + if hint.isOverallTracker { + speed := float64(0) + + p.trackersActiveMutex.RLock() + for _, tracker := range p.trackersActive { + speed += float64(tracker.Value()) / time.Since(tracker.timeStart).Round(speedPrecision).Seconds() + } + p.trackersActiveMutex.RUnlock() + + if speed > 0 { + writeSpeed(p.style.Options.SpeedOverallFormatter(int64(speed))) + } + } else { + timeTaken := time.Since(t.timeStart) + if timeTakenRounded := timeTaken.Round(speedPrecision); timeTakenRounded > speedPrecision { + writeSpeed(t.Units.Sprint(int64(float64(t.Value()) / timeTakenRounded.Seconds()))) + } + } +} + func (p *Progress) renderTrackerStatsTime(outStats *strings.Builder, t *Tracker, hint renderHint) { var td, tp time.Duration if t.IsDone() { @@ -375,42 +419,3 @@ func (p *Progress) renderTrackerStatsETA(out *strings.Builder, t *Tracker, hint out.WriteString(p.style.Colors.Time.Sprint(eta)) } } - -func (p *Progress) renderTrackerSpeed(out *strings.Builder, t *Tracker, hint renderHint) { - if hint.isOverallTracker && !p.style.Visibility.SpeedOverall { - return - } - if !hint.isOverallTracker && !p.style.Visibility.Speed { - return - } - - tpSpeed := p.style.Options.SpeedPrecision - - write := func(speed float64, formatter func(int64) string) { - if formatter == nil { - formatter = FormatNumber - } - out.WriteString(p.style.Colors.Speed.Sprint(formatter(int64(speed)))) - out.WriteString("/s; ") - } - - if hint.isOverallTracker { - speed := float64(0) - - p.trackersActiveMutex.RLock() - for _, tracker := range p.trackersActive { - speed += float64(tracker.Value()) / time.Since(tracker.timeStart).Round(tpSpeed).Seconds() - } - p.trackersActiveMutex.RUnlock() - - if speed > 0 { - write(speed, p.style.Options.SpeedOverallFormatter) - } - return - } - - since := time.Since(t.timeStart) - if eta := since.Round(tpSpeed); eta > tpSpeed { - write(float64(t.Value())/eta.Seconds(), t.Units.Formatter) - } -} diff --git a/progress/render_test.go b/progress/render_test.go index ced9bed..d1ecd7d 100644 --- a/progress/render_test.go +++ b/progress/render_test.go @@ -417,12 +417,12 @@ func TestProgress_RenderSomeTrackers_OnLeftSide(t *testing.T) { renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[K\d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms] \.\.\. Calculating Total # 1`), - regexp.MustCompile(`\x1b\[K\d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms] \.\.\. Downloading File # 2`), - regexp.MustCompile(`\x1b\[K\d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms] \.\.\. Transferring Amount # 3`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[K\d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms] \.\.\. Calculating Total # 1`), + regexp.MustCompile(`\x1b\[K\d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms] \.\.\. Downloading File # 2`), + regexp.MustCompile(`\x1b\[K\d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms] \.\.\. Transferring Amount # 3`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -445,12 +445,12 @@ func TestProgress_RenderSomeTrackers_OnRightSide(t *testing.T) { renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -474,12 +474,12 @@ func TestProgress_RenderSomeTrackers_WithAutoStop(t *testing.T) { renderAndWait(pw, true) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -501,12 +501,12 @@ func TestProgress_RenderSomeTrackers_WithError(t *testing.T) { renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \?\?\? \[[<#>.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. fail! \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \?\?\? \[[<#>.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. fail! \[\$\d+ in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -528,12 +528,12 @@ func TestProgress_RenderSomeTrackers_WithIndeterminateTracker(t *testing.T) { renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \?\?\? \[[<#>.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \?\?\? \[[<#>.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -557,12 +557,12 @@ func TestProgress_RenderSomeTrackers_WithLineWidth1(t *testing.T) { renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalc~ \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDown~ \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTran~ \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalc~ \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDown~ \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTran~ \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalc~ \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDown~ \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTran~ \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalc~ \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDown~ \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTran~ \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -586,12 +586,12 @@ func TestProgress_RenderSomeTrackers_WithLineWidth2(t *testing.T) { renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1\s{28}\.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2\s{28}\.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3\s{28}\.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1\s{28}\.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2\s{28}\.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3\s{28}\.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1\s{28}\.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2\s{28}\.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3\s{28}\.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1\s{28}\.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2\s{28}\.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3\s{28}\.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -619,13 +619,13 @@ func TestProgress_RenderSomeTrackers_WithOverallTracker(t *testing.T) { renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[K\[[.#]+] \[\d+\.\d+\w+/s; [\d.ms]+; ~ETA: [\d.ms]+`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[K\[[.#]+] \[[\d.ms]+; ~ETA: [\d.ms]+`), regexp.MustCompile(`some information about something that happened at \d\d\d\d`), } out := renderOutput.String() @@ -656,13 +656,13 @@ func TestProgress_RenderSomeTrackers_WithOverallTracker_WithoutETAOverall(t *tes renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[K\[[.#]+] \[\d+\.\d+\w+/s; [\d.ms]+]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[K\[[.#]+] \[[\d.ms]+]`), regexp.MustCompile(`some information about something that happened at \d\d\d\d`), } out := renderOutput.String() @@ -689,12 +689,12 @@ func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithETA(t *testing.T) renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms; ~ETA: [\d]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms; ~ETA: [\d]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms; ~ETA: [\d]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms; ~ETA: [\d]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms; ~ETA: [\d]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms; ~ETA: [\d]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -705,14 +705,15 @@ func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithETA(t *testing.T) showOutputOnFailure(t, out) } -func TestProgress_RenderSomeTrackers_WithOverallTracker_WithoutSpeedOverall(t *testing.T) { +func TestProgress_RenderSomeTrackers_WithOverallTracker_WithSpeedAndSpeedOverall(t *testing.T) { renderOutput := outputWriter{} pw := generateWriter() pw.SetOutputWriter(&renderOutput) pw.SetTrackerPosition(PositionRight) pw.Style().Options.TimeOverallPrecision = time.Millisecond - pw.Style().Visibility.SpeedOverall = false + pw.Style().Visibility.Speed = true + pw.Style().Visibility.SpeedOverall = true pw.Style().Visibility.TrackerOverall = true go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault}) go func() { @@ -723,12 +724,12 @@ func TestProgress_RenderSomeTrackers_WithOverallTracker_WithoutSpeedOverall(t *t renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms; \d+\.\d+\w+/s]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms; \d+\.\d+KB/s]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms; \$\d+\.\d+\w+/s]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms; \d+\.\d+K/s]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms; \d+\.\d+KB/s]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms; \$\d+\.\d+K/s]`), regexp.MustCompile(`\x1b\[K\[[.#]+] \[[\d.ms]+; ~ETA: [\d.ms]+]`), regexp.MustCompile(`some information about something that happened at \d\d\d\d`), } @@ -741,14 +742,15 @@ func TestProgress_RenderSomeTrackers_WithOverallTracker_WithoutSpeedOverall(t *t showOutputOnFailure(t, out) } -func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithoutSpeed(t *testing.T) { +func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithSpeedOnLeft(t *testing.T) { renderOutput := outputWriter{} pw := generateWriter() pw.SetOutputWriter(&renderOutput) pw.SetTrackerPosition(PositionRight) + pw.Style().Options.SpeedPosition = PositionLeft pw.Style().Options.TimeOverallPrecision = time.Millisecond - pw.Style().Visibility.Speed = false + pw.Style().Visibility.Speed = true pw.Style().Visibility.TrackerOverall = false go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault}) go func() { @@ -759,12 +761,12 @@ func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithoutSpeed(t *testi renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[[\d.]+(B|KB)/s; \d+(B|KB) in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB/s; \d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), } out := renderOutput.String() for _, expectedOutPattern := range expectedOutPatterns { @@ -781,9 +783,12 @@ func TestProgress_RenderSomeTrackers_WithOverallTracker_WithSpeedOverall_Without pw := generateWriter() pw.SetOutputWriter(&renderOutput) pw.SetTrackerPosition(PositionRight) + pw.Style().Options.SpeedOverallFormatter = nil + pw.Style().Options.SpeedPosition = PositionLeft pw.Style().Options.TimeOverallPrecision = time.Millisecond + pw.Style().Visibility.Speed = false + pw.Style().Visibility.SpeedOverall = true pw.Style().Visibility.TrackerOverall = true - pw.Style().Options.SpeedOverallFormatter = nil go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault}) go func() { pw.Log("some information about something that happened at %s", time.Now().Format(time.RFC3339)) @@ -793,13 +798,13 @@ func TestProgress_RenderSomeTrackers_WithOverallTracker_WithSpeedOverall_Without renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \d+B in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+\.\d+\w+/s; \$\d+ in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+\w+/s; \d+\.\d+KB in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\d+\.\d+\w+/s; \$\d+\.\d+K in [\d.]+ms]`), - regexp.MustCompile(`\x1b\[K\[[.#]+] \[\d+\.\d+\w+/s; [\d.ms]+; ~ETA: [\d.ms]+]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[K\[[.#]+] \[\d+.\d+\w+/s; [\d.ms]+; ~ETA: [\d.ms]+]`), regexp.MustCompile(`some information about something that happened at \d\d\d\d`), } out := renderOutput.String() diff --git a/progress/style.go b/progress/style.go index 724a76c..1eadba3 100644 --- a/progress/style.go +++ b/progress/style.go @@ -149,19 +149,21 @@ var ( // StyleOptions defines misc. options to control how the Tracker or its parts // gets rendered. type StyleOptions struct { - DoneString string // "done!" string - ErrorString string // "error!" string - ETAPrecision time.Duration // precision for ETA - ETAString string // string for ETA - Separator string // text between message and tracker - SnipIndicator string // text denoting message snipping - PercentFormat string // formatting to use for percentage - PercentIndeterminate string // when percentage cannot be computed - TimeDonePrecision time.Duration // precision for time when done - TimeInProgressPrecision time.Duration // precision for time when in progress - TimeOverallPrecision time.Duration // precision for overall time - SpeedPrecision time.Duration // precision for speed - SpeedOverallFormatter func(value int64) string // formatter for the overall tracker speed + DoneString string // "done!" string + ErrorString string // "error!" string + ETAPrecision time.Duration // precision for ETA + ETAString string // string for ETA + Separator string // text between message and tracker + SnipIndicator string // text denoting message snipping + PercentFormat string // formatting to use for percentage + PercentIndeterminate string // when percentage cannot be computed + SpeedPosition Position // where speed is displayed in stats + SpeedPrecision time.Duration // precision for speed + SpeedOverallFormatter UnitsFormatter // formatter for the overall tracker speed + SpeedSuffix string // suffix (/s) + TimeDonePrecision time.Duration // precision for time when done + TimeInProgressPrecision time.Duration // precision for time when in progress + TimeOverallPrecision time.Duration // precision for overall time } var ( @@ -176,11 +178,13 @@ var ( PercentIndeterminate: " ??? ", Separator: " ... ", SnipIndicator: "~", + SpeedPosition: PositionRight, + SpeedPrecision: time.Microsecond, + SpeedOverallFormatter: FormatNumber, + SpeedSuffix: "/s", TimeDonePrecision: time.Millisecond, TimeInProgressPrecision: time.Microsecond, TimeOverallPrecision: time.Second, - SpeedPrecision: time.Microsecond, - SpeedOverallFormatter: FormatNumber, } ) @@ -189,12 +193,12 @@ type StyleVisibility struct { ETA bool // ETA for each tracker ETAOverall bool // ETA for the overall tracker Percentage bool // tracker progress percentage value + Speed bool // tracker speed + SpeedOverall bool // overall tracker speed Time bool // tracker time taken Tracker bool // tracker ([===========-----------]) TrackerOverall bool // overall tracker Value bool // tracker value - Speed bool // tracker speed - SpeedOverall bool // speed for the overall tracker } var ( @@ -203,11 +207,11 @@ var ( ETA: false, ETAOverall: true, Percentage: true, + Speed: false, + SpeedOverall: false, Time: true, Tracker: true, TrackerOverall: false, Value: true, - Speed: true, - SpeedOverall: true, } ) diff --git a/progress/units.go b/progress/units.go index 97c4965..a21c3db 100644 --- a/progress/units.go +++ b/progress/units.go @@ -14,9 +14,12 @@ const ( UnitsNotationPositionAfter ) +// UnitsFormatter defines a function that prints a value in a specific style. +type UnitsFormatter func(value int64) string + // Units defines the "type" of the value being tracked by the Tracker. type Units struct { - Formatter func(value int64) string // default: FormatNumber + Formatter UnitsFormatter // default: FormatNumber Notation string NotationPosition UnitsNotationPosition // default: UnitsNotationPositionBefore }