Skip to content

Commit 2ac3ff0

Browse files
authored
progress: option to override tracker rendering logic (#369)
1 parent c802c02 commit 2ac3ff0

File tree

4 files changed

+161
-1
lines changed

4 files changed

+161
-1
lines changed

cmd/demo-progress/demo.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"flag"
55
"fmt"
66
"math/rand"
7+
"strings"
78
"time"
89

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

3032
messageColors = []text.Color{
3133
text.FgRed,
@@ -119,6 +121,51 @@ func trackSomething(pw progress.Writer, idx int64, updateMessage bool) {
119121
}
120122
}
121123

124+
// customTrackerRender creates a progress bar using rainbow colors for determinate progress
125+
func customTrackerRender(value int64, total int64, maxLen int) string {
126+
progress := float64(value) / float64(total)
127+
completed := int(progress * float64(maxLen))
128+
129+
var result strings.Builder
130+
for i := 0; i < maxLen; i++ {
131+
if i < completed {
132+
// Use rainbow colors based on position in the progress bar
133+
colorIdx := (i * 6) / maxLen // Map position to 6 rainbow colors
134+
colors := []text.Color{
135+
text.FgRed,
136+
text.FgYellow,
137+
text.FgGreen,
138+
text.FgCyan,
139+
text.FgBlue,
140+
text.FgMagenta,
141+
}
142+
if colorIdx >= len(colors) {
143+
colorIdx = len(colors) - 1
144+
}
145+
result.WriteString(colors[colorIdx].Sprint("█"))
146+
} else {
147+
result.WriteString(text.FgHiBlack.Sprint("░"))
148+
}
149+
}
150+
151+
return result.String()
152+
}
153+
154+
// customTrackerIndeterminateRender creates a progress bar using rotating rainbow colors for indeterminate progress
155+
func customTrackerIndeterminateRender(maxLen int) string {
156+
// For indeterminate progress, use rotating rainbow colors
157+
colors := []text.Color{
158+
text.FgRed,
159+
text.FgYellow,
160+
text.FgGreen,
161+
text.FgCyan,
162+
text.FgBlue,
163+
text.FgMagenta,
164+
}
165+
idx := int(time.Now().UnixNano()/100000000) % len(colors)
166+
return colors[idx].Sprint(strings.Repeat("█", maxLen))
167+
}
168+
122169
func main() {
123170
flag.Parse()
124171
fmt.Printf("Tracking Progress of %d trackers ...\n\n", *flagNumTrackers)
@@ -145,6 +192,13 @@ func main() {
145192
pw.Style().Visibility.Value = !*flagHideValue
146193
pw.Style().Visibility.Pinned = *flagShowPinned
147194

195+
// set up custom render functions if flag is enabled
196+
if *flagCustomRender {
197+
pw.Style().Renderer.TrackerDeterminate = customTrackerRender
198+
pw.Style().Renderer.TrackerIndeterminate = customTrackerIndeterminateRender
199+
fmt.Println("Using custom render functions with rainbow colors!")
200+
}
201+
148202
// call Render() in async mode; yes we don't have any trackers at the moment
149203
go pw.Render()
150204

progress/render.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ func (p *Progress) generateTrackerStr(t *Tracker, maxLen int, hint renderHint) s
107107
// generateTrackerStrDeterminate generates the tracker string for the case where
108108
// the Total value is known, and the progress percentage can be calculated.
109109
func (p *Progress) generateTrackerStrDeterminate(value int64, total int64, maxLen int) string {
110+
if p.style.Renderer.TrackerDeterminate != nil {
111+
return p.style.Renderer.TrackerDeterminate(value, total, maxLen)
112+
}
113+
110114
pFinishedDots, pFinishedDotsFraction := 0.0, 0.0
111115
pDotValue := float64(total) / float64(maxLen)
112116
if pDotValue > 0 {
@@ -139,9 +143,13 @@ func (p *Progress) generateTrackerStrDeterminate(value int64, total int64, maxLe
139143
)
140144
}
141145

142-
// generateTrackerStrDeterminate generates the tracker string for the case where
146+
// generateTrackerStrIndeterminate generates the tracker string for the case where
143147
// the Total value is unknown, and the progress percentage cannot be calculated.
144148
func (p *Progress) generateTrackerStrIndeterminate(maxLen int) string {
149+
if p.style.Renderer.TrackerIndeterminate != nil {
150+
return p.style.Renderer.TrackerIndeterminate(maxLen)
151+
}
152+
145153
indicator := p.style.Chars.Indeterminate(maxLen)
146154

147155
pUnfinished := ""

progress/render_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,3 +938,87 @@ func TestProgress_RenderSomeTrackers_WithPinnedMessages_MultiLines(t *testing.T)
938938
}
939939
showOutputOnFailure(t, out)
940940
}
941+
942+
func TestProgress_RenderSomeTrackers_WithCustomTrackerDeterminate(t *testing.T) {
943+
renderOutput := outputWriter{}
944+
945+
pw := generateWriter()
946+
pw.SetOutputWriter(&renderOutput)
947+
pw.SetTrackerPosition(PositionRight)
948+
949+
symbols := []string{"♠", "♥", "♦", "♣", "🂡", "🂱"}
950+
951+
pw.Style().Renderer.TrackerDeterminate = func(value int64, total int64, maxLen int) string {
952+
v := float64(value) / float64(total)
953+
b := &strings.Builder{}
954+
fmt.Fprintf(b, "[")
955+
inner := maxLen - 2
956+
for i := 0; i < inner; i++ {
957+
delta := float64(i) / float64(inner)
958+
symbolIdx := int(delta * float64(len(symbols)))
959+
if symbolIdx >= len(symbols) {
960+
symbolIdx = len(symbols) - 1
961+
}
962+
if delta < v {
963+
fmt.Fprintf(b, "%s", symbols[symbolIdx])
964+
} else {
965+
fmt.Fprintf(b, " ")
966+
}
967+
}
968+
fmt.Fprintf(b, "]")
969+
return b.String()
970+
}
971+
972+
go trackSomething(pw, &Tracker{Message: "Custom Tracker #1", Total: 1000, Units: UnitsDefault})
973+
go trackSomething(pw, &Tracker{Message: "Custom Tracker #2", Total: 1000, Units: UnitsBytes})
974+
renderAndWait(pw, false)
975+
976+
expectedOutPatterns := []*regexp.Regexp{
977+
regexp.MustCompile(`Custom Tracker #1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`),
978+
regexp.MustCompile(`Custom Tracker #2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`),
979+
}
980+
out := renderOutput.String()
981+
for _, expectedOutPattern := range expectedOutPatterns {
982+
if !expectedOutPattern.MatchString(out) {
983+
assert.Fail(t, "Failed to find a pattern in the Output.", expectedOutPattern.String())
984+
}
985+
}
986+
showOutputOnFailure(t, out)
987+
}
988+
989+
func TestProgress_RenderSomeTrackers_WithCustomTrackerIndeterminate(t *testing.T) {
990+
renderOutput := outputWriter{}
991+
992+
pw := generateWriter()
993+
pw.SetOutputWriter(&renderOutput)
994+
pw.SetTrackerPosition(PositionRight)
995+
996+
pw.Style().Renderer.TrackerIndeterminate = func(maxLen int) string {
997+
b := &strings.Builder{}
998+
fmt.Fprintf(b, "[")
999+
inner := maxLen - 2
1000+
for i := 0; i < inner; i++ {
1001+
if i == 0 {
1002+
fmt.Fprintf(b, "★")
1003+
} else {
1004+
fmt.Fprintf(b, "☆")
1005+
}
1006+
}
1007+
fmt.Fprintf(b, "]")
1008+
return b.String()
1009+
}
1010+
1011+
go trackSomethingIndeterminate(pw, &Tracker{Message: "Indeterminate Tracker", Total: 0, Units: UnitsDefault})
1012+
renderAndWait(pw, false)
1013+
1014+
expectedOutPatterns := []*regexp.Regexp{
1015+
regexp.MustCompile(`Indeterminate Tracker \.\.\. done! \[\d+ in [\d.]+ms]`),
1016+
}
1017+
out := renderOutput.String()
1018+
for _, expectedOutPattern := range expectedOutPatterns {
1019+
if !expectedOutPattern.MatchString(out) {
1020+
assert.Fail(t, "Failed to find a pattern in the Output.", expectedOutPattern.String())
1021+
}
1022+
}
1023+
showOutputOnFailure(t, out)
1024+
}

progress/style.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Style struct {
1313
Colors StyleColors // colors to use on the progress bar
1414
Options StyleOptions // misc. options for the progress bar
1515
Visibility StyleVisibility // show/hide components of the progress bar(s)
16+
Renderer StyleRenderer // custom render functions for the progress bar
1617
}
1718

1819
var (
@@ -215,3 +216,16 @@ var StyleVisibilityDefault = StyleVisibility{
215216
TrackerOverall: false,
216217
Value: true,
217218
}
219+
220+
type StyleRenderer struct {
221+
// TrackerDeterminate will override how the progress bar is rendered.
222+
// value is the current value of the tracker out of total.
223+
// maxLen is the number of characters available for the progress bar.
224+
// return the complete progress bar string. E.g. [===----]
225+
TrackerDeterminate func(value int64, total int64, maxLen int) string
226+
227+
// TrackerIndeterminate will override how the indeterminate progress bar is rendered.
228+
// maxLen is the number of characters available for the progress bar.
229+
// return the complete progress bar string. E.g. [<#>----]
230+
TrackerIndeterminate func(maxLen int) string
231+
}

0 commit comments

Comments
 (0)