Ver código fonte

vendor in progress bar and change to bytes instead of bibytes (#130)

Patrick Devine 1 ano atrás
pai
commit
e4d7f3e287
8 arquivos alterados com 1325 adições e 14 exclusões
  1. 1 1
      cmd/cmd.go
  2. 1 1
      cmd/spinner.go
  3. 3 6
      go.mod
  4. 0 6
      go.sum
  5. 21 0
      progressbar/LICENSE
  6. 121 0
      progressbar/README.md
  7. 1098 0
      progressbar/progressbar.go
  8. 80 0
      progressbar/spinners.go

+ 1 - 1
cmd/cmd.go

@@ -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
cmd/spinner.go

@@ -5,7 +5,7 @@ import (
 	"os"
 	"time"
 
-	"github.com/schollz/progressbar/v3"
+	"github.com/jmorganca/ollama/progressbar"
 )
 
 type Spinner struct {

+ 3 - 6
go.mod

@@ -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

+ 0 - 6
go.sum

@@ -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=

+ 21 - 0
progressbar/LICENSE

@@ -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.

+ 121 - 0
progressbar/README.md

@@ -0,0 +1,121 @@
+# progressbar
+
+[![CI](https://github.com/schollz/progressbar/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/schollz/progressbar/actions/workflows/ci.yml)
+[![go report card](https://goreportcard.com/badge/github.com/schollz/progressbar)](https://goreportcard.com/report/github.com/schollz/progressbar) 
+[![coverage](https://img.shields.io/badge/coverage-84%25-brightgreen.svg)](https://gocover.io/github.com/schollz/progressbar)
+[![godocs](https://godoc.org/github.com/schollz/progressbar?status.svg)](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:
+
+![Example of basic bar](examples/basic/basic.gif)
+
+
+### 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:
+
+![Example of download bar](examples/download/download.gif)
+
+
+### 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:
+
+![Example of download bar with unknown length](examples/download-unknown/download-unknown.gif)
+
+
+### 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:
+
+![Example of customized bar](examples/customization/customization.gif)
+
+
+## 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

+ 1098 - 0
progressbar/progressbar.go

@@ -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
+}

+ 80 - 0
progressbar/spinners.go

@@ -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: {"⦾", "⦿"},
+}