-
Notifications
You must be signed in to change notification settings - Fork 363
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
460 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package progress | ||
|
||
import ( | ||
"math" | ||
"time" | ||
) | ||
|
||
type barInfo struct { | ||
iter uint | ||
title string | ||
limit uint | ||
incrementTs []time.Time | ||
} | ||
|
||
func newBarInfo(title string, limit uint) *barInfo { | ||
info := &barInfo{ | ||
title: title, | ||
limit: limit, | ||
incrementTs: make([]time.Time, 0, limit), | ||
} | ||
info.incrementTs = append(info.incrementTs, time.Now()) | ||
return info | ||
} | ||
|
||
func (self *barInfo) Update(progressAmount uint) { | ||
self.iter += progressAmount | ||
|
||
now := time.Now() | ||
for i := uint(0); i < progressAmount; i++ { | ||
self.incrementTs = append(self.incrementTs, now) | ||
} | ||
} | ||
|
||
func (self *barInfo) Elapsed() time.Duration { | ||
return time.Now().Sub(self.incrementTs[0]).Truncate(time.Second) | ||
} | ||
|
||
func (self *barInfo) Pct() int { | ||
pct := math.Round(safeDivide(float64(self.iter), float64(self.limit)) * 100) | ||
return int(pct) | ||
} | ||
|
||
func (self *barInfo) Avg() time.Duration { | ||
if len(self.incrementTs) < 2 { | ||
return time.Now().Sub(self.incrementTs[0]) | ||
} | ||
var sum time.Duration | ||
for i := 1; i < len(self.incrementTs); i++ { | ||
sum += self.incrementTs[i].Sub(self.incrementTs[i-1]) | ||
} | ||
return (sum / time.Duration(self.iter)) | ||
} | ||
|
||
func (self *barInfo) Eta() time.Duration { | ||
if self.iter >= self.limit { | ||
return 0 | ||
} | ||
avg := self.Avg() | ||
if avg == 0 { | ||
return 0 | ||
} | ||
|
||
return time.Duration(self.limit-self.iter) * avg | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Package progress provides a simple progress indicator | ||
// for tracking the progress for input provided via stdin. | ||
// | ||
// It shows a progress bar when the limit is known and some simple stats when not. | ||
// | ||
// ------------------------------------ | ||
// #!/bin/bash | ||
// | ||
// urls=( | ||
// | ||
// "http://example.com/file1.txt" | ||
// "http://example.com/file2.txt" | ||
// "http://example.com/file3.txt" | ||
// | ||
// ) | ||
// | ||
// for url in "${urls[@]}"; do | ||
// | ||
// wget -q -nc "$url" | ||
// echo "Downloaded: $url" | ||
// | ||
// done | gum progress --show-output --limit ${#urls[@]} | ||
// ------------------------------------ | ||
package progress | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"os" | ||
|
||
tea "github.com/charmbracelet/bubbletea" | ||
"github.com/mattn/go-isatty" | ||
) | ||
|
||
func (o Options) GetFormatString() string { | ||
if o.Format != "" { | ||
return o.Format | ||
} | ||
|
||
switch { | ||
case o.Limit == 0 && o.Title == "": | ||
return "[Elapsed ~ {Elapsed}] Iter {Iter}" | ||
case o.Limit == 0 && o.Title != "": | ||
return "[Elapsed ~ {Elapsed}] Iter {Iter} ~ {Title}" | ||
case o.Limit > 0 && o.Title == "": | ||
return "{Bar} {Pct}" | ||
case o.Limit > 0 && o.Title != "": | ||
return "{Title} ~ {Bar} {Pct}" | ||
default: | ||
return "{Iter}" | ||
} | ||
} | ||
|
||
func (o Options) Run() error { | ||
m := &model{ | ||
reader: bufio.NewReader(os.Stdin), | ||
output: o.ShowOutput, | ||
isTTY: isatty.IsTerminal(os.Stdout.Fd()), | ||
progressIndicator: o.ProgressIndicator, | ||
hideProgressIndicator: o.HideProgressIndicator, | ||
|
||
bfmt: newBarFormatter(o.GetFormatString(), o.ProgressColor), | ||
binfo: newBarInfo(o.TitleStyle.ToLipgloss().Render(o.Title), o.Limit), | ||
} | ||
p := tea.NewProgram(m, tea.WithOutput(os.Stderr)) | ||
if _, err := p.Run(); err != nil { | ||
return fmt.Errorf("failed to run progress: %w", err) | ||
} | ||
|
||
return m.err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package progress | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
"time" | ||
|
||
"github.com/charmbracelet/bubbles/progress" | ||
"github.com/charmbracelet/lipgloss" | ||
) | ||
|
||
type barFormatter struct { | ||
pbar progress.Model | ||
numBars int | ||
tplstr string | ||
} | ||
|
||
var barPlaceholderRe = regexp.MustCompile(`{\s*Bar\s*}`) | ||
var nonBarPlaceholderRe = regexp.MustCompile(`{\s*(Title|Elapsed|Iter|Avg|Pct|Eta|Remaining|Limit)\s*}`) | ||
|
||
func newBarFormatter(tplstr string, barColor string) *barFormatter { | ||
var bar progress.Model | ||
if barColor != "" { | ||
bar = progress.New(progress.WithoutPercentage(), progress.WithSolidFill(barColor)) | ||
} else { | ||
bar = progress.New(progress.WithoutPercentage()) | ||
} | ||
barfmt := &barFormatter{ | ||
pbar: bar, | ||
tplstr: tplstr, | ||
numBars: len(barPlaceholderRe.FindAllString(tplstr, -1)), | ||
} | ||
return barfmt | ||
} | ||
|
||
func (self *barFormatter) Render(info *barInfo, maxWidth int) string { | ||
rendered := nonBarPlaceholderRe.ReplaceAllStringFunc(self.tplstr, func(s string) string { | ||
switch strings.TrimSpace(s[1 : len(s)-1]) { | ||
case "Title": | ||
return info.title | ||
case "Iter": | ||
return fmt.Sprint(info.iter) | ||
case "Limit": | ||
if info.limit == 0 { | ||
return s | ||
} | ||
return fmt.Sprint(info.limit) | ||
case "Elapsed": | ||
return info.Elapsed().String() | ||
case "Pct": | ||
if info.limit == 0 { | ||
return s | ||
} | ||
return fmt.Sprintf("%d%%", info.Pct()) | ||
case "Avg": | ||
return info.Avg().Round(time.Second).String() | ||
case "Remaining": | ||
if info.limit == 0 { | ||
return s | ||
} | ||
return info.Eta().Round(time.Second).String() | ||
case "Eta": | ||
if info.limit == 0 { | ||
return s | ||
} | ||
return time.Now().Add(info.Eta()).Format(time.TimeOnly) | ||
default: | ||
return "" | ||
} | ||
}) | ||
|
||
if info.limit > 0 && self.numBars > 0 { | ||
self.pbar.Width = max(0, (maxWidth-lipgloss.Width(rendered))/int(self.numBars)) | ||
bar := self.pbar.ViewAs(safeDivide(float64(info.iter), float64(info.limit))) | ||
rendered = barPlaceholderRe.ReplaceAllLiteralString(rendered, bar) | ||
} | ||
return rendered | ||
} | ||
|
||
func min(a, b uint) uint { | ||
if a < b { | ||
return a | ||
} | ||
return b | ||
} | ||
|
||
func minI(a, b int) int { | ||
if a < b { | ||
return a | ||
} | ||
return b | ||
} | ||
|
||
func max(a, b int) int { | ||
if a > b { | ||
return a | ||
} | ||
return b | ||
} | ||
|
||
func safeDivide(a, b float64) float64 { | ||
if b == 0 { | ||
return 0 | ||
} | ||
return a / b | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package progress | ||
|
||
import ( | ||
"github.com/charmbracelet/gum/style" | ||
) | ||
|
||
type Options struct { | ||
Title string `help:"Text to display to user while spinning" env:"GUM_PROGRESS_TITLE"` | ||
TitleStyle style.Styles `embed:"" prefix:"title." envprefix:"GUM_PROGRESS_TITLE_"` | ||
Format string `short:"f" help:"What format to use for rendering the bar. Choose from: {Iter}, {Elapsed}, {Title} and {Avg} or see --limit for more options. Unknown options remain untouched." envprefix:"GUM_PROGRESS_FORMAT"` | ||
ProgressColor string `help:"Set the color for the progress" envprefix:"GUM_PROGRESS_PROGRESS_COLOR"` | ||
|
||
ProgressIndicator string `help:"What indicator to use for counting progress" default:"\n" env:"GUM_PROGRESS_PROGRESS_INDICATOR"` | ||
HideProgressIndicator bool `help:"Don't show the --progress-indicator in the output. Only makes sense in combination with --show-output" default:"false"` | ||
ShowOutput bool `short:"o" help:"Print what gum reads" default:"false"` | ||
|
||
Limit uint `short:"l" help:"Species how many items there are (enables {Bar}, {Limit}, {Remaining}, {Eta} and {Pct} to be used in --format)"` | ||
} |
Oops, something went wrong.