Skip to content

Commit

Permalink
progress: "pin"->"pinned"; sort interfaces/structs
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t committed Oct 2, 2022
1 parent 99ac11f commit 0772882
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 66 deletions.
13 changes: 9 additions & 4 deletions cmd/demo-progress/demo.go
Expand Up @@ -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")

Expand All @@ -34,6 +34,7 @@ var (
text.FgCyan,
text.FgWhite,
}
timeStart = time.Now()
)

func getMessage(idx int64, units *progress.Units) string {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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()
Expand Down
43 changes: 22 additions & 21 deletions progress/progress.go
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"io"
"os"
"strings"
"sync"
"time"
"unicode/utf8"
Expand All @@ -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
Expand Down Expand Up @@ -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{}) {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
32 changes: 16 additions & 16 deletions progress/progress_test.go
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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())
}
31 changes: 16 additions & 15 deletions progress/render.go
Expand Up @@ -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") {
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions progress/render_test.go
Expand Up @@ -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})
Expand Down Expand Up @@ -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})
Expand Down
8 changes: 4 additions & 4 deletions progress/style.go
Expand Up @@ -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
Expand All @@ -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},
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions progress/writer.go
Expand Up @@ -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)
Expand All @@ -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()
Expand Down
13 changes: 13 additions & 0 deletions 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())
}

0 comments on commit 0772882

Please sign in to comment.