package main import ( "fmt" "os" "strings" ) const ( cReset = "\x1b[0m" cBold = "\x1b[1m" cDim = "\x1b[2m" cRed = "\x1b[31m" cGreen = "\x1b[32m" cYellow = "\x1b[33m" cCyan = "\x1b[36m" cGrey = "\x1b[90m" ) var useColor bool func initColor(mode string) error { switch mode { case "always": useColor = true case "never": useColor = false case "auto": if os.Getenv("NO_COLOR") != "" || os.Getenv("TERM") == "dumb" { useColor = false return nil } st, err := os.Stdout.Stat() useColor = err == nil && st.Mode()&os.ModeCharDevice != 0 default: return fmt.Errorf("unknown color mode %q (want auto, always, never)", mode) } return nil } func paint(s, code string) string { if !useColor || s == "" { return s } return code + s + cReset } func stripANSI(s string) string { var b strings.Builder for i := 0; i < len(s); { if s[i] == 0x1b { for i < len(s) && s[i] != 'm' { i++ } if i < len(s) { i++ } continue } b.WriteByte(s[i]) i++ } return b.String() } func visWidth(s string) int { return len([]rune(stripANSI(s))) } func pad(s string, w int, right bool) string { gap := w - visWidth(s) if gap < 0 { gap = 0 } if right { return strings.Repeat(" ", gap) + s } return s + strings.Repeat(" ", gap) } func commas(v uint64) string { s := fmt.Sprintf("%d", v) if len(s) <= 3 { return s } var parts []string for len(s) > 3 { parts = append([]string{s[len(s)-3:]}, parts...) s = s[:len(s)-3] } return strings.Join(append([]string{s}, parts...), ",") } func humanBytes(b uint64) string { const unit = 1000.0 v := float64(b) for _, suffix := range []string{"B", "kB", "MB", "GB", "TB"} { if v < unit { return fmt.Sprintf("%.1f %s", v, suffix) } v /= unit } return fmt.Sprintf("%.1f PB", v) } type colSpec struct { title string width int right bool } type streamTable struct { cols []colSpec sinceHeader int headerEvery int } func (t *streamTable) headerLines() []string { var titles, rules []string for _, c := range t.cols { titles = append(titles, pad(c.title, c.width, c.right)) rules = append(rules, strings.Repeat("─", c.width)) } return []string{ paint(strings.Join(titles, " "), cBold), paint(strings.Join(rules, " "), cGrey), } } func (t *streamTable) emit(cells []string) []string { var out []string if t.sinceHeader == 0 || (t.headerEvery > 0 && t.sinceHeader >= t.headerEvery) { out = append(out, t.headerLines()...) t.sinceHeader = 0 } var padded []string for i, c := range t.cols { v := "" if i < len(cells) { v = cells[i] } padded = append(padded, pad(v, c.width, c.right)) } t.sinceHeader++ return append(out, strings.Join(padded, " ")) } func renderBox(title string, headers []string, rights []bool, rows [][]string) string { n := len(headers) widths := make([]int, n) for i, h := range headers { widths[i] = visWidth(h) } for _, r := range rows { for i := 0; i < n && i < len(r); i++ { if w := visWidth(r[i]); w > widths[i] { widths[i] = w } } } line := func(l, m, r string) string { var parts []string for _, w := range widths { parts = append(parts, strings.Repeat("─", w+2)) } return paint(l+strings.Join(parts, m)+r, cGrey) } rowText := func(cells []string, bold bool) string { var parts []string for i := 0; i < n; i++ { v := "" if i < len(cells) { v = cells[i] } if bold { v = paint(v, cBold) } parts = append(parts, " "+pad(v, widths[i], rights[i])+" ") } bar := paint("│", cGrey) return bar + strings.Join(parts, bar) + bar } var b strings.Builder if title != "" { b.WriteString(paint(title, cBold+cCyan) + "\n") } b.WriteString(line("┌", "┬", "┐") + "\n") b.WriteString(rowText(headers, true) + "\n") b.WriteString(line("├", "┼", "┤") + "\n") for _, r := range rows { b.WriteString(rowText(r, false) + "\n") } b.WriteString(line("└", "┴", "┘")) return b.String() } func statusCell(v uint64) string { s := commas(v) if v == 0 { return paint(s, cGreen) } return paint(s, cRed) } func gapCell(v int64) string { if v <= 0 { return paint("0", cGreen) } return paint(commas(uint64(v)), cYellow) } func lostCell(v int64) string { if v <= 0 { return paint("0", cGreen) } return paint(commas(uint64(v)), cRed) } func rateCell(gb float64, target float64) string { s := fmt.Sprintf("%.2f", gb) switch { case gb >= target*0.995: return paint(s, cGreen) case gb >= target*0.90: return paint(s, cYellow) default: return paint(s, cRed) } }