package progress import ( "fmt" "math" "regexp" "strconv" "strings" "sync" "golang.org/x/term" ) var regex = regexp.MustCompile("\033\\[[0-9;]+m") //║░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░║ 40 characters type ProgressInfo struct { Size int64 Desc string ClearOnFinish bool } type ProgressBar struct { lock sync.Mutex maxBytes int64 currentBytes int64 hasStarted bool currentPercent int desc string clearFinish bool textWidth int } func New(info ProgressInfo) *ProgressBar { return &ProgressBar{ maxBytes: info.Size, desc: info.Desc, clearFinish: info.ClearOnFinish, } } func (p *ProgressBar) render(final bool) { p.lock.Lock() defer p.lock.Unlock() var sb strings.Builder numFilled := math.Round((float64(p.currentBytes) / float64(p.maxBytes)) * 40) numBlank := 40 - numFilled donePercent := math.Round(float64(p.currentBytes) / float64(p.maxBytes) * 100) if int(donePercent) == p.currentPercent { return } var blockColor string if final { blockColor = "34" } else { blockColor = "214" } sb.WriteString(p.desc) sb.WriteString(" ") percentString := strconv.Itoa(int(donePercent)) + "%" blankPercent := 4 - len(percentString) for i := 0; i < blankPercent; i++ { sb.WriteString(" ") } sb.WriteString(percentString) sb.WriteString("\033[1m\033[38;5;247m [\033[0m") for i := 0; i < int(numFilled); i++ { sb.WriteString(fmt.Sprintf("\033[38;5;%sm█\033[0m", blockColor)) } if numFilled < 40 { numBlank = numBlank - 1 sb.WriteString("\033[38;5;214m▒\033[0m") for i := 0; i < int(numBlank); i++ { sb.WriteString("\033[38;5;247m░\033[0m") } } else { for i := 0; i < int(numBlank); i++ { sb.WriteString("\033[38;5;247m░\033[0m") } } sb.WriteString("\033[1m\033[38;5;247m]\033[0m") width, _, err := term.GetSize(0) if err != nil { return } var lineNum int if width == 0 { lineNum = 1 } else { lineNum = ((len(removeANSIEscapeCodes(sb.String())) + width - 1) / width) } if p.hasStarted { for i := 0; i < lineNum; i++ { if i == lineNum-1 { fmt.Println("\033[A\033[0G\033[K" + sb.String()) } else { fmt.Println("\033[A\033[0G\033[K") } } } else { fmt.Println(sb.String()) p.hasStarted = true } p.textWidth = len(removeANSIEscapeCodes(sb.String())) } func (p *ProgressBar) Add(num int64) { p.currentBytes = p.currentBytes + num p.render(false) } func (p *ProgressBar) Close() (err error) { if p.clearFinish { width, _, err := term.GetSize(0) if err != nil { return err } var lineNum int if width == 0 { lineNum = 1 } else { lineNum = ((p.textWidth + width - 1) / width) } for i := 0; i < lineNum; i++ { fmt.Println("\033[A\033[0G\033[K") } } else { p.render(true) } return } func (p *ProgressBar) Write(b []byte) (n int, err error) { n = len(b) p.Add(int64(n)) return } func (p *ProgressBar) Read(b []byte) (n int, err error) { p.Add(int64(n)) return } func removeANSIEscapeCodes(input string) string { return regex.ReplaceAllString(input, "") }