Skip to content

Commit

Permalink
progress: refactor visibility options (Show*); fixes #196 (#199)
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t authored Apr 14, 2022
1 parent 5d0c7e3 commit 35b05aa
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 87 deletions.
49 changes: 28 additions & 21 deletions cmd/demo-progress/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ import (
)

var (
autoStop = flag.Bool("auto-stop", false, "Auto-stop rendering?")
numTrackers = flag.Int("num-trackers", 13, "Number of Trackers")
randomFail = flag.Bool("rnd-fail", false, "Enable random failures in tracking")
randomLogs = flag.Bool("rnd-logs", false, "Enable random logs in the middle of tracking")
flagAutoStop = flag.Bool("auto-stop", false, "Auto-stop rendering?")
flagHideETA = flag.Bool("hide-eta", false, "Hide the ETA?")
flagHideETAOverall = flag.Bool("hide-eta-overall", false, "Hide the ETA in the overall tracker?")
flagHideOverallTracker = flag.Bool("hide-overall", false, "Hide the Overall Tracker?")
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?")
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")

messageColors = []text.Color{
text.FgRed,
Expand Down Expand Up @@ -57,12 +63,12 @@ func getUnits(idx int64) *progress.Units {

func trackSomething(pw progress.Writer, idx int64, updateMessage bool) {
total := idx * idx * idx * 250
incrementPerCycle := idx * int64(*numTrackers) * 250
incrementPerCycle := idx * int64(*flagNumTrackers) * 250

units := getUnits(idx)
message := getMessage(idx, units)
tracker := progress.Tracker{Message: message, Total: total, Units: *units}
if idx == int64(*numTrackers) {
if idx == int64(*flagNumTrackers) {
tracker.Total = 0
}

Expand All @@ -74,9 +80,9 @@ func trackSomething(pw progress.Writer, idx int64, updateMessage bool) {
select {
case <-ticker:
tracker.Increment(incrementPerCycle)
if idx == int64(*numTrackers) && tracker.Value() >= total {
if idx == int64(*flagNumTrackers) && tracker.Value() >= total {
tracker.MarkAsDone()
} else if *randomFail && rand.Float64() < 0.1 {
} else if *flagRandomFail && rand.Float64() < 0.1 {
tracker.MarkAsErrored()
}
case <-updateTicker:
Expand All @@ -93,40 +99,41 @@ func trackSomething(pw progress.Writer, idx int64, updateMessage bool) {

func main() {
flag.Parse()
fmt.Printf("Tracking Progress of %d trackers ...\n\n", *numTrackers)
fmt.Printf("Tracking Progress of %d trackers ...\n\n", *flagNumTrackers)

// instantiate a Progress Writer and set up the options
pw := progress.NewWriter()
pw.SetAutoStop(*autoStop)
pw.SetAutoStop(*flagAutoStop)
pw.SetTrackerLength(25)
pw.ShowETA(true)
pw.ShowOverallTracker(true)
pw.ShowTime(true)
pw.ShowTracker(true)
pw.ShowValue(true)
pw.SetMessageWidth(24)
pw.SetNumTrackersExpected(*numTrackers)
pw.SetNumTrackersExpected(*flagNumTrackers)
pw.SetSortBy(progress.SortByPercentDsc)
pw.SetStyle(progress.StyleDefault)
pw.SetTrackerPosition(progress.PositionRight)
pw.SetUpdateFrequency(time.Millisecond * 100)
pw.Style().Colors = progress.StyleColorsExample
pw.Style().Options.PercentFormat = "%4.1f%%"
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

// call Render() in async mode; yes we don't have any trackers at the moment
go pw.Render()

// add a bunch of trackers with random parameters to demo most of the
// features available; do this in async too like a client might do (for ex.
// when downloading a bunch of files in parallel)
for idx := int64(1); idx <= int64(*numTrackers); idx++ {
go trackSomething(pw, idx, idx == int64(*numTrackers))
for idx := int64(1); idx <= int64(*flagNumTrackers); idx++ {
go trackSomething(pw, idx, idx == int64(*flagNumTrackers))

// in auto-stop mode, the Render logic terminates the moment it detects
// zero active trackers; but in a manual-stop mode, it keeps waiting and
// is a good chance to demo trackers being added dynamically while other
// trackers are active or done
if !*autoStop {
if !*flagAutoStop {
time.Sleep(time.Millisecond * 100)
}
}
Expand All @@ -136,7 +143,7 @@ func main() {
time.Sleep(time.Second)
messagesLogged := make(map[string]bool)
for pw.IsRenderInProgress() {
if *randomLogs && pw.LengthDone()%3 == 0 {
if *flagRandomLogs && pw.LengthDone()%3 == 0 {
logMsg := text.Faint.Sprintf("[INFO] done with %d trackers", pw.LengthDone())
if !messagesLogged[logMsg] {
pw.Log(logMsg)
Expand All @@ -145,7 +152,7 @@ func main() {
}

// for manual-stop mode, stop when there are no more active trackers
if !*autoStop && pw.LengthActive() == 0 {
if !*flagAutoStop && pw.LengthActive() == 0 {
pw.Stop()
}
time.Sleep(time.Millisecond * 100)
Expand Down
35 changes: 22 additions & 13 deletions progress/progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"sync"
"time"
"unicode/utf8"

"github.com/jedib0t/go-pretty/v6/text"
)

var (
Expand All @@ -24,11 +26,8 @@ type Progress struct {
done chan bool
lengthTracker int
lengthProgress int
lengthProgressOverall int
outputWriter io.Writer
hideTime bool
hideTracker bool
hideValue bool
hidePercentage bool
logsToRender []string
logsToRenderMutex sync.RWMutex
messageWidth int
Expand All @@ -37,8 +36,6 @@ type Progress struct {
overallTrackerMutex sync.RWMutex
renderInProgress bool
renderInProgressMutex sync.RWMutex
showETA bool
showOverallTracker bool
sortBy SortBy
style *Style
trackerPosition Position
Expand Down Expand Up @@ -216,33 +213,39 @@ func (p *Progress) SetUpdateFrequency(frequency time.Duration) {
}

// ShowETA toggles showing the ETA for all individual trackers.
// Deprecated: in favor of Style().Visibility.ETA
func (p *Progress) ShowETA(show bool) {
p.showETA = show
p.Style().Visibility.ETA = show
}

// ShowPercentage toggles showing the Percent complete for each Tracker.
// Deprecated: in favor of Style().Visibility.Percentage
func (p *Progress) ShowPercentage(show bool) {
p.hidePercentage = !show
p.Style().Visibility.Percentage = show
}

// ShowOverallTracker toggles showing the Overall progress tracker with an ETA.
// Deprecated: in favor of Style().Visibility.TrackerOverall
func (p *Progress) ShowOverallTracker(show bool) {
p.showOverallTracker = show
p.Style().Visibility.TrackerOverall = show
}

// ShowTime toggles showing the Time taken by each Tracker.
// Deprecated: in favor of Style().Visibility.Time
func (p *Progress) ShowTime(show bool) {
p.hideTime = !show
p.Style().Visibility.Time = show
}

// ShowTracker toggles showing the Tracker (the progress bar).
// Deprecated: in favor of Style().Visibility.Tracker
func (p *Progress) ShowTracker(show bool) {
p.hideTracker = !show
p.Style().Visibility.Tracker = show
}

// ShowValue toggles showing the actual Value of the Tracker.
// Deprecated: in favor of Style().Visibility.Value
func (p *Progress) ShowValue(show bool) {
p.hideValue = !show
p.Style().Visibility.Value = show
}

// Stop stops the Render() logic that is in progress.
Expand Down Expand Up @@ -273,11 +276,17 @@ func (p *Progress) initForRender() {
p.lengthTracker = DefaultLengthTracker
}

// calculate length of the actual progress bar by discount the left/right
// calculate length of the actual progress bar by discounting the left/right
// border/box chars
p.lengthProgress = p.lengthTracker -
utf8.RuneCountInString(p.style.Chars.BoxLeft) -
utf8.RuneCountInString(p.style.Chars.BoxRight)
p.lengthProgressOverall = p.messageWidth +
text.RuneCount(p.style.Options.Separator) +
p.lengthProgress + 1
if p.style.Visibility.Percentage {
p.lengthProgressOverall += text.RuneCount(fmt.Sprintf(p.style.Options.PercentFormat, 0.0))
}

// if not output write has been set, output to STDOUT
if p.outputWriter == nil {
Expand Down
28 changes: 18 additions & 10 deletions progress/progress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,44 +165,52 @@ func TestProgress_SetUpdateFrequency(t *testing.T) {
assert.Equal(t, time.Duration(time.Second), p.updateFrequency)
}

func TestProgress_ShowETA(t *testing.T) {
p := Progress{}
assert.False(t, p.Style().Visibility.ETA)

p.ShowETA(true)
assert.True(t, p.Style().Visibility.ETA)
}

func TestProgress_ShowOverallTracker(t *testing.T) {
p := Progress{}
assert.False(t, p.showOverallTracker)
assert.False(t, p.Style().Visibility.TrackerOverall)

p.ShowOverallTracker(true)
assert.True(t, p.showOverallTracker)
assert.True(t, p.Style().Visibility.TrackerOverall)
}

func TestProgress_ShowPercentage(t *testing.T) {
p := Progress{}
assert.False(t, p.hidePercentage)
assert.True(t, p.Style().Visibility.Percentage)

p.ShowPercentage(false)
assert.True(t, p.hidePercentage)
assert.False(t, p.Style().Visibility.Percentage)
}

func TestProgress_ShowTime(t *testing.T) {
p := Progress{}
assert.False(t, p.hideTime)
assert.True(t, p.Style().Visibility.Time)

p.ShowTime(false)
assert.True(t, p.hideTime)
assert.False(t, p.Style().Visibility.Time)
}

func TestProgress_ShowTracker(t *testing.T) {
p := Progress{}
assert.False(t, p.hideTracker)
assert.True(t, p.Style().Visibility.Tracker)

p.ShowTracker(false)
assert.True(t, p.hideTracker)
assert.False(t, p.Style().Visibility.Tracker)
}

func TestProgress_ShowValue(t *testing.T) {
p := Progress{}
assert.False(t, p.hideValue)
assert.True(t, p.Style().Visibility.Value)

p.ShowValue(false)
assert.True(t, p.hideValue)
assert.False(t, p.Style().Visibility.Value)
}

func TestProgress_Stop(t *testing.T) {
Expand Down
32 changes: 17 additions & 15 deletions progress/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func (p *Progress) generateTrackerStrIndeterminate(maxLen int) string {

func (p *Progress) moveCursorToTheTop(out *strings.Builder) {
numLinesToMoveUp := len(p.trackersActive)
if p.showOverallTracker && p.overallTracker != nil && !p.overallTracker.IsDone() {
if p.style.Visibility.TrackerOverall && p.overallTracker != nil && !p.overallTracker.IsDone() {
numLinesToMoveUp++
}
if numLinesToMoveUp > 0 {
Expand Down Expand Up @@ -183,18 +183,14 @@ func (p *Progress) renderTracker(out *strings.Builder, t *Tracker, hint renderHi
out.WriteString(text.EraseLine.Sprint())
if hint.isOverallTracker {
if !t.IsDone() {
trackerLen := p.messageWidth
trackerLen += text.RuneCount(p.style.Options.Separator)
trackerLen += text.RuneCount(p.style.Options.DoneString)
trackerLen += p.lengthProgress + 1
hint := renderHint{hideValue: true, isOverallTracker: true}
p.renderTrackerProgress(out, t, message, p.generateTrackerStr(t, trackerLen, hint), hint)
p.renderTrackerProgress(out, t, message, p.generateTrackerStr(t, p.lengthProgressOverall, hint), hint)
}
} else {
if t.IsDone() {
p.renderTrackerDone(out, t, message)
} else {
hint := renderHint{hideTime: p.hideTime, hideValue: p.hideValue}
hint := renderHint{hideTime: !p.style.Visibility.Time, hideValue: !p.style.Visibility.Value}
p.renderTrackerProgress(out, t, message, p.generateTrackerStr(t, p.lengthProgress, hint), hint)
}
}
Expand All @@ -208,7 +204,7 @@ func (p *Progress) renderTrackerDone(out *strings.Builder, t *Tracker, message s
} else {
out.WriteString(p.style.Colors.Error.Sprint(p.style.Options.ErrorString))
}
p.renderTrackerStats(out, t, renderHint{hideTime: p.hideTime, hideValue: p.hideValue})
p.renderTrackerStats(out, t, renderHint{hideTime: !p.style.Visibility.Time, hideValue: !p.style.Visibility.Value})
out.WriteRune('\n')
}

Expand All @@ -221,7 +217,7 @@ func (p *Progress) renderTrackerMessage(out *strings.Builder, t *Tracker, messag
}

func (p *Progress) renderTrackerPercentage(out *strings.Builder, t *Tracker) {
if !p.hidePercentage {
if p.style.Visibility.Percentage {
var percentageStr string
if t.IsIndeterminate() {
percentageStr = p.style.Options.PercentIndeterminate
Expand All @@ -241,14 +237,14 @@ func (p *Progress) renderTrackerProgress(out *strings.Builder, t *Tracker, messa
p.renderTrackerMessage(out, t, message)
out.WriteString(p.style.Colors.Message.Sprint(p.style.Options.Separator))
p.renderTrackerPercentage(out, t)
if !p.hideTracker {
if p.style.Visibility.Tracker {
out.WriteString(p.style.Colors.Tracker.Sprint(" " + trackerStr))
}
p.renderTrackerStats(out, t, hint)
out.WriteRune('\n')
} else {
p.renderTrackerPercentage(out, t)
if !p.hideTracker {
if p.style.Visibility.Tracker {
out.WriteString(p.style.Colors.Tracker.Sprint(" " + trackerStr))
}
p.renderTrackerStats(out, t, hint)
Expand Down Expand Up @@ -276,7 +272,7 @@ func (p *Progress) renderTrackers(lastRenderLength int) int {
p.renderTrackersDoneAndActive(&out)

// render the overall tracker
if p.showOverallTracker {
if p.style.Visibility.TrackerOverall {
p.renderTracker(&out, p.overallTracker, renderHint{isOverallTracker: true})
}

Expand Down Expand Up @@ -356,12 +352,18 @@ func (p *Progress) renderTrackerStatsTime(outStats *strings.Builder, t *Tracker,
tp = p.style.Options.TimeInProgressPrecision
}
outStats.WriteString(p.style.Colors.Time.Sprint(td.Round(tp)))
if p.showETA || hint.isOverallTracker {
p.renderTrackerStatsETA(outStats, t, hint)
}

p.renderTrackerStatsETA(outStats, t, hint)
}

func (p *Progress) renderTrackerStatsETA(out *strings.Builder, t *Tracker, hint renderHint) {
if hint.isOverallTracker && !p.style.Visibility.ETAOverall {
return
}
if !hint.isOverallTracker && !p.style.Visibility.ETA {
return
}

tpETA := p.style.Options.ETAPrecision
if eta := t.ETA().Round(tpETA); hint.isOverallTracker || eta > tpETA {
out.WriteString("; ")
Expand Down
Loading

0 comments on commit 35b05aa

Please sign in to comment.