forked from toolshed/abra
		
	We were running behind and there were quite some deprecations to update. This was mostly in the upstream copy/pasta package but seems quite minimal.
		
			
				
	
	
		
			1491 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1491 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package progressbar
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"log"
 | 
						|
	"math"
 | 
						|
	"net/http"
 | 
						|
	"os"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/mitchellh/colorstring"
 | 
						|
	"github.com/rivo/uniseg"
 | 
						|
	"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 {
 | 
						|
	Max            int64
 | 
						|
	CurrentNum     int64
 | 
						|
	CurrentPercent float64
 | 
						|
	CurrentBytes   float64
 | 
						|
	SecondsSince   float64
 | 
						|
	SecondsLeft    float64
 | 
						|
	KBsPerSecond   float64
 | 
						|
	Description    string
 | 
						|
}
 | 
						|
 | 
						|
type state struct {
 | 
						|
	currentNum        int64
 | 
						|
	currentPercent    int
 | 
						|
	lastPercent       int
 | 
						|
	currentSaucerSize int
 | 
						|
	isAltSaucerHead   bool
 | 
						|
 | 
						|
	lastShown time.Time
 | 
						|
	startTime time.Time // time when the progress bar start working
 | 
						|
 | 
						|
	counterTime         time.Time
 | 
						|
	counterNumSinceLast int64
 | 
						|
	counterLastTenRates []float64
 | 
						|
	spinnerIdx          int // the index of spinner
 | 
						|
 | 
						|
	maxLineWidth int
 | 
						|
	currentBytes float64
 | 
						|
	finished     bool
 | 
						|
	exit         bool // Progress bar exit halfway
 | 
						|
 | 
						|
	details []string // details to show,only used when detail row is set to more than 0
 | 
						|
 | 
						|
	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 the total bytes (e.g. 23/24 or 23/-, vs. just 23).
 | 
						|
	showTotalBytes 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
 | 
						|
 | 
						|
	// spinnerChangeInterval the change interval of spinner
 | 
						|
	// if set this attribute to 0, the spinner only change when renderProgressBar was called
 | 
						|
	// for example, each time when Add() was called,which will call renderProgressBar function
 | 
						|
	spinnerChangeInterval time.Duration
 | 
						|
 | 
						|
	// 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
 | 
						|
 | 
						|
	// whether to use the IEC units (e.g. MiB) instead of the default SI units (e.g. MB)
 | 
						|
	useIECUnits bool
 | 
						|
 | 
						|
	// showDescriptionAtLineEnd specifies whether description should be written at line end instead of line start
 | 
						|
	showDescriptionAtLineEnd bool
 | 
						|
 | 
						|
	// specifies how many rows of details to show,default value is 0 and no details will be shown
 | 
						|
	maxDetailRow int
 | 
						|
 | 
						|
	stdBuffer bytes.Buffer
 | 
						|
}
 | 
						|
 | 
						|
// Theme defines the elements of the bar
 | 
						|
