package ui import ( "fmt" "strings" "time" ) // Progress tracks and displays progress type Progress struct { Total int Current int StartTime time.Time Message string } // NewProgress creates a new progress tracker func NewProgress(total int, message string) *Progress { return &Progress{ Total: total, Current: 0, StartTime: time.Now(), Message: message, } } // Increment advances progress by one func (p *Progress) Increment() { p.Current++ p.Print() } // SetMessage updates the current message func (p *Progress) SetMessage(msg string) { p.Message = msg } // Print displays current progress func (p *Progress) Print() { elapsed := time.Since(p.StartTime) percent := float64(p.Current) / float64(p.Total) * 100 bar := p.bar(20) fmt.Printf("\r[%s] %.1f%% (%d/%d) %s - %s ", bar, percent, p.Current, p.Total, elapsed.Round(time.Second), p.Message) } // Complete marks progress as done func (p *Progress) Complete() { p.Current = p.Total elapsed := time.Since(p.StartTime) fmt.Printf("\r[%s] 100%% (%d/%d) %s - Complete\n", p.bar(20), p.Total, p.Total, elapsed.Round(time.Second)) } func (p *Progress) bar(width int) string { filled := int(float64(p.Current) / float64(p.Total) * float64(width)) empty := width - filled return strings.Repeat("█", filled) + strings.Repeat("░", empty) } // Spinner displays a spinning indicator type Spinner struct { Message string stop chan bool done chan bool } // NewSpinner creates a new spinner func NewSpinner(message string) *Spinner { return &Spinner{ Message: message, stop: make(chan bool), done: make(chan bool), } } // Start begins the spinner animation func (s *Spinner) Start() { go func() { frames := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} i := 0 for { select { case <-s.stop: s.done <- true return default: fmt.Printf("\r%s %s", frames[i%len(frames)], s.Message) i++ time.Sleep(100 * time.Millisecond) } } }() } // Stop stops the spinner func (s *Spinner) Stop() { s.stop <- true <-s.done fmt.Printf("\r%s\n", strings.Repeat(" ", len(s.Message)+5)) } // StopWithMessage stops spinner with a final message func (s *Spinner) StopWithMessage(msg string) { s.stop <- true <-s.done fmt.Printf("\r%s\n", msg) }