Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added BarStartFilled and BarEndFille to Theme struct #203 #204

Merged
merged 2 commits into from
Oct 3, 2024
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
80 changes: 63 additions & 17 deletions progressbar.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,45 @@ type Theme struct {
SaucerPadding string
BarStart string
BarEnd string
}

// BarStartFilled is used after the Bar starts filling, if set. Otherwise, it defaults to BarStart.
BarStartFilled string

// BarEndFilled is used once the Bar finishes, if set. Otherwise, it defaults to BarEnd.
BarEndFilled string
}

var (
// ThemeDefault is given by default (if not changed with OptionSetTheme), and it looks like "|████ |".
ThemeDefault = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEnd: "|"}

// ThemeASCII is a predefined Theme that uses ASCII symbols. It looks like "[===>...]".
// Configure it with OptionSetTheme(ThemeASCII).
ThemeASCII = Theme{
Saucer: "=",
SaucerHead: ">",
SaucerPadding: ".",
BarStart: "[",
BarEnd: "]",
}

// ThemeUnicode is a predefined Theme that uses Unicode characters, displaying a graphic bar.
// It looks like "" (rendering will depend on font being used).
// It requires special symbols usually found in "nerd fonts" [2], or in Fira Code [1], and other sources.
// Configure it with OptionSetTheme(ThemeUnicode).
//
// [1] /~https://github.com/tonsky/FiraCode
// [2] https://www.nerdfonts.com/
ThemeUnicode = Theme{
Saucer: "\uEE04", // 
SaucerHead: "\uEE04", // 
SaucerPadding: "\uEE01", // 
BarStart: "\uEE00", // 
BarStartFilled: "\uEE03", // 
BarEnd: "\uEE02", // 
BarEndFilled: "\uEE05", // 
}
)

// Option is the type all options need to adhere to
type Option func(p *ProgressBar)
Expand Down Expand Up @@ -185,7 +223,8 @@ func OptionSpinnerCustom(spinner []string) Option {
}
}