type Theme struct {
 | 
						|
	Saucer        string
 | 
						|
	AltSaucerHead string
 | 
						|
	SaucerHead    string
 | 
						|
	SaucerPadding string
 | 
						|
	BarStart      string
 | 
						|
	BarEnd        string
 | 
						|
 | 
						|
	// BarStartFilled is used after the Bar starts filling, if set. Otherwise, it defaults to BarStart.
 | 
						|
	BarStartFilled string
 | 
						|
 | 
						|
	// BarEndFilled is used once the Bar finishes, if set. Otherwise, it defaults to BarEnd.
 | 
						|
	BarEndFilled string
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	// ThemeDefault is given by default (if not changed with OptionSetTheme), and it looks like "|████     |".
 | 
						|
	ThemeDefault = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEnd: "|"}
 | 
						|
 | 
						|
	// ThemeASCII is a predefined Theme that uses ASCII symbols. It looks like "[===>...]".
 | 
						|
	// Configure it with OptionSetTheme(ThemeASCII).
 | 
						|
	ThemeASCII = Theme{
 | 
						|
		Saucer:        "=",
 | 
						|
		SaucerHead:    ">",
 | 
						|
		SaucerPadding: ".",
 | 
						|
		BarStart:      "[",
 | 
						|
		BarEnd:        "]",
 | 
						|
	}
 | 
						|
 | 
						|
	// ThemeUnicode is a predefined Theme that uses Unicode characters, displaying a graphic bar.
 | 
						|
	// It looks like "" (rendering will depend on font being used).
 | 
						|
	// It requires special symbols usually found in "nerd fonts" [2], or in Fira Code [1], and other sources.
 | 
						|
	// Configure it with OptionSetTheme(ThemeUnicode).
 | 
						|
	//
 | 
						|
	// [1] https://github.com/tonsky/FiraCode
 | 
						|
	// [2] https://www.nerdfonts.com/
 | 
						|
	ThemeUnicode = Theme{
 | 
						|
		Saucer:         "\uEE04", // 
 | 
						|
		SaucerHead:     "\uEE04", // 
 | 
						|
		SaucerPadding:  "\uEE01", // 
 | 
						|
		BarStart:       "\uEE00", // 
 | 
						|
		BarStartFilled: "\uEE03", // 
 | 
						|
		BarEnd:         "\uEE02", // 
 | 
						|
		BarEndFilled:   "\uEE05", // 
 | 
						|
	}
 | 
						|
)
 | 
						|
 | 
						|
// Option is the type all options need to adhere to
 | 
						|
type Option func(p *ProgressBar)
 | 
						|
 | 
						|
// OptionSetWidth sets the width of the bar
 | 
						|
