package progressbar import ( "errors" "fmt" "io" "os" "strings" "sync" "time" ) // ProgressBar is a thread-safe, simple // progress bar type ProgressBar struct { max int // max number of the counter size int // size of the saucer currentNum int currentPercent int lastPercent int currentSaucerSize int lastShown time.Time startTime time.Time w io.Writer // symbols symbolFinished string symbolLeft string leftBookend string rightBookend string sync.RWMutex } // New returns a new ProgressBar // with the specified maximum func New(max int) *ProgressBar { return &ProgressBar{ max: max, size: 40, symbolFinished: "█", symbolLeft: " ", leftBookend: "|", rightBookend: "|", w: os.Stdout, lastShown: time.Now(), startTime: time.Now(), } } // Reset will reset the clock that is used // to calculate current time and the time left. func (p *ProgressBar) Reset() { p.Lock() defer p.Unlock() p.lastShown = time.Now() p.startTime = time.Now() p.currentNum = 0 } // SetMax sets the total number of the progress bar func (p *ProgressBar) SetMax(num int) { p.Lock() defer p.Unlock() p.max = num } // SetSize sets the size of the progress bar. func (p *ProgressBar) SetSize(size int) { p.Lock() defer p.Unlock() p.size = size } // Add with increase the current count on the progress bar func (p *ProgressBar) Add(num int) error { p.RLock() currentNum := p.currentNum p.RUnlock() return p.Set(currentNum + num) } // Set will change the current count on the progress bar func (p *ProgressBar) Set(num int) error { p.Lock() if p.max == 0 { p.Unlock() return errors.New("max must be greater than 0") } p.currentNum = num percent := float64(p.currentNum) / float64(p.max) p.currentSaucerSize = int(percent * float64(p.size)) p.currentPercent = int(percent * 100) updateBar := p.currentPercent != p.lastPercent && p.currentPercent > 0 p.lastPercent = p.currentPercent p.Unlock() if updateBar { return p.Show() } return nil } // Show will print the current progress bar func (p *ProgressBar) Show() error { p.RLock() defer p.RUnlock() if p.currentNum > p.max { return errors.New("current number exceeds max") } secondsLeft := time.Since(p.startTime).Seconds() / float64(p.currentNum) * (float64(p.max) - float64(p.currentNum)) s := fmt.Sprintf("\r%4d%% %s%s%s%s [%s:%s] ", p.currentPercent, p.leftBookend, strings.Repeat(p.symbolFinished, p.currentSaucerSize), strings.Repeat(p.symbolLeft, p.size-p.currentSaucerSize), p.rightBookend, (time.Duration(time.Since(p.startTime).Seconds()) * time.Second).String(), (time.Duration(secondsLeft) * time.Second).String(), ) _, err := io.WriteString(p.w, s) if err != nil { return err } if f, ok := p.w.(*os.File); ok { f.Sync() } return nil }