190 lines
3.6 KiB
Go
190 lines
3.6 KiB
Go
package progress
|
|
|
|
import (
|
|
"egtyl.xyz/omnibill/tui"
|
|
"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) {
|
|
|
|
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 && !final {
|
|
return
|
|
}
|
|
p.currentPercent = int(donePercent)
|
|
|
|
var blockColor string
|
|
|
|
if final {
|
|
blockColor = tui.FgColorGreen
|
|
} else {
|
|
blockColor = tui.FgColorGold
|
|
}
|
|
|
|
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(tui.Format(tui.FmtBold, tui.FgColorGrey) + " [" + tui.FmtReset)
|
|
//sb.WriteString("\033[1m\033[38;5;247m [\033[0m")
|
|
|
|
for i := 0; i < int(numFilled); i++ {
|
|
|
|
sb.WriteString(blockColor + "█" + tui.FmtReset)
|
|
//sb.WriteString(fmt.Sprintf("%sm█\033[0m", blockColor))
|
|
|
|
}
|
|
|
|
if numFilled < 40 {
|
|
|
|
numBlank = numBlank - 1
|
|
|
|
sb.WriteString(tui.FgColorGold + "▒" + tui.FmtReset)
|
|
//sb.WriteString("\033[38;5;214m▒\033[0m")
|
|
|
|
for i := 0; i < int(numBlank); i++ {
|
|
sb.WriteString(tui.FgColorGrey + "░" + tui.FmtReset)
|
|
//sb.WriteString("\033[38;5;247m░\033[0m")
|
|
}
|
|
|
|
} else {
|
|
|
|
for i := 0; i < int(numBlank); i++ {
|
|
sb.WriteString(tui.FgColorGrey + "░" + tui.FmtReset)
|
|
//sb.WriteString("\033[38;5;247m░\033[0m")
|
|
}
|
|
|
|
}
|
|
|
|
sb.WriteString(tui.Format(tui.FmtBold, tui.FgColorGrey) + "]" + tui.FmtReset)
|
|
//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.lock.Lock()
|
|
p.currentBytes = p.currentBytes + num
|
|
|
|
p.render(false)
|
|
p.lock.Unlock()
|
|
}
|
|
|
|
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, "")
|
|
}
|