package utils import ( "fmt" "os" "time" ) type ProgressBar struct { total int current int width int startTime time.Time label string } func NewProgressBar(total int, label string) *ProgressBar { return &ProgressBar{ total: total, current: 0, width: 40, startTime: time.Now(), label: label, } } func (p *ProgressBar) Update(current int) { p.current = current p.render() } func (p *ProgressBar) Increment() { p.current++ p.render() } func (p *ProgressBar) render() { if p.total == 0 { return } percent := float64(p.current) / float64(p.total) filled := int(percent * float64(p.width)) empty := p.width - filled bar := "" for i := 0; i < filled; i++ { bar += "█" } for i := 0; i < empty; i++ { bar += "░" } elapsed := time.Since(p.startTime) var eta time.Duration if p.current > 0 && elapsed.Seconds() > 0 { rate := float64(p.current) / elapsed.Seconds() if rate > 0 { remaining := float64(p.total-p.current) / rate eta = time.Duration(remaining) * time.Second if eta < 0 { eta = 0 } } } if eta > 0 { fmt.Fprintf(os.Stderr, "\r%s [%s] %d/%d (%.1f%%) ETA: %s", p.label, bar, p.current, p.total, percent*100, eta.Round(time.Second)) } else { fmt.Fprintf(os.Stderr, "\r%s [%s] %d/%d (%.1f%%)", p.label, bar, p.current, p.total, percent*100) } if p.current >= p.total { fmt.Fprintf(os.Stderr, "\n") } } func (p *ProgressBar) Finish() { p.current = p.total p.render() } type Spinner struct { chars []string index int label string stopChan chan bool doneChan chan bool } func NewSpinner(label string) *Spinner { return &Spinner{ chars: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, index: 0, label: label, stopChan: make(chan bool), doneChan: make(chan bool), } } func (s *Spinner) Start() { go func() { ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for { select { case <-s.stopChan: fmt.Fprintf(os.Stderr, "\r%s %s\n", s.chars[s.index], s.label) s.doneChan <- true return case <-ticker.C: fmt.Fprintf(os.Stderr, "\r%s %s", s.chars[s.index], s.label) s.index = (s.index + 1) % len(s.chars) } } }() } func (s *Spinner) Stop() { s.stopChan <- true <-s.doneChan } func (s *Spinner) StopWithMessage(message string) { s.Stop() fmt.Fprintf(os.Stderr, "%s\n", message) }