refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
210
modules/bonfire/widgets.go
Normal file
210
modules/bonfire/widgets.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package bonfire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var spinnerFrames = []rune{'⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'}
|
||||
|
||||
const (
|
||||
boxTopLeft = "┌"
|
||||
boxTopRight = "┐"
|
||||
boxBottomLeft = "└"
|
||||
boxBottomRight = "┘"
|
||||
boxHorizontal = "─"
|
||||
boxVertical = "│"
|
||||
boxTeeRight = "├"
|
||||
boxTeeLeft = "┤"
|
||||
boxTeeDown = "┬"
|
||||
boxTeeUp = "┴"
|
||||
boxCross = "┼"
|
||||
)
|
||||
|
||||
func (c *console) Spinner(message string, fn func() error) error {
|
||||
if fn == nil {
|
||||
fn = func() error { return nil }
|
||||
}
|
||||
if !c.tty {
|
||||
c.Printf("[...] %s\n", message)
|
||||
return fn()
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
i := 0
|
||||
ticker := time.NewTicker(80 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
c.Printf("\r%s %s", c.style(ansiCyan, string(spinnerFrames[0])), message)
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
i++
|
||||
frame := spinnerFrames[i%len(spinnerFrames)]
|
||||
c.Printf("\r%s %s", c.style(ansiCyan, string(frame)), message)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := fn()
|
||||
close(done)
|
||||
wg.Wait()
|
||||
if err != nil {
|
||||
c.Printf("\r%s %s\n", c.style(ansiRed, symCross), message)
|
||||
return err
|
||||
}
|
||||
c.Printf("\r%s %s\n", c.style(ansiGreen, symCheck), message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *console) Progress(total int, fn func(Progress) error) error {
|
||||
if total < 1 {
|
||||
total = 1
|
||||
}
|
||||
bar := &progressBar{c: c, total: total, lastPct: -1}
|
||||
if fn == nil {
|
||||
return nil
|
||||
}
|
||||
err := fn(bar)
|
||||
if c.tty && bar.current >= bar.total {
|
||||
c.Println()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type progressBar struct {
|
||||
c *console
|
||||
total int
|
||||
current int
|
||||
lastPct int
|
||||
}
|
||||
|
||||
func (p *progressBar) Advance(step int) {
|
||||
if step < 1 {
|
||||
step = 1
|
||||
}
|
||||
p.current += step
|
||||
if p.current > p.total {
|
||||
p.current = p.total
|
||||
}
|
||||
pct := 0
|
||||
if p.total > 0 {
|
||||
pct = p.current * 100 / p.total
|
||||
}
|
||||
if p.c.tty {
|
||||
p.renderTTY(pct)
|
||||
return
|
||||
}
|
||||
if p.lastPct >= 0 && pct/10 == p.lastPct/10 && pct != 100 {
|
||||
return
|
||||
}
|
||||
if pct == 0 && p.lastPct < 0 {
|
||||
p.lastPct = 0
|
||||
return
|
||||
}
|
||||
if pct%10 == 0 || pct == 100 {
|
||||
p.c.Printf("[%d/%d] %d%%\n", p.current, p.total, pct)
|
||||
p.lastPct = pct
|
||||
}
|
||||
}
|
||||
|
||||
func (p *progressBar) renderTTY(pct int) {
|
||||
width := 40
|
||||
filled := 0
|
||||
if p.total > 0 {
|
||||
filled = p.current * width / p.total
|
||||
}
|
||||
if filled > width {
|
||||
filled = width
|
||||
}
|
||||
bar := strings.Repeat("█", filled) + strings.Repeat(" ", width-filled)
|
||||
color := progressColor(pct)
|
||||
p.c.Printf("\r %s %5.1f%% %d/%d", p.c.style(color, bar), float64(pct), p.current, p.total)
|
||||
}
|
||||
|
||||
func progressColor(pct int) string {
|
||||
switch {
|
||||
case pct >= 100:
|
||||
return "\x1b[38;5;34m"
|
||||
case pct >= 50:
|
||||
return "\x1b[38;5;36m"
|
||||
default:
|
||||
return "\x1b[38;5;37m"
|
||||
}
|
||||
}
|
||||
|
||||
func (c *console) Table(headers []string, rows [][]string) {
|
||||
if !c.tty {
|
||||
c.Println(strings.Join(headers, "\t"))
|
||||
for _, row := range rows {
|
||||
c.Println(strings.Join(padRow(row, len(headers)), "\t"))
|
||||
}
|
||||
return
|
||||
}
|
||||
cols := len(headers)
|
||||
all := make([][]string, 0, 1+len(rows))
|
||||
all = append(all, headers)
|
||||
for _, row := range rows {
|
||||
all = append(all, padRow(row, cols))
|
||||
}
|
||||
widths := make([]int, cols)
|
||||
for _, row := range all {
|
||||
for i, cell := range row {
|
||||
if n := utf8.RuneCountInString(cell); n > widths[i] {
|
||||
widths[i] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Println(boxLine(widths, boxTopLeft, boxTeeDown, boxTopRight))
|
||||
c.Println(boxRow(headers, widths, true, c))
|
||||
c.Println(boxLine(widths, boxTeeRight, boxCross, boxTeeLeft))
|
||||
for _, row := range rows {
|
||||
c.Println(boxRow(padRow(row, cols), widths, false, c))
|
||||
}
|
||||
c.Println(boxLine(widths, boxBottomLeft, boxTeeUp, boxBottomRight))
|
||||
}
|
||||
|
||||
func padRow(row []string, n int) []string {
|
||||
out := make([]string, n)
|
||||
for i := 0; i < n; i++ {
|
||||
if i < len(row) {
|
||||
out[i] = row[i]
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func boxLine(widths []int, left, mid, right string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString(left)
|
||||
for i, w := range widths {
|
||||
if i > 0 {
|
||||
b.WriteString(mid)
|
||||
}
|
||||
b.WriteString(strings.Repeat(boxHorizontal, w+2))
|
||||
}
|
||||
b.WriteString(right)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func boxRow(cells []string, widths []int, header bool, c *console) string {
|
||||
var b strings.Builder
|
||||
b.WriteString(boxVertical)
|
||||
for i, cell := range cells {
|
||||
pad := widths[i] - utf8.RuneCountInString(cell)
|
||||
text := cell
|
||||
if header {
|
||||
text = c.style("\x1b[1m", cell)
|
||||
}
|
||||
fmt.Fprintf(&b, " %s%s %s", text, strings.Repeat(" ", pad), boxVertical)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
Reference in New Issue
Block a user