// Package progress implements a simple command line progress bar. // // Example: // // import ( // time" // // "gitlab.com/rhogenson/progress-bar" // ) // // func main() { // b := progress.New(100) // for i := range 100 { // b.Set(i) // b.Print() // time.Sleep(time.Second) // } // } package progress import ( "fmt" "os" "strings" "time" "gitlab.com/rhogenson/vecdeque" "golang.org/x/term" ) const measurements = 20 type measurement struct { t time.Time i int } // Bar is a progess bar. type Bar struct { max int measurements vecdeque.DQ[measurement] cols int } func New(max int) *Bar { b := &Bar{max: max} b.measurements.Grow(measurements) return b } // Set sets the current value to val. func (b *Bar) Set(val int) { if b.measurements.Len() == measurements { b.measurements.PopFront() } b.measurements.PushBack(measurement{time.Now(), val}) } // Print shows the progress bar on standard error. func (b *Bar) Print() { if b.measurements.Len() == 0 { return } first := b.measurements.Get(0) last := b.measurements.Get(b.measurements.Len() - 1) deltaT := last.t.Sub(first.t) delta := last.i - first.i eta := time.Duration(-1) if delta != 0 { eta = time.Duration(float64(b.max-last.i) / float64(delta) * float64(deltaT)) } p := last.i * b.cols / b.max if b.cols == 0 { var err error if b.cols, _, err = term.GetSize(int(os.Stderr.Fd())); err != nil { fmt.Fprintf(os.Stderr, "Warning: unable to determine terminal size: %s\n", err) } else { fmt.Fprintf(os.Stderr, "%s>\n", strings.Repeat("=", max(p-1, 0))) } } else { fmt.Fprintf(os.Stderr, "\033[2F\033[J%s>\n", strings.Repeat("=", max(p-1, 0))) } if eta < 0 { fmt.Fprintln(os.Stderr, "ETA: calculating...") } else { fmt.Fprintf(os.Stderr, "ETA: %s\n", eta.Round(time.Second)) } }