From 0772882e8ed4a06995ff3d7ed74dbe109bfe5fd2 Mon Sep 17 00:00:00 2001 From: Naveen Mahalingam Date: Sun, 2 Oct 2022 15:43:55 -0700 Subject: [PATCH] progress: "pin"->"pinned"; sort interfaces/structs --- cmd/demo-progress/demo.go | 13 ++++++++---- progress/progress.go | 43 ++++++++++++++++++++------------------- progress/progress_test.go | 32 ++++++++++++++--------------- progress/render.go | 31 ++++++++++++++-------------- progress/render_test.go | 8 ++++---- progress/style.go | 8 ++++---- progress/writer.go | 4 ++-- text/direction_test.go | 13 ++++++++++++ 8 files changed, 86 insertions(+), 66 deletions(-) create mode 100644 text/direction_test.go diff --git a/cmd/demo-progress/demo.go b/cmd/demo-progress/demo.go index d7bae4e..d214b4b 100644 --- a/cmd/demo-progress/demo.go +++ b/cmd/demo-progress/demo.go @@ -21,7 +21,7 @@ var ( 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?") - flagShowPin = flag.Bool("show-pin", false, "Show the pin message?") + flagShowPinned = flag.Bool("show-pinned", false, "Show a pinned message?") 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") @@ -34,6 +34,7 @@ var ( text.FgCyan, text.FgWhite, } + timeStart = time.Now() ) func getMessage(idx int64, units *progress.Units) string { @@ -88,8 +89,12 @@ func trackSomething(pw progress.Writer, idx int64, updateMessage bool) { } else if *flagRandomFail && rand.Float64() < 0.1 { tracker.MarkAsErrored() } - t := time.Now().Format(time.RFC3339) - pw.SetPinMessage(fmt.Sprintf("Current Time: %s", t), fmt.Sprintf("CURRENT TIME: %s", t)) + pw.SetPinnedMessages( + fmt.Sprintf("Current Time: %s | Total Time: %s", + time.Now().Format(time.RFC3339), + time.Now().Sub(timeStart).Round(time.Second), + ), + ) case <-updateTicker: if updateMessage { rndIdx := rand.Intn(len(messageColors)) @@ -126,7 +131,7 @@ func main() { pw.Style().Visibility.Time = !*flagHideTime pw.Style().Visibility.TrackerOverall = !*flagHideOverallTracker pw.Style().Visibility.Value = !*flagHideValue - pw.Style().Visibility.Pin = *flagShowPin + pw.Style().Visibility.Pinned = *flagShowPinned // call Render() in async mode; yes we don't have any trackers at the moment go pw.Render() diff --git a/progress/progress.go b/progress/progress.go index 2cce24e..fdd683a 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "os" - "strings" "sync" "time" "unicode/utf8" @@ -25,18 +24,18 @@ var ( type Progress struct { autoStop bool done chan bool - lengthTracker int lengthProgress int lengthProgressOverall int - outputWriter io.Writer - pinMessage []string // split by '\n' - pinMessageMutex sync.RWMutex + lengthTracker int logsToRender []string logsToRenderMutex sync.RWMutex messageWidth int numTrackersExpected int64 + outputWriter io.Writer overallTracker *Tracker overallTrackerMutex sync.RWMutex + pinnedMessages []string + pinnedMessagesMutex sync.RWMutex renderInProgress bool renderInProgressMutex sync.RWMutex sortBy SortBy @@ -147,14 +146,6 @@ func (p *Progress) LengthInQueue() int { return out } -// PinMessage returns the current pinned message. -func (p *Progress) PinMessage() string { - p.pinMessageMutex.RLock() - defer p.pinMessageMutex.RUnlock() - - return strings.Join(p.pinMessage, "\n") -} - // Log appends a log to display above the active progress bars during the next // refresh. func (p *Progress) Log(msg string, a ...interface{}) { @@ -166,6 +157,14 @@ func (p *Progress) Log(msg string, a ...interface{}) { p.logsToRenderMutex.Unlock() } +// PinnedMessages returns the current pinned messages. +func (p *Progress) PinnedMessages() []string { + p.pinnedMessagesMutex.RLock() + defer p.pinnedMessagesMutex.RUnlock() + + return p.pinnedMessages +} + // SetAutoStop toggles the auto-stop functionality. Auto-stop set to true would // mean that the Render() function will automatically stop once all currently // active Trackers reach their final states. When set to false, the client code @@ -194,6 +193,16 @@ func (p *Progress) SetOutputWriter(writer io.Writer) { p.outputWriter = writer } +// SetPinnedMessages sets message(s) pinned above all the trackers of the +// progress bar. This method can be used to overwrite all the pinned messages. +// Call this function without arguments to "clear" the pinned messages. +func (p *Progress) SetPinnedMessages(messages ...string) { + p.pinnedMessagesMutex.Lock() + defer p.pinnedMessagesMutex.Unlock() + + p.pinnedMessages = messages +} + // SetSortBy defines the sorting mechanism to use to sort the Active Trackers // before rendering the. Default: no-sorting == sort-by-insertion-order. func (p *Progress) SetSortBy(sortBy SortBy) { @@ -223,14 +232,6 @@ func (p *Progress) SetUpdateFrequency(frequency time.Duration) { p.updateFrequency = frequency } -// SetPinMessage sets the pin message of the progress bar. It can be modified in progress. messages will be displayed per line -func (p *Progress) SetPinMessage(messages ...string) { - p.pinMessageMutex.Lock() - defer p.pinMessageMutex.Unlock() - - p.pinMessage = messages -} - // ShowETA toggles showing the ETA for all individual trackers. // Deprecated: in favor of Style().Visibility.ETA func (p *Progress) ShowETA(show bool) { diff --git a/progress/progress_test.go b/progress/progress_test.go index d5ab522..cb7ed56 100644 --- a/progress/progress_test.go +++ b/progress/progress_test.go @@ -95,6 +95,14 @@ func TestProgress_Log(t *testing.T) { assert.Len(t, p.logsToRender, 1) } +func TestProgress_PinnedMessages(t *testing.T) { + p := Progress{} + assert.Nil(t, p.pinnedMessages) + + p.pinnedMessages = []string{"pin1", "pin2"} + assert.Equal(t, []string{"pin1", "pin2"}, p.PinnedMessages()) +} + func TestProgress_SetAutoStop(t *testing.T) { p := Progress{} assert.False(t, p.autoStop) @@ -119,6 +127,14 @@ func TestProgress_SetOutputWriter(t *testing.T) { assert.Equal(t, os.Stdout, p.outputWriter) } +func TestProgress_SetPinnedMessages(t *testing.T) { + p := Progress{} + assert.Nil(t, p.pinnedMessages) + + p.SetPinnedMessages("pin1", "pin2") + assert.Equal(t, []string{"pin1", "pin2"}, p.pinnedMessages) +} + func TestProgress_SetSortBy(t *testing.T) { p := Progress{} assert.Zero(t, p.sortBy) @@ -230,19 +246,3 @@ func TestProgress_Style(t *testing.T) { assert.NotNil(t, p.Style()) assert.Equal(t, StyleDefault.Name, p.Style().Name) } - -func TestProgress_SetPinMessage(t *testing.T) { - p := Progress{} - assert.Nil(t, p.pinMessage) - - p.SetPinMessage("pin1", "pin2") - assert.Equal(t, []string{"pin1", "pin2"}, p.pinMessage) -} - -func TestProgress_PinMessage(t *testing.T) { - p := Progress{} - assert.Nil(t, p.pinMessage) - - p.pinMessage = []string{"pin1", "pin2"} - assert.Equal(t, "pin1\npin2", p.PinMessage()) -} diff --git a/progress/render.go b/progress/render.go index c8e777b..18f8c6c 100644 --- a/progress/render.go +++ b/progress/render.go @@ -158,14 +158,25 @@ func (p *Progress) moveCursorToTheTop(out *strings.Builder) { if p.style.Visibility.TrackerOverall && p.overallTracker != nil && !p.overallTracker.IsDone() { numLinesToMoveUp++ } - if p.style.Visibility.Pin { - numLinesToMoveUp += len(p.pinMessage) + if p.style.Visibility.Pinned { + numLinesToMoveUp += len(p.pinnedMessages) } if numLinesToMoveUp > 0 { out.WriteString(text.CursorUp.Sprintn(numLinesToMoveUp)) } } +func (p *Progress) renderPinnedMessages(out *strings.Builder) { + p.pinnedMessagesMutex.RLock() + defer p.pinnedMessagesMutex.RUnlock() + + for _, msg := range p.pinnedMessages { + out.WriteString(text.EraseLine.Sprint()) + out.WriteString(p.Style().Colors.Pinned.Sprint(msg)) + out.WriteRune('\n') + } +} + func (p *Progress) renderTracker(out *strings.Builder, t *Tracker, hint renderHint) { message := t.message() if strings.Contains(message, "\t") { @@ -290,16 +301,6 @@ func (p *Progress) renderTrackers(lastRenderLength int) int { return out.Len() } -func (p *Progress) renderPin(out *strings.Builder) { - p.pinMessageMutex.RLock() - for _, pin := range p.pinMessage { - out.WriteString(text.EraseLine.Sprint()) - out.WriteString(p.Style().Colors.Pin.Sprint(pin)) - out.WriteRune('\n') - } - p.pinMessageMutex.RUnlock() -} - func (p *Progress) renderTrackersDoneAndActive(out *strings.Builder) { // find the currently "active" and "done" trackers trackersActive, trackersDone := p.extractDoneAndActiveTrackers() @@ -322,9 +323,9 @@ func (p *Progress) renderTrackersDoneAndActive(out *strings.Builder) { p.logsToRender = nil p.logsToRenderMutex.Unlock() - // render pin message - if p.style.Visibility.Pin { - p.renderPin(out) + // render pinned messages + if len(trackersActive) > 0 && p.style.Visibility.Pinned { + p.renderPinnedMessages(out) } // sort and render the active trackers diff --git a/progress/render_test.go b/progress/render_test.go index b46e065..b5cd5db 100644 --- a/progress/render_test.go +++ b/progress/render_test.go @@ -823,8 +823,8 @@ func TestProgress_RenderSomeTrackers_WithPinMessage_OneLine(t *testing.T) { pw.SetMessageWidth(5) pw.SetOutputWriter(&renderOutput) pw.SetTrackerPosition(PositionRight) - pw.Style().Visibility.Pin = true - pw.SetPinMessage("PIN") + pw.Style().Visibility.Pinned = true + pw.SetPinnedMessages("PIN") go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault}) 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}) @@ -855,8 +855,8 @@ func TestProgress_RenderSomeTrackers_WithPinMessage_MultiLines(t *testing.T) { pw.SetMessageWidth(5) pw.SetOutputWriter(&renderOutput) pw.SetTrackerPosition(PositionRight) - pw.Style().Visibility.Pin = true - pw.SetPinMessage("PIN", "PIN2") + pw.Style().Visibility.Pinned = true + pw.SetPinnedMessages("PIN", "PIN2") go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault}) 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}) diff --git a/progress/style.go b/progress/style.go index 7a85930..536f308 100644 --- a/progress/style.go +++ b/progress/style.go @@ -118,10 +118,10 @@ var ( // StyleColors defines what colors to use for various parts of the Progress and // Tracker texts. type StyleColors struct { - Pin text.Colors // color of the pin message Message text.Colors // message text colors Error text.Colors // error text colors Percent text.Colors // percentage text colors + Pinned text.Colors // color of the pin message Stats text.Colors // stats text (time, value) colors Time text.Colors // time text colors (overrides Stats) Tracker text.Colors // tracker text colors @@ -136,10 +136,10 @@ var ( // StyleColorsExample defines a few choice color options. Use this is just // as an example to customize the Tracker/text colors. StyleColorsExample = StyleColors{ - Pin: text.Colors{text.FgBlue}, Message: text.Colors{text.FgWhite}, Error: text.Colors{text.FgRed}, Percent: text.Colors{text.FgHiRed}, + Pinned: text.Colors{text.BgHiBlack, text.Bold, text.Underline}, Stats: text.Colors{text.FgHiBlack}, Time: text.Colors{text.FgGreen}, Tracker: text.Colors{text.FgYellow}, @@ -192,10 +192,10 @@ var ( // StyleVisibility controls what gets shown and what gets hidden. type StyleVisibility struct { - Pin bool // pin message ETA bool // ETA for each tracker ETAOverall bool // ETA for the overall tracker Percentage bool // tracker progress percentage value + Pinned bool // pin message Speed bool // tracker speed SpeedOverall bool // overall tracker speed Time bool // tracker time taken @@ -207,10 +207,10 @@ type StyleVisibility struct { var ( // StyleVisibilityDefault defines sane defaults for the Visibility. StyleVisibilityDefault = StyleVisibility{ - Pin: false, ETA: false, ETAOverall: true, Percentage: true, + Pinned: true, Speed: false, SpeedOverall: false, Time: true, diff --git a/progress/writer.go b/progress/writer.go index 0645548..153273a 100644 --- a/progress/writer.go +++ b/progress/writer.go @@ -15,12 +15,13 @@ type Writer interface { LengthActive() int LengthDone() int LengthInQueue() int - PinMessage() string Log(msg string, a ...interface{}) + PinnedMessages() []string SetAutoStop(autoStop bool) SetMessageWidth(width int) SetNumTrackersExpected(numTrackers int) SetOutputWriter(output io.Writer) + SetPinnedMessages(messages ...string) SetSortBy(sortBy SortBy) SetStyle(style Style) SetTrackerLength(length int) @@ -38,7 +39,6 @@ type Writer interface { // Deprecated: in favor of Style().Visibility.Value ShowValue(show bool) SetUpdateFrequency(frequency time.Duration) - SetPinMessage(messages ...string) Stop() Style() *Style Render() diff --git a/text/direction_test.go b/text/direction_test.go new file mode 100644 index 0000000..f6d308c --- /dev/null +++ b/text/direction_test.go @@ -0,0 +1,13 @@ +package text + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDirection_Modifier(t *testing.T) { + assert.Equal(t, "", Default.Modifier()) + assert.Equal(t, "\u202a", LeftToRight.Modifier()) + assert.Equal(t, "\u202b", RightToLeft.Modifier()) +}