func OptionSetWidth(s int) Option {
 | 
						|
	return func(p *ProgressBar) {
 | 
						|
		p.config.width = s
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// OptionSetSpinnerChangeInterval sets the spinner change interval
 | 
						|
// the spinner will change according to this value.
 | 
						|
// By default, this value is 100 * time.Millisecond
 | 
						|
// If you don't want to let this progressbar update by specified time interval
 | 
						|
// you can  set this value to zero, then the spinner will change each time rendered,
 | 
						|
// such as when Add() or Describe() was called
 | 
						|
func OptionSetSpinnerChangeInterval(interval time.Duration) Option {
 | 
						|
	return func(p *ProgressBar) {
 | 
						|
		p.config.spinnerChangeInterval = interval
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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 with.
 | 
						|
// There are two pre-defined themes you can use: ThemeASCII and ThemeUnicode.
 | 
						|
func OptionSetTheme(t Theme) Option {
 | 
						|
	return func(p *ProgressBar) {
 | 
						|
		p.config.theme = t
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// OptionShowElapsedTimeOnFinish will keep the display of elapsed time on finish.
 | 
						|
func OptionShowElapsedTimeOnFinish() Option {
 | 
						|
	return func(p *ProgressBar) {
 | 
						|
		p.config.showElapsedTimeOnFinish = true
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// OptionShowTotalBytes will keep the display of total bytes.
 | 
						|
func OptionShowTotalBytes(flag bool) Option {
 | 
						|
	return func(p *ProgressBar) {
 | 
						|
		p.config.showTotalBytes = flag
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// OptionUseIECUnits will enable IEC units (e.g. MiB) instead of the default
 | 
						|
// SI units (e.g. MB).
 | 
						|
func OptionUseIECUnits(val bool) Option {
 | 
						|
	return func(p *ProgressBar) {
 | 
						|
		p.config.useIECUnits = 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
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// OptionSetMaxDetailRow sets the max row of details
 | 
						|
// the row count should be less than the terminal height, otherwise it will not give you the output you want
 | 
						|
func OptionSetMaxDetailRow(row int) Option {
 | 
						|
	return func(p *ProgressBar) {
 | 
						|
		p.config.maxDetailRow = row
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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: state{
 | 
						|
			startTime:   time.Time{},
 | 
						|
			lastShown:   time.Time{},
 | 
						|
			counterTime: time.Time{},
 | 
						|
		},
 | 
						|
		config: config{
 | 
						|
			writer:                os.Stdout,
 | 
						|
			theme:                 ThemeDefault,
 | 
						|
			iterationString:       "it",
 | 
						|
			width:                 40,
 | 
						|
			max:                   max,
 | 
						|
			throttleDuration:      0 * time.Nanosecond,
 | 
						|
			elapsedTime:           max == -1,
 | 
						|
			predictTime:           true,
 | 
						|
			spinnerType:           9,
 | 
						|
			invisible:             false,
 | 
						|
			spinnerChangeInterval: 100 * time.Millisecond,
 | 
						|
			showTotalBytes:        true,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	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")
 | 
						|
	}
 | 
						|
 | 
						|
	if b.config.maxDetailRow < 0 {
 | 
						|
		panic("invalid max detail row, must be greater than 0")
 | 
						|
	}
 | 
						|
 | 
						|
	// ignoreLength if max bytes not known
 | 
						|
	if b.config.max == -1 {
 | 
						|
		b.lengthUnknown()
 | 
						|
	}
 | 
						|
 | 
						|
	b.config.maxHumanized, b.config.maxHumanizedSuffix = humanizeBytes(float64(b.config.max),
 | 
						|
		b.config.useIECUnits)
 | 
						|
 | 
						|
	if b.config.renderWithBlankState {
 | 
						|
		b.RenderBlank()
 | 
						|
	}
 | 
						|
 | 
						|
	// if the render time interval attribute is set
 | 
						|
	if b.config.spinnerChangeInterval != 0 && !b.config.invisible && b.config.ignoreLength {
 | 
						|
		go func() {
 | 
						|
			ticker := time.NewTicker(b.config.spinnerChangeInterval)
 | 
						|
			defer ticker.Stop()
 | 
						|
			for {
 | 
						|
				select {
 | 
						|
				case <-ticker.C:
 | 
						|
					if b.IsFinished() {
 | 
						|
						return
 | 
						|
					}
 | 
						|
					if b.IsStarted() {
 | 
						|
						b.lock.Lock()
 | 
						|
						b.render()
 | 
						|
						b.lock.Unlock()
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}()
 | 
						|
	}
 | 
						|
 | 
						|
	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),
 | 
						|
		OptionShowTotalBytes(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),
 | 
						|
		OptionShowTotalBytes(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),
 | 
						|
		OptionShowTotalBytes(true),
 | 
						|
		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),
 | 
						|
		OptionShowTotalBytes(true),
 | 
						|
		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 {
 | 
						|
	p.lock.Lock()
 | 
						|
	defer p.lock.Unlock()
 | 
						|
 | 
						|
	if p.config.invisible {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if p.state.currentNum == 0 {
 | 
						|
		p.state.lastShown = time.Time{}
 | 
						|
	}
 | 
						|
	return p.render()
 | 
						|
}
 | 
						|
 | 
						|
// StartWithoutRender will start the progress bar without rendering it
 | 
						|
// this method is created for the use case where you want to start the progress
 | 
						|
// but don't want to render it immediately.
 | 
						|
// If you want to start the progress and render it immediately, use RenderBlank instead,
 | 
						|
// or maybe you can use Add to start it automatically, but it will make the time calculation less precise.
 | 
						|
func (p *ProgressBar) StartWithoutRender() {
 | 
						|
	p.lock.Lock()
 | 
						|
	defer p.lock.Unlock()
 | 
						|
 | 
						|
	if p.IsStarted() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	p.state.startTime = time.Now()
 | 
						|
	// the counterTime should be set to the current time
 | 
						|
	p.state.counterTime = time.Now()
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
	if !p.config.ignoreLength {
 | 
						|
		p.state.currentBytes = float64(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)
 | 
						|
 | 
						|
	if p.state.counterTime.IsZero() {
 | 
						|
		p.state.counterTime = time.Now()
 | 
						|
	}
 | 
						|
 | 
						|
	// 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
 | 
						|
}
 | 
						|
 | 
						|
// AddDetail adds a detail to the progress bar. Only used when maxDetailRow is set to a value greater than 0
 | 
						|
func (p *ProgressBar) AddDetail(detail string) error {
 | 
						|
	if p.config.maxDetailRow == 0 {
 | 
						|
		return errors.New("maxDetailRow is set to 0, cannot add detail")
 | 
						|
	}
 | 
						|
	if p.IsFinished() {
 | 
						|
		return errors.New("cannot add detail to a finished progress bar")
 | 
						|
	}
 | 
						|
 | 
						|
	p.lock.Lock()
 | 
						|
	defer p.lock.Unlock()
 | 
						|
	if p.state.details == nil {
 | 
						|
		// if we add a detail before the first add, it will be weird that we have detail but don't have the progress bar in the top.
 | 
						|
		// so when we add the first detail, we will render the progress bar first.
 | 
						|
		if err := p.render(); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	p.state.details = append(p.state.details, detail)
 | 
						|
	if len(p.state.details) > p.config.maxDetailRow {
 | 
						|
		p.state.details = p.state.details[1:]
 | 
						|
	}
 | 
						|
	if err := p.renderDetails(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// renderDetails renders the details of the progress bar
 | 
						|
func (p *ProgressBar) renderDetails() error {
 | 
						|
	if p.config.invisible {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if p.state.finished {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if p.config.maxDetailRow == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	b := strings.Builder{}
 | 
						|
	b.WriteString("\n")
 | 
						|
 | 
						|
	// render the details row
 | 
						|
	for _, detail := range p.state.details {
 | 
						|
		b.WriteString(fmt.Sprintf("\u001B[K\r%s\n", detail))
 | 
						|
	}
 | 
						|
	// add empty lines to fill the maxDetailRow
 | 
						|
	for i := len(p.state.details); i < p.config.maxDetailRow; i++ {
 | 
						|
		b.WriteString("\u001B[K\n")
 | 
						|
	}
 | 
						|
 | 
						|
	// move the cursor up to the start of the details row
 | 
						|
	b.WriteString(fmt.Sprintf("\u001B[%dF", p.config.maxDetailRow+1))
 | 
						|
 | 
						|
	writeString(p.config, b.String())
 | 
						|
 | 
						|
	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 {
 | 
						|
	p.lock.Lock()
 | 
						|
	defer p.lock.Unlock()
 | 
						|
 | 
						|
	return int(p.config.max)
 | 
						|
}
 | 
						|
 | 
						|
// GetMax64 returns the current max
 | 
						|
func (p *ProgressBar) GetMax64() int64 {
 | 
						|
	p.lock.Lock()
 | 
						|
	defer p.lock.Unlock()
 | 
						|
 | 
						|
	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.lock.Lock()
 | 
						|
 | 
						|
	p.config.max = newMax
 | 
						|
 | 
						|
	if p.config.showBytes {
 | 
						|
		p.config.maxHumanized, p.config.maxHumanizedSuffix = humanizeBytes(float64(p.config.max),
 | 
						|
			p.config.useIECUnits)
 | 
						|
	}
 | 
						|
 | 
						|
	if newMax == -1 {
 | 
						|
		p.lengthUnknown()
 | 
						|
	} else {
 | 
						|
		p.lengthKnown(newMax)
 | 
						|
	}
 | 
						|
	p.lock.Unlock() // so p.Add can lock
 | 
						|
 | 
						|
	p.Add(0) // re-render
 | 
						|
}
 | 
						|
 | 
						|
// AddMax takes in a int
 | 
						|
// and adds it to the max
 | 
						|
// value of the progress bar
 | 
						|
func (p *ProgressBar) AddMax(added int) {
 | 
						|
	p.AddMax64(int64(added))
 | 
						|
}
 | 
						|
 | 
						|
// AddMax64 is basically
 | 
						|
// the same as AddMax,
 | 
						|
// but takes in a int64
 | 
						|
// to avoid casting
 | 
						|
func (p *ProgressBar) AddMax64(added int64) {
 | 
						|
	p.lock.Lock()
 | 
						|
 | 
						|
	p.config.max += added
 | 
						|
 | 
						|
	if p.config.showBytes {
 | 
						|
		p.config.maxHumanized, p.config.maxHumanizedSuffix = humanizeBytes(float64(p.config.max),
 | 
						|
			p.config.useIECUnits)
 | 
						|
	}
 | 
						|
 | 
						|
	if p.config.max == -1 {
 | 
						|
		p.lengthUnknown()
 | 
						|
	} else {
 | 
						|
		p.lengthKnown(p.config.max)
 | 
						|
	}
 | 
						|
	p.lock.Unlock() // so p.Add can lock
 | 
						|
 | 
						|
	p.Add(0) // re-render
 | 
						|
}
 | 
						|
 | 
						|
// IsFinished returns true if progress bar is completed
 | 
						|
func (p *ProgressBar) IsFinished() bool {
 | 
						|
	p.lock.Lock()
 | 
						|
	defer p.lock.Unlock()
 | 
						|
 | 
						|
	return p.state.finished
 | 
						|
}
 | 
						|
 | 
						|
// IsStarted returns true if progress bar is started
 | 
						|
func (p *ProgressBar) IsStarted() bool {
 | 
						|
	return !p.state.startTime.IsZero()
 | 
						|
}
 | 
						|
 | 
						|
// 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 !p.IsStarted() {
 | 
						|
		p.state.startTime = time.Now()
 | 
						|
	} else 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, if not yet finished.
 | 
						|
		if !p.state.finished {
 | 
						|
			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 {
 | 
						|
			io.Copy(p.config.writer, &p.config.stdBuffer)
 | 
						|
			renderProgressBar(p.config, &p.state)
 | 
						|
		}
 | 
						|
		if p.config.maxDetailRow > 0 {
 | 
						|
			p.renderDetails()
 | 
						|
			// put the cursor back to the last line of the details
 | 
						|
			writeString(p.config, fmt.Sprintf("\u001B[%dB\r\u001B[%dC", p.config.maxDetailRow, len(p.state.details[len(p.state.details)-1])))
 | 
						|
		}
 | 
						|
		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
 | 
						|
	io.Copy(p.config.writer, &p.config.stdBuffer)
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
// lengthUnknown sets the progress bar to ignore the length
 | 
						|
func (p *ProgressBar) lengthUnknown() {
 | 
						|
	p.config.ignoreLength = true
 | 
						|
	p.config.max = int64(p.config.width)
 | 
						|
	p.config.predictTime = false
 | 
						|
}
 | 
						|
 | 
						|
// lengthKnown sets the progress bar to do not ignore the length
 | 
						|
func (p *ProgressBar) lengthKnown(max int64) {
 | 
						|
	p.config.ignoreLength = false
 | 
						|
	p.config.max = max
 | 
						|
	p.config.predictTime = true
 | 
						|
}
 | 
						|
 | 
						|
// State returns the current state
 | 
						|
func (p *ProgressBar) State() State {
 | 
						|
	p.lock.Lock()
 | 
						|
	defer p.lock.Unlock()
 | 
						|
	s := State{}
 | 
						|
	s.CurrentNum = p.state.currentNum
 | 
						|
	s.Max = p.config.max
 | 
						|
	if p.config.ignoreLength {
 | 
						|
		s.Max = -1
 | 
						|
	}
 | 
						|
	s.CurrentPercent = float64(p.state.currentNum) / float64(p.config.max)
 | 
						|
	s.CurrentBytes = p.state.currentBytes
 | 
						|
	if p.IsStarted() {
 | 
						|
		s.SecondsSince = time.Since(p.state.startTime).Seconds()
 | 
						|
	} else {
 | 
						|
		s.SecondsSince = 0
 | 
						|
	}
 | 
						|
 | 
						|
	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) / 1024.0 / s.SecondsSince
 | 
						|
	s.Description = p.config.description
 | 
						|
	return s
 | 
						|
}
 | 
						|
 | 
						|
// StartHTTPServer starts an HTTP server dedicated to serving progress bar updates. This allows you to
 | 
						|
// display the status in various UI elements, such as an OS status bar with an `xbar` extension.
 | 
						|
// It is recommended to run this function in a separate goroutine to avoid blocking the main thread.
 | 
						|
//
 | 
						|
// hostPort specifies the address and port to bind the server to, for example, "0.0.0.0:19999".
 | 
						|
func (p *ProgressBar) StartHTTPServer(hostPort string) {
 | 
						|
	// for advanced users, we can return the data as json
 | 
						|
	http.HandleFunc("/state", func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		w.Header().Set("Content-Type", "text/json")
 | 
						|
		// since the state is a simple struct, we can just ignore the error
 | 
						|
		bs, _ := json.Marshal(p.State())
 | 
						|
		w.Write(bs)
 | 
						|
	})
 | 
						|
	// for others, we just return the description in a plain text format
 | 
						|
	http.HandleFunc("/desc", func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		w.Header().Set("Content-Type", "text/plain")
 | 
						|
		fmt.Fprintf(w,
 | 
						|
			"%d/%d, %.2f%%, %s left",
 | 
						|
			p.State().CurrentNum, p.State().Max, p.State().CurrentPercent*100,
 | 
						|
			(time.Second * time.Duration(p.State().SecondsLeft)).String(),
 | 
						|
		)
 | 
						|
	})
 | 
						|
	log.Fatal(http.ListenAndServe(hostPort, nil))
 | 
						|
}
 | 
						|
 | 
						|
// 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 := uniseg.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, c.useIECUnits)
 | 
						|
				if currentSuffix == c.maxHumanizedSuffix {
 | 
						|
					if c.showTotalBytes {
 | 
						|
						sb.WriteString(fmt.Sprintf("%s/%s%s",
 | 
						|
							currentHumanize, c.maxHumanized, c.maxHumanizedSuffix))
 | 
						|
					} else {
 | 
						|
						sb.WriteString(fmt.Sprintf("%s%s",
 | 
						|
							currentHumanize, c.maxHumanizedSuffix))
 | 
						|
					}
 | 
						|
				} else if c.showTotalBytes {
 | 
						|
					sb.WriteString(fmt.Sprintf("%s%s/%s%s",
 | 
						|
						currentHumanize, currentSuffix, c.maxHumanized, c.maxHumanizedSuffix))
 | 
						|
				} else {
 | 
						|
					sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix))
 | 
						|
				}
 | 
						|
			} else if c.showTotalBytes {
 | 
						|
				sb.WriteString(fmt.Sprintf("%.0f/%d", s.currentBytes, c.max))
 | 
						|
			} else {
 | 
						|
				sb.WriteString(fmt.Sprintf("%.0f", s.currentBytes))
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if c.showBytes {
 | 
						|
				currentHumanize, currentSuffix := humanizeBytes(s.currentBytes, c.useIECUnits)
 | 
						|
				sb.WriteString(fmt.Sprintf("%s%s", currentHumanize, currentSuffix))
 | 
						|
			} else if c.showTotalBytes {
 | 
						|
				sb.WriteString(fmt.Sprintf("%.0f/%s", s.currentBytes, "-"))
 | 
						|
			} else {
 | 
						|
				sb.WriteString(fmt.Sprintf("%.0f", 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, c.useIECUnits)
 | 
						|
		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 := "", "", "", ""
 | 
						|
	barStart, barEnd := c.theme.BarStart, c.theme.BarEnd
 | 
						|
	if s.finished && c.theme.BarEndFilled != "" {
 | 
						|
		barEnd = c.theme.BarEndFilled
 | 
						|
	}
 | 
						|
 | 
						|
	// show time prediction in "current/total" seconds format
 | 
						|
	switch {
 | 
						|
	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 || c.showElapsedTimeOnFinish:
 | 
						|
		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 || s.currentPercent > 0) && c.theme.BarStartFilled != "" {
 | 
						|
		barStart = c.theme.BarStartFilled
 | 
						|
	}
 | 
						|
	if s.currentSaucerSize > 0 {
 | 
						|
		if c.ignoreLength {
 | 
						|
			saucer = strings.Repeat(c.theme.SaucerPadding, s.currentSaucerSize-1)
 | 
						|
		} 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
 | 
						|
		}
 | 
						|
 | 
						|
		var spinner string
 | 
						|
		if c.spinnerChangeInterval != 0 {
 | 
						|
			// if the spinner is changed according to an interval, calculate it
 | 
						|
			spinner = selectedSpinner[int(math.Round(math.Mod(float64(time.Since(s.startTime).Nanoseconds()/c.spinnerChangeInterval.Nanoseconds()), float64(len(selectedSpinner)))))]
 | 
						|
		} else {
 | 
						|
			// if the spinner is changed according to the number render was called
 | 
						|
			spinner = selectedSpinner[s.spinnerIdx]
 | 
						|
			s.spinnerIdx = (s.spinnerIdx + 1) % 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,
 | 
						|
			barStart,
 | 
						|
			saucer,
 | 
						|
			saucerHead,
 | 
						|
			strings.Repeat(c.theme.SaucerPadding, repeatAmount),
 | 
						|
			barEnd,
 | 
						|
			sb.String())
 | 
						|
		if (s.currentPercent == 100 && c.showElapsedTimeOnFinish) || c.elapsedTime {
 | 
						|
			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,
 | 
						|
				barStart,
 | 
						|
				saucer,
 | 
						|
				saucerHead,
 | 
						|
				strings.Repeat(c.theme.SaucerPadding, repeatAmount),
 | 
						|
				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,
 | 
						|
				barStart,
 | 
						|
				saucer,
 | 
						|
				saucerHead,
 | 
						|
				strings.Repeat(c.theme.SaucerPadding, repeatAmount),
 | 
						|
				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)
 | 
						|
	err = p.Add(n)
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// Read implement io.Reader
 | 
						|
func (p *ProgressBar) Read(b []byte) (n int, err error) {
 | 
						|
	n = len(b)
 | 
						|
	err = p.Add(n)
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func (p *ProgressBar) Close() (err error) {
 | 
						|
	err = 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, iec bool) (string, string) {
 | 
						|
	sizes := []string{" B", " kB", " MB", " GB", " TB", " PB", " EB"}
 | 
						|
	base := 1000.0
 | 
						|
 | 
						|
	if iec {
 | 
						|
		sizes = []string{" B", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB"}
 | 
						|
		base = 1024.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
 | 
						|
}
 | 
						|
 | 
						|
func shouldCacheOutput(pb *ProgressBar) bool {
 | 
						|
	return !pb.state.finished && !pb.state.exit && !pb.config.invisible
 | 
						|
}
 | 
						|
 | 
						|
func Bprintln(pb *ProgressBar, a ...interface{}) (int, error) {
 | 
						|
	pb.lock.Lock()
 | 
						|
	defer pb.lock.Unlock()
 | 
						|
	if !shouldCacheOutput(pb) {
 | 
						|
		return fmt.Fprintln(pb.config.writer, a...)
 | 
						|
	} else {
 | 
						|
		return fmt.Fprintln(&pb.config.stdBuffer, a...)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func Bprintf(pb *ProgressBar, format string, a ...interface{}) (int, error) {
 | 
						|
	pb.lock.Lock()
 | 
						|
	defer pb.lock.Unlock()
 | 
						|
	if !shouldCacheOutput(pb) {
 | 
						|
		return fmt.Fprintf(pb.config.writer, format, a...)
 | 
						|
	} else {
 | 
						|
		return fmt.Fprintf(&pb.config.stdBuffer, format, a...)
 | 
						|
	}
 | 
						|
}
 |