github.com/schollz/progressbar/v3@v3.14.2/progressbar.go (about)

     1  package progressbar
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"math"
     8  	"os"
     9  	"regexp"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/mitchellh/colorstring"
    15  	"github.com/rivo/uniseg"
    16  	"golang.org/x/term"
    17  )
    18  
    19  // ProgressBar is a thread-safe, simple
    20  // progress bar
    21  type ProgressBar struct {
    22  	state  state
    23  	config config
    24  	lock   sync.Mutex
    25  }
    26  
    27  // State is the basic properties of the bar
    28  type State struct {
    29  	Max            int64
    30  	CurrentNum     int64
    31  	CurrentPercent float64
    32  	CurrentBytes   float64
    33  	SecondsSince   float64
    34  	SecondsLeft    float64
    35  	KBsPerSecond   float64
    36  	Description    string
    37  }
    38  
    39  type state struct {
    40  	currentNum        int64
    41  	currentPercent    int
    42  	lastPercent       int
    43  	currentSaucerSize int
    44  	isAltSaucerHead   bool
    45  
    46  	lastShown time.Time
    47  	startTime time.Time
    48  
    49  	counterTime         time.Time
    50  	counterNumSinceLast int64
    51  	counterLastTenRates []float64
    52  
    53  	maxLineWidth int
    54  	currentBytes float64
    55  	finished     bool
    56  	exit         bool // Progress bar exit halfway
    57  
    58  	rendered string
    59  }
    60  
    61  type config struct {
    62  	max                  int64 // max number of the counter
    63  	maxHumanized         string
    64  	maxHumanizedSuffix   string
    65  	width                int
    66  	writer               io.Writer
    67  	theme                Theme
    68  	renderWithBlankState bool
    69  	description          string
    70  	iterationString      string
    71  	ignoreLength         bool // ignoreLength if max bytes not known
    72  
    73  	// whether the output is expected to contain color codes
    74  	colorCodes bool
    75  
    76  	// show rate of change in kB/sec or MB/sec
    77  	showBytes bool
    78  	// show the iterations per second
    79  	showIterationsPerSecond bool
    80  	showIterationsCount     bool
    81  
    82  	// whether the progress bar should show elapsed time.
    83  	// always enabled if predictTime is true.
    84  	elapsedTime bool
    85  
    86  	showElapsedTimeOnFinish bool
    87  
    88  	// whether the progress bar should attempt to predict the finishing
    89  	// time of the progress based on the start time and the average
    90  	// number of seconds between  increments.
    91  	predictTime bool
    92  
    93  	// minimum time to wait in between updates
    94  	throttleDuration time.Duration
    95  
    96  	// clear bar once finished
    97  	clearOnFinish bool
    98  
    99  	// spinnerType should be a number between 0-75
   100  	spinnerType int
   101  
   102  	// spinnerTypeOptionUsed remembers if the spinnerType was changed manually
   103  	spinnerTypeOptionUsed bool
   104  
   105  	// spinner represents the spinner as a slice of string
   106  	spinner []string
   107  
   108  	// fullWidth specifies whether to measure and set the bar to a specific width
   109  	fullWidth bool
   110  
   111  	// invisible doesn't render the bar at all, useful for debugging
   112  	invisible bool
   113  
   114  	onCompletion func()
   115  
   116  	// whether the render function should make use of ANSI codes to reduce console I/O
   117  	useANSICodes bool
   118  
   119  	// whether to use the IEC units (e.g. MiB) instead of the default SI units (e.g. MB)
   120  	useIECUnits bool
   121  
   122  	// showDescriptionAtLineEnd specifies whether description should be written at line end instead of line start
   123  	showDescriptionAtLineEnd bool
   124  }
   125  
   126  // Theme defines the elements of the bar
   127  type Theme struct {
   128  	Saucer        string
   129  	AltSaucerHead string
   130  	SaucerHead    string
   131  	SaucerPadding string
   132  	BarStart      string
   133  	BarEnd        string
   134  }
   135  
   136  // Option is the type all options need to adhere to
   137  type Option func(p *ProgressBar)
   138  
   139  // OptionSetWidth sets the width of the bar
   140  func OptionSetWidth(s int) Option {
   141  	return func(p *ProgressBar) {
   142  		p.config.width = s
   143  	}
   144  }
   145  
   146  // OptionSpinnerType sets the type of spinner used for indeterminate bars
   147  func OptionSpinnerType(spinnerType int) Option {
   148  	return func(p *ProgressBar) {
   149  		p.config.spinnerTypeOptionUsed = true
   150  		p.config.spinnerType = spinnerType
   151  	}
   152  }
   153  
   154  // OptionSpinnerCustom sets the spinner used for indeterminate bars to the passed
   155  // slice of string
   156  func OptionSpinnerCustom(spinner []string) Option {
   157  	return func(p *ProgressBar) {
   158  		p.config.spinner = spinner
   159  	}
   160  }
   161  
   162  // OptionSetTheme sets the elements the bar is constructed of
   163  func OptionSetTheme(t Theme) Option {
   164  	return func(p *ProgressBar) {
   165  		p.config.theme = t
   166  	}
   167  }
   168  
   169  // OptionSetVisibility sets the visibility
   170  func OptionSetVisibility(visibility bool) Option {
   171  	return func(p *ProgressBar) {
   172  		p.config.invisible = !visibility
   173  	}
   174  }
   175  
   176  // OptionFullWidth sets the bar to be full width
   177  func OptionFullWidth() Option {
   178  	return func(p *ProgressBar) {
   179  		p.config.fullWidth = true
   180  	}
   181  }
   182  
   183  // OptionSetWriter sets the output writer (defaults to os.StdOut)
   184  func OptionSetWriter(w io.Writer) Option {
   185  	return func(p *ProgressBar) {
   186  		p.config.writer = w
   187  	}
   188  }
   189  
   190  // OptionSetRenderBlankState sets whether or not to render a 0% bar on construction
   191  func OptionSetRenderBlankState(r bool) Option {
   192  	return func(p *ProgressBar) {
   193  		p.config.renderWithBlankState = r
   194  	}
   195  }
   196  
   197  // OptionSetDescription sets the description of the bar to render in front of it
   198  func OptionSetDescription(description string) Option {
   199  	return func(p *ProgressBar) {
   200  		p.config.description = description
   201  	}
   202  }
   203  
   204  // OptionEnableColorCodes enables or disables support for color codes
   205  // using mitchellh/colorstring
   206  func OptionEnableColorCodes(colorCodes bool) Option {
   207  	return func(p *ProgressBar) {
   208  		p.config.colorCodes = colorCodes
   209  	}
   210  }
   211  
   212  // OptionSetElapsedTime will enable elapsed time. Always enabled if OptionSetPredictTime is true.
   213  func OptionSetElapsedTime(elapsedTime bool) Option {
   214  	return func(p *ProgressBar) {
   215  		p.config.elapsedTime = elapsedTime
   216  	}
   217  }
   218  
   219  // OptionSetPredictTime will also attempt to predict the time remaining.
   220  func OptionSetPredictTime(predictTime bool) Option {
   221  	return func(p *ProgressBar) {
   222  		p.config.predictTime = predictTime
   223  	}
   224  }
   225  
   226  // OptionShowCount will also print current count out of total
   227  func OptionShowCount() Option {
   228  	return func(p *ProgressBar) {
   229  		p.config.showIterationsCount = true
   230  	}
   231  }
   232  
   233  // OptionShowIts will also print the iterations/second
   234  func OptionShowIts() Option {
   235  	return func(p *ProgressBar) {
   236  		p.config.showIterationsPerSecond = true
   237  	}
   238  }
   239  
   240  // OptionShowElapsedOnFinish will keep the display of elapsed time on finish
   241  func OptionShowElapsedTimeOnFinish() Option {
   242  	return func(p *ProgressBar) {
   243  		p.config.showElapsedTimeOnFinish = true
   244  	}
   245  }
   246  
   247  // OptionSetItsString sets what's displayed for iterations a second. The default is "it" which would display: "it/s"
   248  func OptionSetItsString(iterationString string) Option {
   249  	return func(p *ProgressBar) {
   250  		p.config.iterationString = iterationString
   251  	}
   252  }
   253  
   254  // OptionThrottle will wait the specified duration before updating again. The default
   255  // duration is 0 seconds.
   256  func OptionThrottle(duration time.Duration) Option {
   257  	return func(p *ProgressBar) {
   258  		p.config.throttleDuration = duration
   259  	}
   260  }
   261  
   262  // OptionClearOnFinish will clear the bar once its finished
   263  func OptionClearOnFinish() Option {
   264  	return func(p *ProgressBar) {
   265  		p.config.clearOnFinish = true
   266  	}
   267  }
   268  
   269  // OptionOnCompletion will invoke cmpl function once its finished
   270  func OptionOnCompletion(cmpl func()) Option {
   271  	return func(p *ProgressBar) {
   272  		p.config.onCompletion = cmpl
   273  	}
   274  }
   275  
   276  // OptionShowBytes will update the progress bar
   277  // configuration settings to display/hide kBytes/Sec
   278  func OptionShowBytes(val bool) Option {
   279  	return func(p *ProgressBar) {
   280  		p.config.showBytes = val
   281  	}
   282  }
   283  
   284  // OptionUseANSICodes will use more optimized terminal i/o.
   285  //
   286  // Only useful in environments with support for ANSI escape sequences.
   287  func OptionUseANSICodes(val bool) Option {
   288  	return func(p *ProgressBar) {
   289  		p.config.useANSICodes = val
   290  	}
   291  }
   292  
   293  // OptionUseIECUnits will enable IEC units (e.g. MiB) instead of the default
   294  // SI units (e.g. MB).
   295  func OptionUseIECUnits(val bool) Option {
   296  	return func(p *ProgressBar) {
   297  		p.config.useIECUnits = val
   298  	}
   299  }
   300  
   301  // OptionShowDescriptionAtLineEnd defines whether description should be written at line end instead of line start
   302  func OptionShowDescriptionAtLineEnd() Option {
   303  	return func(p *ProgressBar) {
   304  		p.config.showDescriptionAtLineEnd = true
   305  	}
   306  }
   307  
   308  var defaultTheme = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEnd: "|"}
   309  
   310  // NewOptions constructs a new instance of ProgressBar, with any options you specify
   311  func NewOptions(max int, options ...Option) *ProgressBar {
   312  	return NewOptions64(int64(max), options...)
   313  }
   314  
   315  // NewOptions64 constructs a new instance of ProgressBar, with any options you specify
   316  func NewOptions64(max int64, options ...Option) *ProgressBar {
   317  	b := ProgressBar{
   318  		state: getBasicState(),
   319  		config: config{
   320  			writer:           os.Stdout,
   321  			theme:            defaultTheme,
   322  			iterationString:  "it",
   323  			width:            40,
   324  			max:              max,
   325  			throttleDuration: 0 * time.Nanosecond,
   326  			elapsedTime:      true,
   327  			predictTime:      true,
   328  			spinnerType:      9,
   329  			invisible:        false,
   330  		},
   331  	}
   332  
   333  	for _, o := range options {
   334  		o(&b)
   335  	}
   336  
   337  	if b.config.spinnerType < 0 || b.config.spinnerType > 75 {
   338  		panic("invalid spinner type, must be between 0 and 75")
   339  	}
   340  
   341  	// ignoreLength if max bytes not known
   342  	if b.config.max == -1 {
   343  		b.config.ignoreLength = true
   344  		b.config.max = int64(b.config.width)
   345  		b.config.predictTime = false
   346  	}
   347  
   348  	b.config.maxHumanized, b.config.maxHumanizedSuffix = humanizeBytes(float64(b.config.max),
   349  		b.config.useIECUnits)
   350  
   351  	if b.config.renderWithBlankState {
   352  		b.RenderBlank()
   353  	}
   354  
   355  	return &b
   356  }
   357  
   358  func getBasicState() state {
   359  	now := time.Now()
   360  	return state{
   361  		startTime:   now,
   362  		lastShown:   now,
   363  		counterTime: now,
   364  	}
   365  }
   366  
   367  // New returns a new ProgressBar
   368  // with the specified maximum
   369  func New(max int) *ProgressBar {
   370  	return NewOptions(max)
   371  }
   372  
   373  // DefaultBytes provides a progressbar to measure byte
   374  // throughput with recommended defaults.
   375  // Set maxBytes to -1 to use as a spinner.
   376  func DefaultBytes(maxBytes int64, description ...string) *ProgressBar {
   377  	desc := ""
   378  	if len(description) > 0 {
   379  		desc = description[0]
   380  	}
   381  	return NewOptions64(
   382  		maxBytes,
   383  		OptionSetDescription(desc),
   384  		OptionSetWriter(os.Stderr),
   385  		OptionShowBytes(true),
   386  		OptionSetWidth(10),
   387  		OptionThrottle(65*time.Millisecond),
   388  		OptionShowCount(),
   389  		OptionOnCompletion(func() {
   390  			fmt.Fprint(os.Stderr, "\n")
   391  		}),
   392  		OptionSpinnerType(14),
   393  		OptionFullWidth(),
   394  		OptionSetRenderBlankState(true),
   395  	)
   396  }
   397  
   398  // DefaultBytesSilent is the same as DefaultBytes, but does not output anywhere.
   399  // String() can be used to get the output instead.
   400  func DefaultBytesSilent(maxBytes int64, description ...string) *ProgressBar {
   401  	// Mostly the same bar as DefaultBytes
   402  
   403  	desc := ""
   404  	if len(description) > 0 {
   405  		desc = description[0]
   406  	}
   407  	return NewOptions64(
   408  		maxBytes,
   409  		OptionSetDescription(desc),
   410  		OptionSetWriter(io.Discard),
   411  		OptionShowBytes(true),
   412  		OptionSetWidth(10),
   413  		OptionThrottle(65*time.Millisecond),
   414  		OptionShowCount(),
   415  		OptionSpinnerType(14),
   416  		OptionFullWidth(),
   417  	)
   418  }
   419  
   420  // Default provides a progressbar with recommended defaults.
   421  // Set max to -1 to use as a spinner.
   422  func Default(max int64, description ...string) *ProgressBar {
   423  	desc := ""
   424  	if len(description) > 0 {
   425  		desc = description[0]
   426  	}
   427  	return NewOptions64(
   428  		max,
   429  		OptionSetDescription(desc),
   430  		OptionSetWriter(os.Stderr),
   431  		OptionSetWidth(10),
   432  		OptionThrottle(65*time.Millisecond),
   433  		OptionShowCount(),
   434  		OptionShowIts(),
   435  		OptionOnCompletion(func() {
   436  			fmt.Fprint(os.Stderr, "\n")
   437  		}),
   438  		OptionSpinnerType(14),
   439  		OptionFullWidth(),
   440  		OptionSetRenderBlankState(true),
   441  	)
   442  }
   443  
   444  // DefaultSilent is the same as Default, but does not output anywhere.
   445  // String() can be used to get the output instead.
   446  func DefaultSilent(max int64, description ...string) *ProgressBar {
   447  	// Mostly the same bar as Default
   448  
   449  	desc := ""
   450  	if len(description) > 0 {
   451  		desc = description[0]
   452  	}
   453  	return NewOptions64(
   454  		max,
   455  		OptionSetDescription(desc),
   456  		OptionSetWriter(io.Discard),
   457  		OptionSetWidth(10),
   458  		OptionThrottle(65*time.Millisecond),
   459  		OptionShowCount(),
   460  		OptionShowIts(),
   461  		OptionSpinnerType(14),
   462  		OptionFullWidth(),
   463  	)
   464  }
   465  
   466  // String returns the current rendered version of the progress bar.
   467  // It will never return an empty string while the progress bar is running.
   468  func (p *ProgressBar) String() string {
   469  	return p.state.rendered
   470  }
   471  
   472  // RenderBlank renders the current bar state, you can use this to render a 0% state
   473  func (p *ProgressBar) RenderBlank() error {
   474  	p.lock.Lock()
   475  	defer p.lock.Unlock()
   476  
   477  	if p.config.invisible {
   478  		return nil
   479  	}
   480  	if p.state.currentNum == 0 {
   481  		p.state.lastShown = time.Time{}
   482  	}
   483  	return p.render()
   484  }
   485  
   486  // Reset will reset the clock that is used
   487  // to calculate current time and the time left.
   488  func (p *ProgressBar) Reset() {
   489  	p.lock.Lock()
   490  	defer p.lock.Unlock()
   491  
   492  	p.state = getBasicState()
   493  }
   494  
   495  // Finish will fill the bar to full
   496  func (p *ProgressBar) Finish() error {
   497  	p.lock.Lock()
   498  	p.state.currentNum = p.config.max
   499  	p.lock.Unlock()
   500  	return p.Add(0)
   501  }
   502  
   503  // Exit will exit the bar to keep current state
   504  func (p *ProgressBar) Exit() error {
   505  	p.lock.Lock()
   506  	defer p.lock.Unlock()
   507  
   508  	p.state.exit = true
   509  	if p.config.onCompletion != nil {
   510  		p.config.onCompletion()
   511  	}
   512  	return nil
   513  }
   514  
   515  // Add will add the specified amount to the progressbar
   516  func (p *ProgressBar) Add(num int) error {
   517  	return p.Add64(int64(num))
   518  }
   519  
   520  // Set will set the bar to a current number
   521  func (p *ProgressBar) Set(num int) error {
   522  	return p.Set64(int64(num))
   523  }
   524  
   525  // Set64 will set the bar to a current number
   526  func (p *ProgressBar) Set64(num int64) error {
   527  	p.lock.Lock()
   528  	toAdd := num - int64(p.state.currentBytes)
   529  	p.lock.Unlock()
   530  	return p.Add64(toAdd)
   531  }
   532  
   533  // Add64 will add the specified amount to the progressbar
   534  func (p *ProgressBar) Add64(num int64) error {
   535  	if p.config.invisible {
   536  		return nil
   537  	}
   538  	p.lock.Lock()
   539  	defer p.lock.Unlock()
   540  
   541  	if p.state.exit {
   542  		return nil
   543  	}
   544  
   545  	// error out since OptionSpinnerCustom will always override a manually set spinnerType
   546  	if p.config.spinnerTypeOptionUsed && len(p.config.spinner) > 0 {
   547  		return errors.New("OptionSpinnerType and OptionSpinnerCustom cannot be used together")
   548  	}
   549  
   550  	if p.config.max == 0 {
   551  		return errors.New("max must be greater than 0")
   552  	}
   553  
   554  	if p.state.currentNum < p.config.max {
   555  		if p.config.ignoreLength {
   556  			p.state.currentNum = (p.state.currentNum + num) % p.config.max
   557  		} else {
   558  			p.state.currentNum += num
   559  		}
   560  	}
   561  
   562  	p.state.currentBytes += float64(num)
   563  
   564  	// reset the countdown timer every second to take rolling average
   565  	p.state.counterNumSinceLast += num
   566  	if time.Since(p.state.counterTime).Seconds() > 0.5 {
   567  		p.state.counterLastTenRates = append(p.state.counterLastTenRates, float64(p.state.counterNumSinceLast)/time.Since(p.state.counterTime).Seconds())
   568  		if len(p.state.counterLastTenRates) > 10 {
   569  			p.state.counterLastTenRates = p.state.counterLastTenRates[1:]
   570  		}
   571  		p.state.counterTime = time.Now()
   572  		p.state.counterNumSinceLast = 0
   573  	}
   574  
   575  	percent := float64(p.state.currentNum) / float64(p.config.max)
   576  	p.state.currentSaucerSize = int(percent * float64(p.config.width))
   577  	p.state.currentPercent = int(percent * 100)
   578  	updateBar := p.state.currentPercent != p.state.lastPercent && p.state.currentPercent > 0
   579  
   580  	p.state.lastPercent = p.state.currentPercent
   581  	if p.state.currentNum > p.config.max {
   582  		return errors.New("current number exceeds max")
   583  	}
   584  
   585  	// always update if show bytes/second or its/second
   586  	if updateBar || p.config.showIterationsPerSecond || p.config.showIterationsCount {
   587  		return p.render()
   588  	}
   589  
   590  	return nil
   591  }
   592  
   593  // Clear erases the progress bar from the current line
   594  func (p *ProgressBar) Clear() error {
   595  	return clearProgressBar(p.config, p.state)
   596  }
   597  
   598  // Describe will change the description shown before the progress, which
   599  // can be changed on the fly (as for a slow running process).
   600  func (p *ProgressBar) Describe(description string) {
   601  	p.lock.Lock()
   602  	defer p.lock.Unlock()
   603  	p.config.description = description
   604  	if p.config.invisible {
   605  		return
   606  	}
   607  	p.render()
   608  }
   609  
   610  // New64 returns a new ProgressBar
   611  // with the specified maximum
   612  func New64(max int64) *ProgressBar {
   613  	return NewOptions64(max)
   614  }
   615  
   616  // GetMax returns the max of a bar
   617  func (p *ProgressBar) GetMax() int {
   618  	return int(p.config.max)
   619  }
   620  
   621  // GetMax64 returns the current max
   622  func (p *ProgressBar) GetMax64() int64 {
   623  	return p.config.max
   624  }
   625  
   626  // ChangeMax takes in a int
   627  // and changes the max value
   628  // of the progress bar
   629  func (p *ProgressBar) ChangeMax(newMax int) {
   630  	p.ChangeMax64(int64(newMax))
   631  }
   632  
   633  // ChangeMax64 is basically
   634  // the same as ChangeMax,
   635  // but takes in a int64
   636  // to avoid casting
   637  func (p *ProgressBar) ChangeMax64(newMax int64) {
   638  	p.config.max = newMax
   639  
   640  	if p.config.showBytes {
   641  		p.config.maxHumanized, p.config.maxHumanizedSuffix = humanizeBytes(float64(p.config.max),
   642  			p.config.useIECUnits)
   643  	}
   644  
   645  	p.Add(0) // re-render
   646  }
   647  
   648  // IsFinished returns true if progress bar is completed
   649  func (p *ProgressBar) IsFinished() bool {
   650  	p.lock.Lock()
   651  	defer p.lock.Unlock()
   652  
   653  	return p.state.finished
   654  }
   655  
   656  // render renders the progress bar, updating the maximum
   657  // rendered line width. this function is not thread-safe,
   658  // so it must be called with an acquired lock.
   659  func (p *ProgressBar) render() error {
   660  	// make sure that the rendering is not happening too quickly
   661  	// but always show if the currentNum reaches the max
   662  	if time.Since(p.state.lastShown).Nanoseconds() < p.config.throttleDuration.Nanoseconds() &&
   663  		p.state.currentNum < p.config.max {
   664  		return nil
   665  	}
   666  
   667  	if !p.config.useANSICodes {
   668  		// first, clear the existing progress bar
   669  		err := clearProgressBar(p.config, p.state)
   670  		if err != nil {
   671  			return err
   672  		}
   673  	}
   674  
   675  	// check if the progress bar is finished
   676  	if !p.state.finished && p.state.currentNum >= p.config.max {
   677  		p.state.finished = true
   678  		if !p.config.clearOnFinish {
   679  			renderProgressBar(p.config, &p.state)
   680  		}
   681  		if p.config.onCompletion != nil {
   682  			p.config.onCompletion()
   683  		}
   684  	}
   685  	if p.state.finished {
   686  		// when using ANSI codes we don't pre-clean the current line
   687  		if p.config.useANSICodes && p.config.clearOnFinish {
   688  			err := clearProgressBar(p.config, p.state)
   689  			if err != nil {
   690  				return err
   691  			}
   692  		}
   693  		return nil
   694  	}
   695  
   696  	// then, re-render the current progress bar
   697  	w, err := renderProgressBar(p.config, &p.state)
   698  	if err != nil {
   699  		return err
   700  	}
   701  
   702  	if w > p.state.maxLineWidth {
   703  		p.state.maxLineWidth = w
   704  	}
   705  
   706  	p.state.lastShown = time.Now()
   707  
   708  	return nil
   709  }
   710  
   711  // State returns the current state
   712  func (p *ProgressBar) State() State {
   713  	p.lock.Lock()
   714  	defer p.lock.Unlock()
   715  	s := State{}
   716  	s.CurrentNum = p.state.currentNum
   717  	s.Max = p.config.max
   718  	if p.config.ignoreLength {
   719  		s.Max = -1
   720  	}
   721  	s.CurrentPercent = float64(p.state.currentNum) / float64(p.config.max)
   722  	s.CurrentBytes = p.state.currentBytes
   723  	s.SecondsSince = time.Since(p.state.startTime).Seconds()
   724  	if p.state.currentNum > 0 {
   725  		s.SecondsLeft = s.SecondsSince / float64(p.state.currentNum) * (float64(p.config.max) - float64(p.state.currentNum))
   726  	}
   727  	s.KBsPerSecond = float64(p.state.currentBytes) / 1024.0 / s.SecondsSince
   728  	s.Description = p.config.description
   729  	return s
   730  }
   731  
   732  // regex matching ansi escape codes
   733  var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
   734  
   735  func getStringWidth(c config, str string, colorize bool) int {
   736  	if c.colorCodes {
   737  		// convert any color codes in the progress bar into the respective ANSI codes
   738  		str = colorstring.Color(str)
   739  	}
   740  
   741  	// the width of the string, if printed to the console
   742  	// does not include the carriage return character
   743  	cleanString := strings.Replace(str, "\r", "", -1)
   744  
   745  	if c.colorCodes {
   746  		// the ANSI codes for the colors do not take up space in the console output,
   747  		// so they do not count towards the output string width
   748  		cleanString = ansiRegex.ReplaceAllString(cleanString, "")
   749  	}
   750  
   751  	// get the amount of runes in the string instead of the
   752  	// character count of the string, as some runes span multiple characters.
   753  	// see https://stackoverflow.com/a/12668840/2733724
   754  	stringWidth := uniseg.StringWidth(cleanString)
   755  	return stringWidth
   756  }
   757  
   758  func renderProgressBar(c config, s *state) (int, error) {
   759  	var sb strings.Builder
   760  
   761  	averageRate := average(s.counterLastTenRates)
   762  	if len(s.counterLastTenRates) == 0 || s.finished {
   763  		// if no average samples, or if finished,
   764  		// then average rate should be the total rate
   765  		if t := time.Since(s.startTime).Seconds(); t > 0 {
   766  			averageRate = s.currentBytes / t
   767  		} else {
   768  			averageRate = 0
   769  		}
   770  	}
   771  
   772  	// show iteration count in "current/total" iterations format
   773  	if c.showIterationsCount {
   774  		if sb.Len() == 0 {
   775  			sb.WriteString("(")
   776  		} else {
   777  			sb.WriteString(", ")
   778  		}
   779  		if !c.ignoreLength {
   780  			if c.showBytes {
   781  				currentHumanize, currentSuffix := humanizeBytes(s.currentBytes, c.useIECUnits)
   782  				if currentSuffix == c.maxHumanizedSuffix {
   783  					sb.WriteString(fmt.Sprintf("%s/%s%s",
   784  						currentHumanize, c.maxHumanized, c.maxHumanizedSuffix))
   785  				} else {
   786  					sb.WriteString(fmt.Sprintf("%s%s/%s%s",
   787  						currentHumanize, currentSuffix, c.maxHumanized, c.maxHumanizedSuffix))
   788  				}
   789  			} else {
   790  				sb.WriteString(fmt.Sprintf("%.0f/%d", s.currentBytes, c.max))
   791  			}
   792  		} else {
   793  			if c.showBytes {
   794  				currentHumanize, currentSuffix := humanizeBytes(s.currentBytes, c.useIECUnits)
   795  				sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix))
   796  			} else {
   797  				sb.WriteString(fmt.Sprintf("%.0f/%s", s.currentBytes, "-"))
   798  			}
   799  		}
   800  	}
   801  
   802  	// show rolling average rate
   803  	if c.showBytes && averageRate > 0 && !math.IsInf(averageRate, 1) {
   804  		if sb.Len() == 0 {
   805  			sb.WriteString("(")
   806  		} else {
   807  			sb.WriteString(", ")
   808  		}
   809  		currentHumanize, currentSuffix := humanizeBytes(averageRate, c.useIECUnits)
   810  		sb.WriteString(fmt.Sprintf("%s%s/s", currentHumanize, currentSuffix))
   811  	}
   812  
   813  	// show iterations rate
   814  	if c.showIterationsPerSecond {
   815  		if sb.Len() == 0 {
   816  			sb.WriteString("(")
   817  		} else {
   818  			sb.WriteString(", ")
   819  		}
   820  		if averageRate > 1 {
   821  			sb.WriteString(fmt.Sprintf("%0.0f %s/s", averageRate, c.iterationString))
   822  		} else if averageRate*60 > 1 {
   823  			sb.WriteString(fmt.Sprintf("%0.0f %s/min", 60*averageRate, c.iterationString))
   824  		} else {
   825  			sb.WriteString(fmt.Sprintf("%0.0f %s/hr", 3600*averageRate, c.iterationString))
   826  		}
   827  	}
   828  	if sb.Len() > 0 {
   829  		sb.WriteString(")")
   830  	}
   831  
   832  	leftBrac, rightBrac, saucer, saucerHead := "", "", "", ""
   833  
   834  	// show time prediction in "current/total" seconds format
   835  	switch {
   836  	case c.predictTime:
   837  		rightBracNum := (time.Duration((1/averageRate)*(float64(c.max)-float64(s.currentNum))) * time.Second)
   838  		if rightBracNum.Seconds() < 0 {
   839  			rightBracNum = 0 * time.Second
   840  		}
   841  		rightBrac = rightBracNum.String()
   842  		fallthrough
   843  	case c.elapsedTime:
   844  		leftBrac = (time.Duration(time.Since(s.startTime).Seconds()) * time.Second).String()
   845  	}
   846  
   847  	if c.fullWidth && !c.ignoreLength {
   848  		width, err := termWidth()
   849  		if err != nil {
   850  			width = 80
   851  		}
   852  
   853  		amend := 1 // an extra space at eol
   854  		switch {
   855  		case leftBrac != "" && rightBrac != "":
   856  			amend = 4 // space, square brackets and colon
   857  		case leftBrac != "" && rightBrac == "":
   858  			amend = 4 // space and square brackets and another space
   859  		case leftBrac == "" && rightBrac != "":
   860  			amend = 3 // space and square brackets
   861  		}
   862  		if c.showDescriptionAtLineEnd {
   863  			amend += 1 // another space
   864  		}
   865  
   866  		c.width = width - getStringWidth(c, c.description, true) - 10 - amend - sb.Len() - len(leftBrac) - len(rightBrac)
   867  		s.currentSaucerSize = int(float64(s.currentPercent) / 100.0 * float64(c.width))
   868  	}
   869  	if s.currentSaucerSize > 0 {
   870  		if c.ignoreLength {
   871  			saucer = strings.Repeat(c.theme.SaucerPadding, s.currentSaucerSize-1)
   872  		} else {
   873  			saucer = strings.Repeat(c.theme.Saucer, s.currentSaucerSize-1)
   874  		}
   875  
   876  		// Check if an alternate saucer head is set for animation
   877  		if c.theme.AltSaucerHead != "" && s.isAltSaucerHead {
   878  			saucerHead = c.theme.AltSaucerHead
   879  			s.isAltSaucerHead = false
   880  		} else if c.theme.SaucerHead == "" || s.currentSaucerSize == c.width {
   881  			// use the saucer for the saucer head if it hasn't been set
   882  			// to preserve backwards compatibility
   883  			saucerHead = c.theme.Saucer
   884  		} else {
   885  			saucerHead = c.theme.SaucerHead
   886  			s.isAltSaucerHead = true
   887  		}
   888  	}
   889  
   890  	/*
   891  		Progress Bar format
   892  		Description % |------        |  (kb/s) (iteration count) (iteration rate) (predict time)
   893  
   894  		or if showDescriptionAtLineEnd is enabled
   895  		% |------        |  (kb/s) (iteration count) (iteration rate) (predict time) Description
   896  	*/
   897  
   898  	repeatAmount := c.width - s.currentSaucerSize
   899  	if repeatAmount < 0 {
   900  		repeatAmount = 0
   901  	}
   902  
   903  	str := ""
   904  
   905  	if c.ignoreLength {
   906  		selectedSpinner := spinners[c.spinnerType]
   907  		if len(c.spinner) > 0 {
   908  			selectedSpinner = c.spinner
   909  		}
   910  		spinner := selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Milliseconds()/100), float64(len(selectedSpinner)))))]
   911  		if c.elapsedTime {
   912  			if c.showDescriptionAtLineEnd {
   913  				str = fmt.Sprintf("\r%s %s [%s] %s ",
   914  					spinner,
   915  					sb.String(),
   916  					leftBrac,
   917  					c.description)
   918  			} else {
   919  				str = fmt.Sprintf("\r%s %s %s [%s] ",
   920  					spinner,
   921  					c.description,
   922  					sb.String(),
   923  					leftBrac)
   924  			}
   925  		} else {
   926  			if c.showDescriptionAtLineEnd {
   927  				str = fmt.Sprintf("\r%s %s %s ",
   928  					spinner,
   929  					sb.String(),
   930  					c.description)
   931  			} else {
   932  				str = fmt.Sprintf("\r%s %s %s ",
   933  					spinner,
   934  					c.description,
   935  					sb.String())
   936  			}
   937  		}
   938  	} else if rightBrac == "" {
   939  		str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
   940  			s.currentPercent,
   941  			c.theme.BarStart,
   942  			saucer,
   943  			saucerHead,
   944  			strings.Repeat(c.theme.SaucerPadding, repeatAmount),
   945  			c.theme.BarEnd,
   946  			sb.String())
   947  
   948  		if s.currentPercent == 100 && c.showElapsedTimeOnFinish {
   949  			str = fmt.Sprintf("%s [%s]", str, leftBrac)
   950  		}
   951  
   952  		if c.showDescriptionAtLineEnd {
   953  			str = fmt.Sprintf("\r%s %s ", str, c.description)
   954  		} else {
   955  			str = fmt.Sprintf("\r%s%s ", c.description, str)
   956  		}
   957  	} else {
   958  		if s.currentPercent == 100 {
   959  			str = fmt.Sprintf("%4d%% %s%s%s%s%s %s",
   960  				s.currentPercent,
   961  				c.theme.BarStart,
   962  				saucer,
   963  				saucerHead,
   964  				strings.Repeat(c.theme.SaucerPadding, repeatAmount),
   965  				c.theme.BarEnd,
   966  				sb.String())
   967  
   968  			if c.showElapsedTimeOnFinish {
   969  				str = fmt.Sprintf("%s [%s]", str, leftBrac)
   970  			}
   971  
   972  			if c.showDescriptionAtLineEnd {
   973  				str = fmt.Sprintf("\r%s %s", str, c.description)
   974  			} else {
   975  				str = fmt.Sprintf("\r%s%s", c.description, str)
   976  			}
   977  		} else {
   978  			str = fmt.Sprintf("%4d%% %s%s%s%s%s %s [%s:%s]",
   979  				s.currentPercent,
   980  				c.theme.BarStart,
   981  				saucer,
   982  				saucerHead,
   983  				strings.Repeat(c.theme.SaucerPadding, repeatAmount),
   984  				c.theme.BarEnd,
   985  				sb.String(),
   986  				leftBrac,
   987  				rightBrac)
   988  
   989  			if c.showDescriptionAtLineEnd {
   990  				str = fmt.Sprintf("\r%s %s", str, c.description)
   991  			} else {
   992  				str = fmt.Sprintf("\r%s%s", c.description, str)
   993  			}
   994  		}
   995  	}
   996  
   997  	if c.colorCodes {
   998  		// convert any color codes in the progress bar into the respective ANSI codes
   999  		str = colorstring.Color(str)
  1000  	}
  1001  
  1002  	s.rendered = str
  1003  
  1004  	return getStringWidth(c, str, false), writeString(c, str)
  1005  }
  1006  
  1007  func clearProgressBar(c config, s state) error {
  1008  	if s.maxLineWidth == 0 {
  1009  		return nil
  1010  	}
  1011  	if c.useANSICodes {
  1012  		// write the "clear current line" ANSI escape sequence
  1013  		return writeString(c, "\033[2K\r")
  1014  	}
  1015  	// fill the empty content
  1016  	// to overwrite the progress bar and jump
  1017  	// back to the beginning of the line
  1018  	str := fmt.Sprintf("\r%s\r", strings.Repeat(" ", s.maxLineWidth))
  1019  	return writeString(c, str)
  1020  	// the following does not show correctly if the previous line is longer than subsequent line
  1021  	// return writeString(c, "\r")
  1022  }
  1023  
  1024  func writeString(c config, str string) error {
  1025  	if _, err := io.WriteString(c.writer, str); err != nil {
  1026  		return err
  1027  	}
  1028  
  1029  	if f, ok := c.writer.(*os.File); ok {
  1030  		// ignore any errors in Sync(), as stdout
  1031  		// can't be synced on some operating systems
  1032  		// like Debian 9 (Stretch)
  1033  		f.Sync()
  1034  	}
  1035  
  1036  	return nil
  1037  }
  1038  
  1039  // Reader is the progressbar io.Reader struct
  1040  type Reader struct {
  1041  	io.Reader
  1042  	bar *ProgressBar
  1043  }
  1044  
  1045  // NewReader return a new Reader with a given progress bar.
  1046  func NewReader(r io.Reader, bar *ProgressBar) Reader {
  1047  	return Reader{
  1048  		Reader: r,
  1049  		bar:    bar,
  1050  	}
  1051  }
  1052  
  1053  // Read will read the data and add the number of bytes to the progressbar
  1054  func (r *Reader) Read(p []byte) (n int, err error) {
  1055  	n, err = r.Reader.Read(p)
  1056  	r.bar.Add(n)
  1057  	return
  1058  }
  1059  
  1060  // Close the reader when it implements io.Closer
  1061  func (r *Reader) Close() (err error) {
  1062  	if closer, ok := r.Reader.(io.Closer); ok {
  1063  		return closer.Close()
  1064  	}
  1065  	r.bar.Finish()
  1066  	return
  1067  }
  1068  
  1069  // Write implement io.Writer
  1070  func (p *ProgressBar) Write(b []byte) (n int, err error) {
  1071  	n = len(b)
  1072  	p.Add(n)
  1073  	return
  1074  }
  1075  
  1076  // Read implement io.Reader
  1077  func (p *ProgressBar) Read(b []byte) (n int, err error) {
  1078  	n = len(b)
  1079  	p.Add(n)
  1080  	return
  1081  }
  1082  
  1083  func (p *ProgressBar) Close() (err error) {
  1084  	p.Finish()
  1085  	return
  1086  }
  1087  
  1088  func average(xs []float64) float64 {
  1089  	total := 0.0
  1090  	for _, v := range xs {
  1091  		total += v
  1092  	}
  1093  	return total / float64(len(xs))
  1094  }
  1095  
  1096  func humanizeBytes(s float64, iec bool) (string, string) {
  1097  	sizes := []string{" B", " kB", " MB", " GB", " TB", " PB", " EB"}
  1098  	base := 1000.0
  1099  
  1100  	if iec {
  1101  		sizes = []string{" B", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB"}
  1102  		base = 1024.0
  1103  	}
  1104  
  1105  	if s < 10 {
  1106  		return fmt.Sprintf("%2.0f", s), sizes[0]
  1107  	}
  1108  	e := math.Floor(logn(float64(s), base))
  1109  	suffix := sizes[int(e)]
  1110  	val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
  1111  	f := "%.0f"
  1112  	if val < 10 {
  1113  		f = "%.1f"
  1114  	}
  1115  
  1116  	return fmt.Sprintf(f, val), suffix
  1117  }
  1118  
  1119  func logn(n, b float64) float64 {
  1120  	return math.Log(n) / math.Log(b)
  1121  }
  1122  
  1123  // termWidth function returns the visible width of the current terminal
  1124  // and can be redefined for testing
  1125  var termWidth = func() (width int, err error) {
  1126  	width, _, err = term.GetSize(int(os.Stdout.Fd()))
  1127  	if err == nil {
  1128  		return width, nil
  1129  	}
  1130  
  1131  	return 0, err
  1132  }