// Package progress implements a simple command line progress bar. // // Example: // // import ( // time" // // "gitlab.com/rhogenson/progress-bar/progress" // ) // // func main() { // b := new(progress.Bar) // for i := range 100 { // b.Set(float64(i)/100) // 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 float64 } // Bar is a progess bar. The zero value is ready for use. type Bar struct { measurements vecdeque.DQ[measurement] cols int } // Set sets the current value to val. val must be between 0 and 1, inclusive. func (b *Bar) Set(val float64) { 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() { 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(deltaT) * (1 - last.i) / delta) } p := int(last.i * float64(b.cols)) 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)) } }