Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08b900250f |
+1
-1
@@ -17,11 +17,11 @@ import (
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/jmorganca/ollama/api"
|
||||
"github.com/jmorganca/ollama/format"
|
||||
"github.com/jmorganca/ollama/progressbar"
|
||||
"github.com/jmorganca/ollama/server"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/jmorganca/ollama/progressbar"
|
||||
)
|
||||
|
||||
type Spinner struct {
|
||||
|
||||
@@ -5,15 +5,13 @@ go 1.20
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/mattn/go-runewidth v0.0.14
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
)
|
||||
require github.com/rivo/uniseg v0.2.0 // indirect
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0
|
||||
@@ -35,7 +33,6 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.13.1
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
|
||||
@@ -42,13 +42,11 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
@@ -70,8 +68,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
|
||||
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -100,11 +96,9 @@ golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Zack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,121 @@
|
||||
# progressbar
|
||||
|
||||
[](https://github.com/schollz/progressbar/actions/workflows/ci.yml)
|
||||
[](https://goreportcard.com/report/github.com/schollz/progressbar)
|
||||
[](https://gocover.io/github.com/schollz/progressbar)
|
||||
[](https://godoc.org/github.com/schollz/progressbar/v3)
|
||||
|
||||
A very simple thread-safe progress bar which should work on every OS without problems. I needed a progressbar for [croc](https://github.com/schollz/croc) and everything I tried had problems, so I made another one. In order to be OS agnostic I do not plan to support [multi-line outputs](https://github.com/schollz/progressbar/issues/6).
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
go get -u github.com/schollz/progressbar/v3
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic usage
|
||||
|
||||
```golang
|
||||
bar := progressbar.Default(100)
|
||||
for i := 0; i < 100; i++ {
|
||||
bar.Add(1)
|
||||
time.Sleep(40 * time.Millisecond)
|
||||
}
|
||||
```
|
||||
|
||||
which looks like:
|
||||
|
||||

|
||||
|
||||
|
||||
### I/O operations
|
||||
|
||||
The `progressbar` implements an `io.Writer` so it can automatically detect the number of bytes written to a stream, so you can use it as a progressbar for an `io.Reader`.
|
||||
|
||||
```golang
|
||||
req, _ := http.NewRequest("GET", "https://dl.google.com/go/go1.14.2.src.tar.gz", nil)
|
||||
resp, _ := http.DefaultClient.Do(req)
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, _ := os.OpenFile("go1.14.2.src.tar.gz", os.O_CREATE|os.O_WRONLY, 0644)
|
||||
defer f.Close()
|
||||
|
||||
bar := progressbar.DefaultBytes(
|
||||
resp.ContentLength,
|
||||
"downloading",
|
||||
)
|
||||
io.Copy(io.MultiWriter(f, bar), resp.Body)
|
||||
```
|
||||
|
||||
which looks like:
|
||||
|
||||

|
||||
|
||||
|
||||
### Progress bar with unknown length
|
||||
|
||||
A progressbar with unknown length is a spinner. Any bar with -1 length will automatically convert it to a spinner with a customizable spinner type. For example, the above code can be run and set the `resp.ContentLength` to `-1`.
|
||||
|
||||
which looks like:
|
||||
|
||||

|
||||
|
||||
|
||||
### Customization
|
||||
|
||||
There is a lot of customization that you can do - change the writer, the color, the width, description, theme, etc. See [all the options](https://pkg.go.dev/github.com/schollz/progressbar/v3?tab=doc#Option).
|
||||
|
||||
```golang
|
||||
bar := progressbar.NewOptions(1000,
|
||||
progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
|
||||
progressbar.OptionEnableColorCodes(true),
|
||||
progressbar.OptionShowBytes(true),
|
||||
progressbar.OptionSetWidth(15),
|
||||
progressbar.OptionSetDescription("[cyan][1/3][reset] Writing moshable file..."),
|
||||
progressbar.OptionSetTheme(progressbar.Theme{
|
||||
Saucer: "[green]=[reset]",
|
||||
SaucerHead: "[green]>[reset]",
|
||||
SaucerPadding: " ",
|
||||
BarStart: "[",
|
||||
BarEnd: "]",
|
||||
}))
|
||||
for i := 0; i < 1000; i++ {
|
||||
bar.Add(1)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
```
|
||||
|
||||
which looks like:
|
||||
|
||||

|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests are welcome. Feel free to...
|
||||
|
||||
- Revise documentation
|
||||
- Add new features
|
||||
- Fix bugs
|
||||
- Suggest improvements
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks [@Dynom](https://github.com/dynom) for massive improvements in version 2.0!
|
||||
|
||||
Thanks [@CrushedPixel](https://github.com/CrushedPixel) for adding descriptions and color code support!
|
||||
|
||||
Thanks [@MrMe42](https://github.com/MrMe42) for adding some minor features!
|
||||
|
||||
Thanks [@tehstun](https://github.com/tehstun) for some great PRs!
|
||||
|
||||
Thanks [@Benzammour](https://github.com/Benzammour) and [@haseth](https://github.com/haseth) for helping create v3!
|
||||
|
||||
Thanks [@briandowns](https://github.com/briandowns) for compiling the list of spinners.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,1098 @@
|
||||
package progressbar
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/mitchellh/colorstring"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// ProgressBar is a thread-safe, simple
|
||||
// progress bar
|
||||
type ProgressBar struct {
|
||||
state state
|
||||
config config
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// State is the basic properties of the bar
|
||||
type State struct {
|
||||
CurrentPercent float64
|
||||
CurrentBytes float64
|
||||
SecondsSince float64
|
||||
SecondsLeft float64
|
||||
KBsPerSecond float64
|
||||
}
|
||||
|
||||
type state struct {
|
||||
currentNum int64
|
||||
currentPercent int
|
||||
lastPercent int
|
||||
currentSaucerSize int
|
||||
isAltSaucerHead bool
|
||||
|
||||
lastShown time.Time
|
||||
startTime time.Time
|
||||
|
||||
counterTime time.Time
|
||||
counterNumSinceLast int64
|
||||
counterLastTenRates []float64
|
||||
|
||||
maxLineWidth int
|
||||
currentBytes float64
|
||||
finished bool
|
||||
exit bool // Progress bar exit halfway
|
||||
|
||||
rendered string
|
||||
}
|
||||
|
||||
type config struct {
|
||||
max int64 // max number of the counter
|
||||
maxHumanized string
|
||||
maxHumanizedSuffix string
|
||||
width int
|
||||
writer io.Writer
|
||||
theme Theme
|
||||
renderWithBlankState bool
|
||||
description string
|
||||
iterationString string
|
||||
ignoreLength bool // ignoreLength if max bytes not known
|
||||
|
||||
// whether the output is expected to contain color codes
|
||||
colorCodes bool
|
||||
|
||||
// show rate of change in kB/sec or MB/sec
|
||||
showBytes bool
|
||||
// show the iterations per second
|
||||
showIterationsPerSecond bool
|
||||
showIterationsCount bool
|
||||
|
||||
// whether the progress bar should show elapsed time.
|
||||
// always enabled if predictTime is true.
|
||||
elapsedTime bool
|
||||
|
||||
showElapsedTimeOnFinish bool
|
||||
|
||||
// whether the progress bar should attempt to predict the finishing
|
||||
// time of the progress based on the start time and the average
|
||||
// number of seconds between increments.
|
||||
predictTime bool
|
||||
|
||||
// minimum time to wait in between updates
|
||||
throttleDuration time.Duration
|
||||
|
||||
// clear bar once finished
|
||||
clearOnFinish bool
|
||||
|
||||
// spinnerType should be a number between 0-75
|
||||
spinnerType int
|
||||
|
||||
// spinnerTypeOptionUsed remembers if the spinnerType was changed manually
|
||||
spinnerTypeOptionUsed bool
|
||||
|
||||
// spinner represents the spinner as a slice of string
|
||||
spinner []string
|
||||
|
||||
// fullWidth specifies whether to measure and set the bar to a specific width
|
||||
fullWidth bool
|
||||
|
||||
// invisible doesn't render the bar at all, useful for debugging
|
||||
invisible bool
|
||||
|
||||
onCompletion func()
|
||||
|
||||
// whether the render function should make use of ANSI codes to reduce console I/O
|
||||
useANSICodes bool
|
||||
|
||||
// showDescriptionAtLineEnd specifies whether description should be written at line end instead of line start
|
||||
showDescriptionAtLineEnd bool
|
||||
}
|
||||
|
||||
// Theme defines the elements of the bar
|
||||
type Theme struct {
|
||||
Saucer string
|
||||
AltSaucerHead string
|
||||
SaucerHead string
|
||||
SaucerPadding string
|
||||
BarStart string
|
||||
BarEnd string
|
||||
}
|
||||
|
||||
// Option is the type all options need to adhere to
|
||||
type Option func(p *ProgressBar)
|
||||
|
||||
// OptionSetWidth sets the width of the bar
|
||||
func OptionSetWidth(s int) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.width = s
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSpinnerType sets the type of spinner used for indeterminate bars
|
||||
func OptionSpinnerType(spinnerType int) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.spinnerTypeOptionUsed = true
|
||||
p.config.spinnerType = spinnerType
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSpinnerCustom sets the spinner used for indeterminate bars to the passed
|
||||
// slice of string
|
||||
func OptionSpinnerCustom(spinner []string) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.spinner = spinner
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetTheme sets the elements the bar is constructed of
|
||||
func OptionSetTheme(t Theme) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.theme = t
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetVisibility sets the visibility
|
||||
func OptionSetVisibility(visibility bool) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.invisible = !visibility
|
||||
}
|
||||
}
|
||||
|
||||
// OptionFullWidth sets the bar to be full width
|
||||
func OptionFullWidth() Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.fullWidth = true
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetWriter sets the output writer (defaults to os.StdOut)
|
||||
func OptionSetWriter(w io.Writer) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.writer = w
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetRenderBlankState sets whether or not to render a 0% bar on construction
|
||||
func OptionSetRenderBlankState(r bool) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.renderWithBlankState = r
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetDescription sets the description of the bar to render in front of it
|
||||
func OptionSetDescription(description string) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.description = description
|
||||
}
|
||||
}
|
||||
|
||||
// OptionEnableColorCodes enables or disables support for color codes
|
||||
// using mitchellh/colorstring
|
||||
func OptionEnableColorCodes(colorCodes bool) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.colorCodes = colorCodes
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetElapsedTime will enable elapsed time. Always enabled if OptionSetPredictTime is true.
|
||||
func OptionSetElapsedTime(elapsedTime bool) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.elapsedTime = elapsedTime
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetPredictTime will also attempt to predict the time remaining.
|
||||
func OptionSetPredictTime(predictTime bool) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.predictTime = predictTime
|
||||
}
|
||||
}
|
||||
|
||||
// OptionShowCount will also print current count out of total
|
||||
func OptionShowCount() Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.showIterationsCount = true
|
||||
}
|
||||
}
|
||||
|
||||
// OptionShowIts will also print the iterations/second
|
||||
func OptionShowIts() Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.showIterationsPerSecond = true
|
||||
}
|
||||
}
|
||||
|
||||
// OptionShowElapsedOnFinish will keep the display of elapsed time on finish
|
||||
func OptionShowElapsedTimeOnFinish() Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.showElapsedTimeOnFinish = true
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetItsString sets what's displayed for iterations a second. The default is "it" which would display: "it/s"
|
||||
func OptionSetItsString(iterationString string) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.iterationString = iterationString
|
||||
}
|
||||
}
|
||||
|
||||
// OptionThrottle will wait the specified duration before updating again. The default
|
||||
// duration is 0 seconds.
|
||||
func OptionThrottle(duration time.Duration) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.throttleDuration = duration
|
||||
}
|
||||
}
|
||||
|
||||
// OptionClearOnFinish will clear the bar once its finished
|
||||
func OptionClearOnFinish() Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.clearOnFinish = true
|
||||
}
|
||||
}
|
||||
|
||||
// OptionOnCompletion will invoke cmpl function once its finished
|
||||
func OptionOnCompletion(cmpl func()) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.onCompletion = cmpl
|
||||
}
|
||||
}
|
||||
|
||||
// OptionShowBytes will update the progress bar
|
||||
// configuration settings to display/hide kBytes/Sec
|
||||
func OptionShowBytes(val bool) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.showBytes = val
|
||||
}
|
||||
}
|
||||
|
||||
// OptionUseANSICodes will use more optimized terminal i/o.
|
||||
//
|
||||
// Only useful in environments with support for ANSI escape sequences.
|
||||
func OptionUseANSICodes(val bool) Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.useANSICodes = val
|
||||
}
|
||||
}
|
||||
|
||||
// OptionShowDescriptionAtLineEnd defines whether description should be written at line end instead of line start
|
||||
func OptionShowDescriptionAtLineEnd() Option {
|
||||
return func(p *ProgressBar) {
|
||||
p.config.showDescriptionAtLineEnd = true
|
||||
}
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
// NewOptions64 constructs a new instance of ProgressBar, with any options you specify
|
||||
func NewOptions64(max int64, options ...Option) *ProgressBar {
|
||||
b := ProgressBar{
|
||||
state: getBasicState(),
|
||||
config: config{
|
||||
writer: os.Stdout,
|
||||
theme: defaultTheme,
|
||||
iterationString: "it",
|
||||
width: 40,
|
||||
max: max,
|
||||
throttleDuration: 0 * time.Nanosecond,
|
||||
elapsedTime: true,
|
||||
predictTime: true,
|
||||
spinnerType: 9,
|
||||
invisible: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
o(&b)
|
||||
}
|
||||
|
||||
if b.config.spinnerType < 0 || b.config.spinnerType > 75 {
|
||||
panic("invalid spinner type, must be between 0 and 75")
|
||||
}
|
||||
|
||||
// ignoreLength if max bytes not known
|
||||
if b.config.max == -1 {
|
||||
b.config.ignoreLength = true
|
||||
b.config.max = int64(b.config.width)
|
||||
b.config.predictTime = false
|
||||
}
|
||||
|
||||
b.config.maxHumanized, b.config.maxHumanizedSuffix = humanizeBytes(float64(b.config.max))
|
||||
|
||||
if b.config.renderWithBlankState {
|
||||
b.RenderBlank()
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
func getBasicState() state {
|
||||
now := time.Now()
|
||||
return state{
|
||||
startTime: now,
|
||||
lastShown: now,
|
||||
counterTime: now,
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new ProgressBar
|
||||
// with the specified maximum
|
||||
func New(max int) *ProgressBar {
|
||||
return NewOptions(max)
|
||||
}
|
||||
|
||||
// DefaultBytes provides a progressbar to measure byte
|
||||
// throughput with recommended defaults.
|
||||
// Set maxBytes to -1 to use as a spinner.
|
||||
func DefaultBytes(maxBytes int64, description ...string) *ProgressBar {
|
||||
desc := ""
|
||||
if len(description) > 0 {
|
||||
desc = description[0]
|
||||
}
|
||||
return NewOptions64(
|
||||
maxBytes,
|
||||
OptionSetDescription(desc),
|
||||
OptionSetWriter(os.Stderr),
|
||||
OptionShowBytes(true),
|
||||
OptionSetWidth(10),
|
||||
OptionThrottle(65*time.Millisecond),
|
||||
OptionShowCount(),
|
||||
OptionOnCompletion(func() {
|
||||
fmt.Fprint(os.Stderr, "\n")
|
||||
}),
|
||||
OptionSpinnerType(14),
|
||||
OptionFullWidth(),
|
||||
OptionSetRenderBlankState(true),
|
||||
)
|
||||
}
|
||||
|
||||
// DefaultBytesSilent is the same as DefaultBytes, but does not output anywhere.
|
||||
// String() can be used to get the output instead.
|
||||
func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar {
|
||||
// Mostly the same bar as DefaultBytes
|
||||
|
||||
desc := ""
|
||||
if len(description) > 0 {
|
||||
desc = description[0]
|
||||
}
|
||||
return NewOptions64(
|
||||
maxBytes,
|
||||
OptionSetDescription(desc),
|
||||
OptionSetWriter(io.Discard),
|
||||
OptionShowBytes(true),
|
||||
OptionSetWidth(10),
|
||||
OptionThrottle(65*time.Millisecond),
|
||||
OptionShowCount(),
|
||||
OptionSpinnerType(14),
|
||||
OptionFullWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// Default provides a progressbar with recommended defaults.
|
||||
// Set max to -1 to use as a spinner.
|
||||
func Default(max int64, description ...string) *ProgressBar {
|
||||
desc := ""
|
||||
if len(description) > 0 {
|
||||
desc = description[0]
|
||||
}
|
||||
return NewOptions64(
|
||||
max,
|
||||
OptionSetDescription(desc),
|
||||
OptionSetWriter(os.Stderr),
|
||||
OptionSetWidth(10),
|
||||
OptionThrottle(65*time.Millisecond),
|
||||
OptionShowCount(),
|
||||
OptionShowIts(),
|
||||
OptionOnCompletion(func() {
|
||||
fmt.Fprint(os.Stderr, "\n")
|
||||
}),
|
||||
OptionSpinnerType(14),
|
||||
OptionFullWidth(),
|
||||
OptionSetRenderBlankState(true),
|
||||
)
|
||||
}
|
||||
|
||||
// DefaultSilent is the same as Default, but does not output anywhere.
|
||||
// String() can be used to get the output instead.
|
||||
func DefaultSilent(max int64, description ...string) *ProgressBar {
|
||||
// Mostly the same bar as Default
|
||||
|
||||
desc := ""
|
||||
if len(description) > 0 {
|
||||
desc = description[0]
|
||||
}
|
||||
return NewOptions64(
|
||||
max,
|
||||
OptionSetDescription(desc),
|
||||
OptionSetWriter(io.Discard),
|
||||
OptionSetWidth(10),
|
||||
OptionThrottle(65*time.Millisecond),
|
||||
OptionShowCount(),
|
||||
OptionShowIts(),
|
||||
OptionSpinnerType(14),
|
||||
OptionFullWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// String returns the current rendered version of the progress bar.
|
||||
// It will never return an empty string while the progress bar is running.
|
||||
func (p *ProgressBar) String() string {
|
||||
return p.state.rendered
|
||||
}
|
||||
|
||||
// RenderBlank renders the current bar state, you can use this to render a 0% state
|
||||
func (p *ProgressBar) RenderBlank() error {
|
||||
if p.config.invisible {
|
||||
return nil
|
||||
}
|
||||
if p.state.currentNum == 0 {
|
||||
p.state.lastShown = time.Time{}
|
||||
}
|
||||
return p.render()
|
||||
}
|
||||
|
||||
// Reset will reset the clock that is used
|
||||
// to calculate current time and the time left.
|
||||
func (p *ProgressBar) Reset() {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.state = getBasicState()
|
||||
}
|
||||
|
||||
// Finish will fill the bar to full
|
||||
func (p *ProgressBar) Finish() error {
|
||||
p.lock.Lock()
|
||||
p.state.currentNum = p.config.max
|
||||
p.lock.Unlock()
|
||||
return p.Add(0)
|
||||
}
|
||||
|
||||
// Exit will exit the bar to keep current state
|
||||
func (p *ProgressBar) Exit() error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.state.exit = true
|
||||
if p.config.onCompletion != nil {
|
||||
p.config.onCompletion()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add will add the specified amount to the progressbar
|
||||
func (p *ProgressBar) Add(num int) error {
|
||||
return p.Add64(int64(num))
|
||||
}
|
||||
|
||||
// Set will set the bar to a current number
|
||||
func (p *ProgressBar) Set(num int) error {
|
||||
return p.Set64(int64(num))
|
||||
}
|
||||
|
||||
// Set64 will set the bar to a current number
|
||||
func (p *ProgressBar) Set64(num int64) error {
|
||||
p.lock.Lock()
|
||||
toAdd := num - int64(p.state.currentBytes)
|
||||
p.lock.Unlock()
|
||||
return p.Add64(toAdd)
|
||||
}
|
||||
|
||||
// Add64 will add the specified amount to the progressbar
|
||||
func (p *ProgressBar) Add64(num int64) error {
|
||||
if p.config.invisible {
|
||||
return nil
|
||||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
if p.state.exit {
|
||||
return nil
|
||||
}
|
||||
|
||||
// error out since OptionSpinnerCustom will always override a manually set spinnerType
|
||||
if p.config.spinnerTypeOptionUsed && len(p.config.spinner) > 0 {
|
||||
return errors.New("OptionSpinnerType and OptionSpinnerCustom cannot be used together")
|
||||
}
|
||||
|
||||
if p.config.max == 0 {
|
||||
return errors.New("max must be greater than 0")
|
||||
}
|
||||
|
||||
if p.state.currentNum < p.config.max {
|
||||
if p.config.ignoreLength {
|
||||
p.state.currentNum = (p.state.currentNum + num) % p.config.max
|
||||
} else {
|
||||
p.state.currentNum += num
|
||||
}
|
||||
}
|
||||
|
||||
p.state.currentBytes += float64(num)
|
||||
|
||||
// reset the countdown timer every second to take rolling average
|
||||
p.state.counterNumSinceLast += num
|
||||
if time.Since(p.state.counterTime).Seconds() > 0.5 {
|
||||
p.state.counterLastTenRates = append(p.state.counterLastTenRates, float64(p.state.counterNumSinceLast)/time.Since(p.state.counterTime).Seconds())
|
||||
if len(p.state.counterLastTenRates) > 10 {
|
||||
p.state.counterLastTenRates = p.state.counterLastTenRates[1:]
|
||||
}
|
||||
p.state.counterTime = time.Now()
|
||||
p.state.counterNumSinceLast = 0
|
||||
}
|
||||
|
||||
percent := float64(p.state.currentNum) / float64(p.config.max)
|
||||
p.state.currentSaucerSize = int(percent * float64(p.config.width))
|
||||
p.state.currentPercent = int(percent * 100)
|
||||
updateBar := p.state.currentPercent != p.state.lastPercent && p.state.currentPercent > 0
|
||||
|
||||
p.state.lastPercent = p.state.currentPercent
|
||||
if p.state.currentNum > p.config.max {
|
||||
return errors.New("current number exceeds max")
|
||||
}
|
||||
|
||||
// always update if show bytes/second or its/second
|
||||
if updateBar || p.config.showIterationsPerSecond || p.config.showIterationsCount {
|
||||
return p.render()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear erases the progress bar from the current line
|
||||
func (p *ProgressBar) Clear() error {
|
||||
return clearProgressBar(p.config, p.state)
|
||||
}
|
||||
|
||||
// Describe will change the description shown before the progress, which
|
||||
// can be changed on the fly (as for a slow running process).
|
||||
func (p *ProgressBar) Describe(description string) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
p.config.description = description
|
||||
if p.config.invisible {
|
||||
return
|
||||
}
|
||||
p.render()
|
||||
}
|
||||
|
||||
// New64 returns a new ProgressBar
|
||||
// with the specified maximum
|
||||
func New64(max int64) *ProgressBar {
|
||||
return NewOptions64(max)
|
||||
}
|
||||
|
||||
// GetMax returns the max of a bar
|
||||
func (p *ProgressBar) GetMax() int {
|
||||
return int(p.config.max)
|
||||
}
|
||||
|
||||
// GetMax64 returns the current max
|
||||
func (p *ProgressBar) GetMax64() int64 {
|
||||
return p.config.max
|
||||
}
|
||||
|
||||
// ChangeMax takes in a int
|
||||
// and changes the max value
|
||||
// of the progress bar
|
||||
func (p *ProgressBar) ChangeMax(newMax int) {
|
||||
p.ChangeMax64(int64(newMax))
|
||||
}
|
||||
|
||||
// ChangeMax64 is basically
|
||||
// the same as ChangeMax,
|
||||
// but takes in a int64
|
||||
// to avoid casting
|
||||
func (p *ProgressBar) ChangeMax64(newMax int64) {
|
||||
p.config.max = newMax
|
||||
|
||||
if p.config.showBytes {
|
||||
p.config.maxHumanized, p.config.maxHumanizedSuffix = humanizeBytes(float64(p.config.max))
|
||||
}
|
||||
|
||||
p.Add(0) // re-render
|
||||
}
|
||||
|
||||
// IsFinished returns true if progress bar is completed
|
||||
func (p *ProgressBar) IsFinished() bool {
|
||||
return p.state.finished
|
||||
}
|
||||
|
||||
// render renders the progress bar, updating the maximum
|
||||
// rendered line width. this function is not thread-safe,
|
||||
// so it must be called with an acquired lock.
|
||||
func (p *ProgressBar) render() error {
|
||||
// make sure that the rendering is not happening too quickly
|
||||
// but always show if the currentNum reaches the max
|
||||
if time.Since(p.state.lastShown).Nanoseconds() < p.config.throttleDuration.Nanoseconds() &&
|
||||
p.state.currentNum < p.config.max {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !p.config.useANSICodes {
|
||||
// first, clear the existing progress bar
|
||||
err := clearProgressBar(p.config, p.state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// check if the progress bar is finished
|
||||
if !p.state.finished && p.state.currentNum >= p.config.max {
|
||||
p.state.finished = true
|
||||
if !p.config.clearOnFinish {
|
||||
renderProgressBar(p.config, &p.state)
|
||||
}
|
||||
if p.config.onCompletion != nil {
|
||||
p.config.onCompletion()
|
||||
}
|
||||
}
|
||||
if p.state.finished {
|
||||
// when using ANSI codes we don't pre-clean the current line
|
||||
if p.config.useANSICodes && p.config.clearOnFinish {
|
||||
err := clearProgressBar(p.config, p.state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// then, re-render the current progress bar
|
||||
w, err := renderProgressBar(p.config, &p.state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if w > p.state.maxLineWidth {
|
||||
p.state.maxLineWidth = w
|
||||
}
|
||||
|
||||
p.state.lastShown = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// State returns the current state
|
||||
func (p *ProgressBar) State() State {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
s := State{}
|
||||
s.CurrentPercent = float64(p.state.currentNum) / float64(p.config.max)
|
||||
s.CurrentBytes = p.state.currentBytes
|
||||
s.SecondsSince = time.Since(p.state.startTime).Seconds()
|
||||
if p.state.currentNum > 0 {
|
||||
s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * (float64(p.config.max) - float64(p.state.currentNum))
|
||||
}
|
||||
s.KBsPerSecond = float64(p.state.currentBytes) / 1000.0 / s.SecondsSince
|
||||
return s
|
||||
}
|
||||
|
||||
// regex matching ansi escape codes
|
||||
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
|
||||
|
||||
func getStringWidth(c config, str string, colorize bool) int {
|
||||
if c.colorCodes {
|
||||
// convert any color codes in the progress bar into the respective ANSI codes
|
||||
str = colorstring.Color(str)
|
||||
}
|
||||
|
||||
// the width of the string, if printed to the console
|
||||
// does not include the carriage return character
|
||||
cleanString := strings.Replace(str, "\r", "", -1)
|
||||
|
||||
if c.colorCodes {
|
||||
// the ANSI codes for the colors do not take up space in the console output,
|
||||
// so they do not count towards the output string width
|
||||
cleanString = ansiRegex.ReplaceAllString(cleanString, "")
|
||||
}
|
||||
|
||||
// get the amount of runes in the string instead of the
|
||||
// character count of the string, as some runes span multiple characters.
|
||||
// see https://stackoverflow.com/a/12668840/2733724
|
||||
stringWidth := runewidth.StringWidth(cleanString)
|
||||
return stringWidth
|
||||
}
|
||||
|
||||
func renderProgressBar(c config, s *state) (int, error) {
|
||||
var sb strings.Builder
|
||||
|
||||
averageRate := average(s.counterLastTenRates)
|
||||
if len(s.counterLastTenRates) == 0 || s.finished {
|
||||
// if no average samples, or if finished,
|
||||
// then average rate should be the total rate
|
||||
if t := time.Since(s.startTime).Seconds(); t > 0 {
|
||||
averageRate = s.currentBytes / t
|
||||
} else {
|
||||
averageRate = 0
|
||||
}
|
||||
}
|
||||
|
||||
// show iteration count in "current/total" iterations format
|
||||
if c.showIterationsCount {
|
||||
if sb.Len() == 0 {
|
||||
sb.WriteString("(")
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
if !c.ignoreLength {
|
||||
if c.showBytes {
|
||||
currentHumanize, currentSuffix := humanizeBytes(s.currentBytes)
|
||||
if currentSuffix == c.maxHumanizedSuffix {
|
||||
sb.WriteString(fmt.Sprintf("%s/%s%s",
|
||||
currentHumanize, c.maxHumanized, c.maxHumanizedSuffix))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%s%s/%s%s",
|
||||
currentHumanize, currentSuffix, c.maxHumanized, c.maxHumanizedSuffix))
|
||||
}
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%.0f/%d", s.currentBytes, c.max))
|
||||
}
|
||||
} else {
|
||||
if c.showBytes {
|
||||
currentHumanize, currentSuffix := humanizeBytes(s.currentBytes)
|
||||
sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%.0f/%s", s.currentBytes, "-"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// show rolling average rate
|
||||
if c.showBytes && averageRate > 0 && !math.IsInf(averageRate, 1) {
|
||||
if sb.Len() == 0 {
|
||||
sb.WriteString("(")
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
currentHumanize, currentSuffix := humanizeBytes(averageRate)
|
||||
sb.WriteString(fmt.Sprintf("%s%s/s", currentHumanize, currentSuffix))
|
||||
}
|
||||
|
||||
// show iterations rate
|
||||
if c.showIterationsPerSecond {
|
||||
if sb.Len() == 0 {
|
||||
sb.WriteString("(")
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
if averageRate > 1 {
|
||||
sb.WriteString(fmt.Sprintf("%0.0f %s/s", averageRate, c.iterationString))
|
||||
} else if averageRate*60 > 1 {
|
||||
sb.WriteString(fmt.Sprintf("%0.0f %s/min", 60*averageRate, c.iterationString))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%0.0f %s/hr", 3600*averageRate, c.iterationString))
|
||||
}
|
||||
}
|
||||
if sb.Len() > 0 {
|
||||
sb.WriteString(")")
|
||||
}
|
||||
|
||||
leftBrac, rightBrac, saucer, saucerHead := "", "", "", ""
|
||||
|
||||
// show time prediction in "current/total" seconds format
|
||||
switch {
|
||||
case c.predictTime:
|
||||
rightBracNum := (time.Duration((1/averageRate)*(float64(c.max)-float64(s.currentNum))) * time.Second)
|
||||
if rightBracNum.Seconds() < 0 {
|
||||
rightBracNum = 0 * time.Second
|
||||
}
|
||||
rightBrac = rightBracNum.String()
|
||||
fallthrough
|
||||
case c.elapsedTime:
|
||||
leftBrac = (time.Duration(time.Since(s.startTime).Seconds()) * time.Second).String()
|
||||
}
|
||||
|
||||
if c.fullWidth && !c.ignoreLength {
|
||||
width, err := termWidth()
|
||||
if err != nil {
|
||||
width = 80
|
||||
}
|
||||
|
||||
amend := 1 // an extra space at eol
|
||||
switch {
|
||||
case leftBrac != "" && rightBrac != "":
|
||||
amend = 4 // space, square brackets and colon
|
||||
case leftBrac != "" && rightBrac == "":
|
||||
amend = 4 // space and square brackets and another space
|
||||
case leftBrac == "" && rightBrac != "":
|
||||
amend = 3 // space and square brackets
|
||||
}
|
||||
if c.showDescriptionAtLineEnd {
|
||||
amend += 1 // another space
|
||||
}
|
||||
|
||||
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 {
|
||||
if c.ignoreLength {
|
||||
saucer = strings.Repeat(c.theme.SaucerPadding, s.currentSaucerSize-1)
|
||||
} else {
|
||||
saucer = strings.Repeat(c.theme.Saucer, s.currentSaucerSize-1)
|
||||
}
|
||||
|
||||
// Check if an alternate saucer head is set for animation
|
||||
if c.theme.AltSaucerHead != "" && s.isAltSaucerHead {
|
||||
saucerHead = c.theme.AltSaucerHead
|
||||
s.isAltSaucerHead = false
|
||||
} else if c.theme.SaucerHead == "" || s.currentSaucerSize == c.width {
|
||||
// use the saucer for the saucer head if it hasn't been set
|
||||
// to preserve backwards compatibility
|
||||
saucerHead = c.theme.Saucer
|
||||
} else {
|
||||
saucerHead = c.theme.SaucerHead
|
||||
s.isAltSaucerHead = true
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Progress Bar format
|
||||
Description % |------ | (kb/s) (iteration count) (iteration rate) (predict time)
|
||||
|
||||
or if showDescriptionAtLineEnd is enabled
|
||||
% |------ | (kb/s) (iteration count) (iteration rate) (predict time) Description
|
||||
*/
|
||||
|
||||
repeatAmount := c.width - s.currentSaucerSize
|
||||
if repeatAmount < 0 {
|
||||
repeatAmount = 0
|
||||
}
|
||||
|
||||
str := ""
|
||||
|
||||
if c.ignoreLength {
|
||||
selectedSpinner := spinners[c.spinnerType]
|
||||
if len(c.spinner) > 0 {
|
||||
selectedSpinner = c.spinner
|
||||
}
|
||||
spinner := selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(selectedSpinner)))))]
|
||||
if c.elapsedTime {
|
||||
if c.showDescriptionAtLineEnd {
|
||||
str = fmt.Sprintf("\r%s %s [%s] %s ",
|
||||
spinner,
|
||||
sb.String(),
|
||||
leftBrac,
|
||||
c.description)
|
||||
} else {
|
||||
str = fmt.Sprintf("\r%s %s %s [%s] ",
|
||||
spinner,
|
||||
c.description,
|
||||
sb.String(),
|
||||
leftBrac)
|
||||
}
|
||||
} else {
|
||||
if c.showDescriptionAtLineEnd {
|
||||
str = fmt.Sprintf("\r%s %s %s ",
|
||||
spinner,
|
||||
sb.String(),
|
||||
c.description)
|
||||
} else {
|
||||
str = fmt.Sprintf("\r%s %s %s ",
|
||||
spinner,
|
||||
c.description,
|
||||
sb.String())
|
||||
}
|
||||
}
|
||||
} else if rightBrac == "" {
|
||||
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
|
||||
s.currentPercent,
|
||||
c.theme.BarStart,
|
||||
saucer,
|
||||
saucerHead,
|
||||
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
|
||||
c.theme.BarEnd,
|
||||
sb.String())
|
||||
|
||||
if s.currentPercent == 100 && c.showElapsedTimeOnFinish {
|
||||
str = fmt.Sprintf("%s [%s]", str, leftBrac)
|
||||
}
|
||||
|
||||
if c.showDescriptionAtLineEnd {
|
||||
str = fmt.Sprintf("\r%s %s ", str, c.description)
|
||||
} else {
|
||||
str = fmt.Sprintf("\r%s%s ", c.description, str)
|
||||
}
|
||||
} else {
|
||||
if s.currentPercent == 100 {
|
||||
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
|
||||
s.currentPercent,
|
||||
c.theme.BarStart,
|
||||
saucer,
|
||||
saucerHead,
|
||||
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
|
||||
c.theme.BarEnd,
|
||||
sb.String())
|
||||
|
||||
if c.showElapsedTimeOnFinish {
|
||||
str = fmt.Sprintf("%s [%s]", str, leftBrac)
|
||||
}
|
||||
|
||||
if c.showDescriptionAtLineEnd {
|
||||
str = fmt.Sprintf("\r%s %s", str, c.description)
|
||||
} else {
|
||||
str = fmt.Sprintf("\r%s%s", c.description, str)
|
||||
}
|
||||
} else {
|
||||
str = fmt.Sprintf("%4d%% %s%s%s%s%s %s [%s:%s]",
|
||||
s.currentPercent,
|
||||
c.theme.BarStart,
|
||||
saucer,
|
||||
saucerHead,
|
||||
strings.Repeat(c.theme.SaucerPadding, repeatAmount),
|
||||
c.theme.BarEnd,
|
||||
sb.String(),
|
||||
leftBrac,
|
||||
rightBrac)
|
||||
|
||||
if c.showDescriptionAtLineEnd {
|
||||
str = fmt.Sprintf("\r%s %s", str, c.description)
|
||||
} else {
|
||||
str = fmt.Sprintf("\r%s%s", c.description, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.colorCodes {
|
||||
// convert any color codes in the progress bar into the respective ANSI codes
|
||||
str = colorstring.Color(str)
|
||||
}
|
||||
|
||||
s.rendered = str
|
||||
|
||||
return getStringWidth(c, str, false), writeString(c, str)
|
||||
}
|
||||
|
||||
func clearProgressBar(c config, s state) error {
|
||||
if s.maxLineWidth == 0 {
|
||||
return nil
|
||||
}
|
||||
if c.useANSICodes {
|
||||
// write the "clear current line" ANSI escape sequence
|
||||
return writeString(c, "\033[2K\r")
|
||||
}
|
||||
// fill the empty content
|
||||
// to overwrite the progress bar and jump
|
||||
// back to the beginning of the line
|
||||
str := fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth))
|
||||
return writeString(c, str)
|
||||
// the following does not show correctly if the previous line is longer than subsequent line
|
||||
// return writeString(c, "\r")
|
||||
}
|
||||
|
||||
func writeString(c config, str string) error {
|
||||
if _, err := io.WriteString(c.writer, str); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f, ok := c.writer.(*os.File); ok {
|
||||
// ignore any errors in Sync(), as stdout
|
||||
// can't be synced on some operating systems
|
||||
// like Debian 9 (Stretch)
|
||||
f.Sync()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reader is the progressbar io.Reader struct
|
||||
type Reader struct {
|
||||
io.Reader
|
||||
bar *ProgressBar
|
||||
}
|
||||
|
||||
// NewReader return a new Reader with a given progress bar.
|
||||
func NewReader(r io.Reader, bar *ProgressBar) Reader {
|
||||
return Reader{
|
||||
Reader: r,
|
||||
bar: bar,
|
||||
}
|
||||
}
|
||||
|
||||
// Read will read the data and add the number of bytes to the progressbar
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.Reader.Read(p)
|
||||
r.bar.Add(n)
|
||||
return
|
||||
}
|
||||
|
||||
// Close the reader when it implements io.Closer
|
||||
func (r *Reader) Close() (err error) {
|
||||
if closer, ok := r.Reader.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
r.bar.Finish()
|
||||
return
|
||||
}
|
||||
|
||||
// Write implement io.Writer
|
||||
func (p *ProgressBar) Write(b []byte) (n int, err error) {
|
||||
n = len(b)
|
||||
p.Add(n)
|
||||
return
|
||||
}
|
||||
|
||||
// Read implement io.Reader
|
||||
func (p *ProgressBar) Read(b []byte) (n int, err error) {
|
||||
n = len(b)
|
||||
p.Add(n)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ProgressBar) Close() (err error) {
|
||||
p.Finish()
|
||||
return
|
||||
}
|
||||
|
||||
func average(xs []float64) float64 {
|
||||
total := 0.0
|
||||
for _, v := range xs {
|
||||
total += v
|
||||
}
|
||||
return total / float64(len(xs))
|
||||
}
|
||||
|
||||
func humanizeBytes(s float64) (string, string) {
|
||||
sizes := []string{" B", " kB", " MB", " GB", " TB", " PB", " EB"}
|
||||
base := 1000.0
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%2.0f", s), sizes[0]
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f"
|
||||
if val < 10 {
|
||||
f = "%.1f"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val), suffix
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
// termWidth function returns the visible width of the current terminal
|
||||
// and can be redefined for testing
|
||||
var termWidth = func() (width int, err error) {
|
||||
width, _, err = term.GetSize(int(os.Stdout.Fd()))
|
||||
if err == nil {
|
||||
return width, nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package progressbar
|
||||
|
||||
var spinners = map[int][]string{
|
||||
0: {"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"},
|
||||
1: {"▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"},
|
||||
2: {"▖", "▘", "▝", "▗"},
|
||||
3: {"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"},
|
||||
4: {"◢", "◣", "◤", "◥"},
|
||||
5: {"◰", "◳", "◲", "◱"},
|
||||
6: {"◴", "◷", "◶", "◵"},
|
||||
7: {"◐", "◓", "◑", "◒"},
|
||||
8: {".", "o", "O", "@", "*"},
|
||||
9: {"|", "/", "-", "\\"},
|
||||
10: {"◡◡", "⊙⊙", "◠◠"},
|
||||
11: {"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"},
|
||||
12: {">))'>", " >))'>", " >))'>", " >))'>", " >))'>", " <'((<", " <'((<", " <'((<"},
|
||||
13: {"⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"},
|
||||
14: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
||||
15: {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"},
|
||||
16: {"▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉"},
|
||||
17: {"■", "□", "▪", "▫"},
|
||||
18: {"←", "↑", "→", "↓"},
|
||||
19: {"╫", "╪"},
|
||||
20: {"⇐", "⇖", "⇑", "⇗", "⇒", "⇘", "⇓", "⇙"},
|
||||
21: {"⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈"},
|
||||
22: {"⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈"},
|
||||
23: {"⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"},
|
||||
24: {"⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋"},
|
||||
25: {"ヲ", "ァ", "ィ", "ゥ", "ェ", "ォ", "ャ", "ュ", "ョ", "ッ", "ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ", "タ", "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "ヒ", "フ", "ヘ", "ホ", "マ", "ミ", "ム", "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ", "ン"},
|
||||
26: {".", "..", "..."},
|
||||
27: {"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁"},
|
||||
28: {".", "o", "O", "°", "O", "o", "."},
|
||||
29: {"+", "x"},
|
||||
30: {"v", "<", "^", ">"},
|
||||
31: {">>--->", " >>--->", " >>--->", " >>--->", " >>--->", " <---<<", " <---<<", " <---<<", " <---<<", "<---<<"},
|
||||
32: {"|", "||", "|||", "||||", "|||||", "|||||||", "||||||||", "|||||||", "||||||", "|||||", "||||", "|||", "||", "|"},
|
||||
33: {"[ ]", "[= ]", "[== ]", "[=== ]", "[==== ]", "[===== ]", "[====== ]", "[======= ]", "[======== ]", "[========= ]", "[==========]"},
|
||||
34: {"(*---------)", "(-*--------)", "(--*-------)", "(---*------)", "(----*-----)", "(-----*----)", "(------*---)", "(-------*--)", "(--------*-)", "(---------*)"},
|
||||
35: {"█▒▒▒▒▒▒▒▒▒", "███▒▒▒▒▒▒▒", "█████▒▒▒▒▒", "███████▒▒▒", "██████████"},
|
||||
36: {"[ ]", "[=> ]", "[===> ]", "[=====> ]", "[======> ]", "[========> ]", "[==========> ]", "[============> ]", "[==============> ]", "[================> ]", "[==================> ]", "[===================>]"},
|
||||
37: {"ဝ", "၀"},
|
||||
38: {"▌", "▀", "▐▄"},
|
||||
39: {"🌍", "🌎", "🌏"},
|
||||
40: {"◜", "◝", "◞", "◟"},
|
||||
41: {"⬒", "⬔", "⬓", "⬕"},
|
||||
42: {"⬖", "⬘", "⬗", "⬙"},
|
||||
43: {"[>>> >]", "[]>>>> []", "[] >>>> []", "[] >>>> []", "[] >>>> []", "[] >>>>[]", "[>> >>]"},
|
||||
44: {"♠", "♣", "♥", "♦"},
|
||||
45: {"➞", "➟", "➠", "➡", "➠", "➟"},
|
||||
46: {" | ", ` \ `, "_ ", ` \ `, " | ", " / ", " _", " / "},
|
||||
47: {" . . . .", ". . . .", ". . . .", ". . . .", ". . . . ", ". . . . ."},
|
||||
48: {" | ", " / ", " _ ", ` \ `, " | ", ` \ `, " _ ", " / "},
|
||||
49: {"⎺", "⎻", "⎼", "⎽", "⎼", "⎻"},
|
||||
50: {"▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"},
|
||||
51: {"[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"},
|
||||
52: {"( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )", "( ● )"},
|
||||
53: {"✶", "✸", "✹", "✺", "✹", "✷"},
|
||||
54: {"▐|\\____________▌", "▐_|\\___________▌", "▐__|\\__________▌", "▐___|\\_________▌", "▐____|\\________▌", "▐_____|\\_______▌", "▐______|\\______▌", "▐_______|\\_____▌", "▐________|\\____▌", "▐_________|\\___▌", "▐__________|\\__▌", "▐___________|\\_▌", "▐____________|\\▌", "▐____________/|▌", "▐___________/|_▌", "▐__________/|__▌", "▐_________/|___▌", "▐________/|____▌", "▐_______/|_____▌", "▐______/|______▌", "▐_____/|_______▌", "▐____/|________▌", "▐___/|_________▌", "▐__/|__________▌", "▐_/|___________▌", "▐/|____________▌"},
|
||||
55: {"▐⠂ ▌", "▐⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂▌", "▐ ⠠▌", "▐ ⡀▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐⠠ ▌"},
|
||||
56: {"¿", "?"},
|
||||
57: {"⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"},
|
||||
58: {"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
|
||||
59: {". ", ".. ", "...", " ..", " .", " "},
|
||||
60: {".", "o", "O", "°", "O", "o", "."},
|
||||
61: {"▓", "▒", "░"},
|
||||
62: {"▌", "▀", "▐", "▄"},
|
||||
63: {"⊶", "⊷"},
|
||||
64: {"▪", "▫"},
|
||||
65: {"□", "■"},
|
||||
66: {"▮", "▯"},
|
||||
67: {"-", "=", "≡"},
|
||||
68: {"d", "q", "p", "b"},
|
||||
69: {"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"},
|
||||
70: {"🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "},
|
||||
71: {"☗", "☖"},
|
||||
72: {"⧇", "⧆"},
|
||||
73: {"◉", "◎"},
|
||||
74: {"㊂", "㊀", "㊁"},
|
||||
75: {"⦾", "⦿"},
|
||||
}
|
||||
Reference in New Issue
Block a user