Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions cmd/demo-progress/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"math/rand"
"strings"
"time"

"github.com/jedib0t/go-pretty/v6/progress"
Expand All @@ -26,6 +27,7 @@ var (
flagRandomDefer = flag.Bool("rnd-defer", false, "Introduce random deferred starts")
flagRandomRemove = flag.Bool("rnd-remove", false, "Introduce random remove of trackers on completion")
flagRandomLogs = flag.Bool("rnd-logs", false, "Output random logs in the middle of tracking")
flagCustomRender = flag.Bool("custom-render", false, "Use custom render functions with rainbow colors")

messageColors = []text.Color{
text.FgRed,
Expand Down Expand Up @@ -119,6 +121,51 @@ func trackSomething(pw progress.Writer, idx int64, updateMessage bool) {
}
}

// customTrackerRender creates a progress bar using rainbow colors for determinate progress
func customTrackerRender(value int64, total int64, maxLen int) string {
progress := float64(value) / float64(total)
completed := int(progress * float64(maxLen))

var result strings.Builder
for i := 0; i < maxLen; i++ {
if i < completed {
// Use rainbow colors based on position in the progress bar
colorIdx := (i * 6) / maxLen // Map position to 6 rainbow colors
colors := []text.Color{
text.FgRed,
text.FgYellow,
text.FgGreen,
text.FgCyan,
text.FgBlue,
text.FgMagenta,
}
if colorIdx >= len(colors) {
colorIdx = len(colors) - 1
}
result.WriteString(colors[colorIdx].Sprint("█"))
} else {
result.WriteString(text.FgHiBlack.Sprint("░"))
}
}

return result.String()
}

// customTrackerIndeterminateRender creates a progress bar using rotating rainbow colors for indeterminate progress
func customTrackerIndeterminateRender(maxLen int) string {
// For indeterminate progress, use rotating rainbow colors
colors := []text.Color{
text.FgRed,
text.FgYellow,
text.FgGreen,
text.FgCyan,
text.FgBlue,
text.FgMagenta,
}
idx := int(time.Now().UnixNano()/100000000) % len(colors)
return colors[idx].Sprint(strings.Repeat("█", maxLen))
}

func main() {
flag.Parse()
fmt.Printf("Tracking Progress of %d trackers ...\n\n", *flagNumTrackers)
Expand All @@ -145,6 +192,13 @@ func main() {
pw.Style().Visibility.Value = !*flagHideValue
pw.Style().Visibility.Pinned = *flagShowPinned

// set up custom render functions if flag is enabled
if *flagCustomRender {
pw.Style().Renderer.TrackerDeterminate = customTrackerRender
pw.Style().Renderer.TrackerIndeterminate = customTrackerIndeterminateRender
fmt.Println("Using custom render functions with rainbow colors!")
}

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

Expand Down
10 changes: 9 additions & 1 deletion progress/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ func (p *Progress) generateTrackerStr(t *Tracker, maxLen int, hint renderHint) s
// generateTrackerStrDeterminate generates the tracker string for the case where
// the Total value is known, and the progress percentage can be calculated.
func (p *Progress) generateTrackerStrDeterminate(value int64, total int64, maxLen int) string {
if p.style.Renderer.TrackerDeterminate != nil {
return p.style.Renderer.TrackerDeterminate(value, total, maxLen)
}

pFinishedDots, pFinishedDotsFraction := 0.0, 0.0
pDotValue := float64(total) / float64(maxLen)
if pDotValue > 0 {
Expand Down Expand Up @@ -139,9 +143,13 @@ func (p *Progress) generateTrackerStrDeterminate(value int64, total int64, maxLe
)
}

// generateTrackerStrDeterminate generates the tracker string for the case where
// generateTrackerStrIndeterminate generates the tracker string for the case where
// the Total value is unknown, and the progress percentage cannot be calculated.
func (p *Progress) generateTrackerStrIndeterminate(maxLen int) string {
if p.style.Renderer.TrackerIndeterminate != nil {
return p.style.Renderer.TrackerIndeterminate(maxLen)
}

indicator := p.style.Chars.Indeterminate(maxLen)

pUnfinished := ""
Expand Down
84 changes: 84 additions & 0 deletions progress/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -938,3 +938,87 @@ func TestProgress_RenderSomeTrackers_WithPinnedMessages_MultiLines(t *testing.T)
}
showOutputOnFailure(t, out)
}

func TestProgress_RenderSomeTrackers_WithCustomTrackerDeterminate(t *testing.T) {
renderOutput := outputWriter{}

pw := generateWriter()
pw.SetOutputWriter(&renderOutput)
pw.SetTrackerPosition(PositionRight)

symbols := []string{"♠", "♥", "♦", "♣", "🂡", "🂱"}

pw.Style().Renderer.TrackerDeterminate = func(value int64, total int64, maxLen int) string {
v := float64(value) / float64(total)
b := &strings.Builder{}
fmt.Fprintf(b, "[")
inner := maxLen - 2
for i := 0; i < inner; i++ {
delta := float64(i) / float64(inner)
symbolIdx := int(delta * float64(len(symbols)))
if symbolIdx >= len(symbols) {
symbolIdx = len(symbols) - 1
}
if delta < v {
fmt.Fprintf(b, "%s", symbols[symbolIdx])
} else {
fmt.Fprintf(b, " ")
}
}
fmt.Fprintf(b, "]")
return b.String()
}

go trackSomething(pw, &Tracker{Message: "Custom Tracker #1", Total: 1000, Units: UnitsDefault})
go trackSomething(pw, &Tracker{Message: "Custom Tracker #2", Total: 1000, Units: UnitsBytes})
renderAndWait(pw, false)

expectedOutPatterns := []*regexp.Regexp{
regexp.MustCompile(`Custom Tracker #1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`),
regexp.MustCompile(`Custom Tracker #2 \.\.\. done! \[\d+\.\d+KB 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_WithCustomTrackerIndeterminate(t *testing.T) {
renderOutput := outputWriter{}

pw := generateWriter()
pw.SetOutputWriter(&renderOutput)
pw.SetTrackerPosition(PositionRight)

pw.Style().Renderer.TrackerIndeterminate = func(maxLen int) string {
b := &strings.Builder{}
fmt.Fprintf(b, "[")
inner := maxLen - 2
for i := 0; i < inner; i++ {
if i == 0 {
fmt.Fprintf(b, "★")
} else {
fmt.Fprintf(b, "☆")
}
}
fmt.Fprintf(b, "]")
return b.String()
}

go trackSomethingIndeterminate(pw, &Tracker{Message: "Indeterminate Tracker", Total: 0, Units: UnitsDefault})
renderAndWait(pw, false)

expectedOutPatterns := []*regexp.Regexp{
regexp.MustCompile(`Indeterminate Tracker \.\.\. done! \[\d+ 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)
}
14 changes: 14 additions & 0 deletions progress/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Style struct {
Colors StyleColors // colors to use on the progress bar
Options StyleOptions // misc. options for the progress bar
Visibility StyleVisibility // show/hide components of the progress bar(s)
Renderer StyleRenderer // custom render functions for the progress bar
}

var (
Expand Down Expand Up @@ -215,3 +216,16 @@ var StyleVisibilityDefault = StyleVisibility{
TrackerOverall: false,
Value: true,
}

type StyleRenderer struct {
// TrackerDeterminate will override how the progress bar is rendered.
// value is the current value of the tracker out of total.
// maxLen is the number of characters available for the progress bar.
// return the complete progress bar string. E.g. [===----]
TrackerDeterminate func(value int64, total int64, maxLen int) string
Comment thread
ptxmac marked this conversation as resolved.

// TrackerIndeterminate will override how the indeterminate progress bar is rendered.
// maxLen is the number of characters available for the progress bar.
// return the complete progress bar string. E.g. [<#>----]
TrackerIndeterminate func(maxLen int) string
}