127 lines
2.4 KiB
Go
127 lines
2.4 KiB
Go
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)
|
|
}
|