// OptionSetTheme sets the elements the bar is constructed of
// OptionSetTheme sets the elements the bar is constructed with.
// There are two pre-defined themes you can use: ThemeASCII and ThemeUnicode.
func OptionSetTheme(t Theme) Option {
return func(p *ProgressBar) {
p.config.theme = t
Expand Down Expand Up @@ -263,7 +302,7 @@ func OptionShowIts() Option {
}
}

// OptionShowElapsedOnFinish will keep the display of elapsed time on finish
// OptionShowElapsedTimeOnFinish will keep the display of elapsed time on finish.
func OptionShowElapsedTimeOnFinish() Option {
return func(p *ProgressBar) {
p.config.showElapsedTimeOnFinish = true
Expand All @@ -285,7 +324,7 @@ func OptionThrottle(duration time.Duration) Option {
}
}

// OptionClearOnFinish will clear the bar once its finished
// OptionClearOnFinish will clear the bar once its finished.
func OptionClearOnFinish() Option {
return func(p *ProgressBar) {
p.config.clearOnFinish = true
Expand Down Expand Up @@ -339,8 +378,6 @@ func OptionSetMaxDetailRow(row int) Option {
}
}

var defaultTheme = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEnd: "|"}

// NewOptions constructs a new instance of ProgressBar, with any options you specify
func NewOptions(max int, options ...Option) *ProgressBar {
return NewOptions64(int64(max), options...)
Expand All @@ -356,7 +393,7 @@ func NewOptions64(max int64, options ...Option) *ProgressBar {
},
config: config{
writer: os.Stdout,
theme: defaultTheme,
theme: ThemeDefault,
iterationString: "it",
width: 40,
max: max,
Expand Down Expand Up @@ -830,10 +867,12 @@ func (p *ProgressBar) render() error {
}

if !p.config.useANSICodes {
// first, clear the existing progress bar
err := clearProgressBar(p.config, p.state)
if err != nil {
return err
// first, clear the existing progress bar, if not yet finished.
if !p.state.finished {
err := clearProgressBar(p.config, p.state)
if err != nil {
return err
}
}
}

Expand Down Expand Up @@ -1021,6 +1060,10 @@ func renderProgressBar(c config, s *state) (int, error) {
}

leftBrac, rightBrac, saucer, saucerHead := "", "", "", ""
barStart, barEnd := c.theme.BarStart, c.theme.BarEnd
if s.finished && c.theme.BarEndFilled != "" {
barEnd = c.theme.BarEndFilled
}

// show time prediction in "current/total" seconds format
switch {
Expand Down Expand Up @@ -1057,6 +1100,9 @@ func renderProgressBar(c config, s *state) (int, error) {
c.width = width - getStringWidth(c, c.description, true) - 10 - amend - sb.Len() - len(leftBrac) - len(rightBrac)
s.currentSaucerSize = int(float64(s.currentPercent) / 100.0 * float64(c.width))
}
if (s.currentSaucerSize > 0 || s.currentPercent > 0) && c.theme.BarStartFilled != "" {
barStart = c.theme.BarStartFilled
}
if s.currentSaucerSize > 0 {
if c.ignoreLength {
saucer = strings.Repeat(c.theme.SaucerPadding, s.currentSaucerSize-1)
Expand Down Expand Up @@ -1138,11 +1184,11 @@ func renderProgressBar(c config, s *state) (int, error) {
} else if rightBrac == "" {
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
s.currentPercent,
c.theme.BarStart,
barStart,
saucer,
saucerHead,
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
c.theme.BarEnd,
barEnd,
sb.String())
if (s.currentPercent == 100 && c.showElapsedTimeOnFinish) || c.elapsedTime {
str = fmt.Sprintf("%s [%s]", str, leftBrac)
Expand All @@ -1157,11 +1203,11 @@ func renderProgressBar(c config, s *state) (int, error) {
if s.currentPercent == 100 {
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
s.currentPercent,
c.theme.BarStart,
barStart,
saucer,
saucerHead,
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
c.theme.BarEnd,
barEnd,
sb.String())

if c.showElapsedTimeOnFinish {
Expand All @@ -1176,11 +1222,11 @@ func renderProgressBar(c config, s *state) (int, error) {
} else {
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s [%s:%s]",
s.currentPercent,
c.theme.BarStart,
barStart,
saucer,
saucerHead,
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
c.theme.BarEnd,
barEnd,
sb.String(),
leftBrac,
rightBrac)
Expand Down
57 changes: 55 additions & 2 deletions progressbar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,16 +452,69 @@ func TestOptionSetTheme(t *testing.T) {
buf := strings.Builder{}
bar := NewOptions(
10,
OptionSetTheme(Theme{Saucer: "#", SaucerPadding: "-", BarStart: ">", BarEnd: "<"}),
OptionSetTheme(
Theme{Saucer: "#", SaucerPadding: "-",
BarStart: ">", BarEnd: "<"}),
OptionSetWidth(10),
OptionSetWriter(&buf),
)
bar.RenderBlank()
result := strings.TrimSpace(buf.String())
expect := "0% >----------<"
if strings.Index(result, expect) == -1 {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
buf.Reset()

bar.Add(5)
result = strings.TrimSpace(buf.String())
expect = "50% >#####-----< [0s:0s]"
if result != expect {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
buf.Reset()

bar.Finish()
result = strings.TrimSpace(buf.String())
expect = "100% >##########<"
if strings.Index(result, expect) == -1 {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
}

func TestOptionSetThemeFilled(t *testing.T) {
buf := strings.Builder{}
bar := NewOptions(
10,
OptionSetTheme(
Theme{Saucer: "#", SaucerPadding: "-",
BarStart: ">", BarStartFilled: "]",
BarEnd: "<", BarEndFilled: "["}),
OptionSetWidth(10),
OptionSetWriter(&buf),
)
bar.RenderBlank()
result := strings.TrimSpace(buf.String())
expect := "50% >#####-----< [0s:0s]"
expect := "0% >----------<"
if strings.Index(result, expect) == -1 {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
buf.Reset()

bar.Add(5)
result = strings.TrimSpace(buf.String())
expect = "50% ]#####-----< [0s:0s]"
if result != expect {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
buf.Reset()

bar.Finish()
result = strings.TrimSpace(buf.String())
expect = "100% ]##########["
if strings.Index(result, expect) == -1 {
t.Errorf("Render miss-match\nResult: '%s'\nExpect: '%s'\n%+v", result, expect, bar)
}
}

// TestOptionSetPredictTime ensures that when predict time is turned off, the progress
Expand Down
Loading