diff --git a/cmd/demo-progress/demo.go b/cmd/demo-progress/demo.go index bee72ce..b95abf3 100644 --- a/cmd/demo-progress/demo.go +++ b/cmd/demo-progress/demo.go @@ -18,6 +18,8 @@ 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") 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") @@ -113,12 +115,15 @@ 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.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 6024bf0..5f09e75 100644 Binary files a/progress/images/demo.gif and b/progress/images/demo.gif differ diff --git a/progress/render.go b/progress/render.go index 6b22ea2..63ae333 100644 --- a/progress/render.go +++ b/progress/render.go @@ -322,6 +322,9 @@ func (p *Progress) renderTrackerStats(out *strings.Builder, t *Tracker, hint ren if !hint.hideValue || !hint.hideTime { var outStats strings.Builder outStats.WriteString(" [") + + p.renderTrackerSpeed(&outStats, t, hint) + if !hint.hideValue { outStats.WriteString(p.style.Colors.Value.Sprint(t.Units.Sprint(t.Value()))) } @@ -372,3 +375,42 @@ 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 64a5836..ced9bed 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+ 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]`), + 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]`), } 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+ 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+\.\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]`), } 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+ 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+\.\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]`), } 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+ 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]`), + 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]`), } 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+ 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]`), + 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]`), } 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+ 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]`), + 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]`), } 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+ 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]`), + 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]`), } 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+ 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(`\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(`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+ 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(`\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(`some information about something that happened at \d\d\d\d`), } out := renderOutput.String() @@ -689,9 +689,79 @@ func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithETA(t *testing.T) renderAndWait(pw, false) expectedOutPatterns := []*regexp.Regexp{ - 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 \.\.\. \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]`), + } + out := renderOutput.String() + for _, expectedOutPattern := range expectedOutPatterns { + if !expectedOutPattern.MatchString(out) { + assert.Fail(t, "Failed to find a pattern in the Output.", expectedOutPattern.String()) + } + } + showOutputOnFailure(t, out) +} + +func TestProgress_RenderSomeTrackers_WithOverallTracker_WithoutSpeedOverall(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.TrackerOverall = true + 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)) + }() + go trackSomething(pw, &Tracker{Message: "Downloading File\t# 2", Total: 1000, Units: UnitsBytes}) + go trackSomething(pw, &Tracker{Message: "Transferring Amount # 3", Total: 1000, Units: UnitsCurrencyDollar}) + 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.ms]+; ~ETA: [\d.ms]+]`), + regexp.MustCompile(`some information about something that happened at \d\d\d\d`), + } + out := renderOutput.String() + for _, expectedOutPattern := range expectedOutPatterns { + if !expectedOutPattern.MatchString(out) { + assert.Fail(t, "Failed to find a pattern in the Output.", expectedOutPattern.String()) + } + } + showOutputOnFailure(t, out) +} + +func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithoutSpeed(t *testing.T) { + renderOutput := outputWriter{} + + pw := generateWriter() + pw.SetOutputWriter(&renderOutput) + pw.SetTrackerPosition(PositionRight) + pw.Style().Options.TimeOverallPrecision = time.Millisecond + pw.Style().Visibility.Speed = false + pw.Style().Visibility.TrackerOverall = false + 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)) + }() + go trackSomething(pw, &Tracker{Message: "Downloading File\t# 2", Total: 1000, Units: UnitsBytes}) + go trackSomething(pw, &Tracker{Message: "Transferring Amount # 3", Total: 1000, Units: UnitsCurrencyDollar}) + 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]`), @@ -704,3 +774,39 @@ func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithETA(t *testing.T) } showOutputOnFailure(t, out) } + +func TestProgress_RenderSomeTrackers_WithOverallTracker_WithSpeedOverall_WithoutFormatter(t *testing.T) { + renderOutput := outputWriter{} + + pw := generateWriter() + pw.SetOutputWriter(&renderOutput) + pw.SetTrackerPosition(PositionRight) + pw.Style().Options.TimeOverallPrecision = time.Millisecond + 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)) + }() + go trackSomething(pw, &Tracker{Message: "Downloading File\t# 2", Total: 1000, Units: UnitsBytes}) + go trackSomething(pw, &Tracker{Message: "Transferring Amount # 3", Total: 1000, Units: UnitsCurrencyDollar}) + 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(`some information about something that happened at \d\d\d\d`), + } + out := renderOutput.String() + for _, expectedOutPattern := range expectedOutPatterns { + if !expectedOutPattern.MatchString(out) { + assert.Fail(t, "Failed to find a pattern in the Output.", expectedOutPattern.String()) + } + } + showOutputOnFailure(t, out) +} diff --git a/progress/style.go b/progress/style.go index 560eddc..724a76c 100644 --- a/progress/style.go +++ b/progress/style.go @@ -125,6 +125,7 @@ type StyleColors struct { Time text.Colors // time text colors (overrides Stats) Tracker text.Colors // tracker text colors Value text.Colors // value text colors (overrides Stats) + Speed text.Colors // speed text colors } var ( @@ -141,23 +142,26 @@ var ( Time: text.Colors{text.FgGreen}, Tracker: text.Colors{text.FgYellow}, Value: text.Colors{text.FgCyan}, + Speed: text.Colors{text.FgMagenta}, } ) // 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 + 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 } var ( @@ -175,6 +179,8 @@ var ( TimeDonePrecision: time.Millisecond, TimeInProgressPrecision: time.Microsecond, TimeOverallPrecision: time.Second, + SpeedPrecision: time.Microsecond, + SpeedOverallFormatter: FormatNumber, } ) @@ -187,6 +193,8 @@ type StyleVisibility struct { Tracker bool // tracker ([===========-----------]) TrackerOverall bool // overall tracker Value bool // tracker value + Speed bool // tracker speed + SpeedOverall bool // speed for the overall tracker } var ( @@ -199,5 +207,7 @@ var ( Tracker: true, TrackerOverall: false, Value: true, + Speed: true, + SpeedOverall: true, } )