97 lines
2.8 KiB
Go
97 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Colors for the prompt, banner and output, as 24-bit truecolor escapes: the
|
|
// Catppuccin Mocha accents with a warm white and three greys of its own.
|
|
const (
|
|
cReset = "\033[0m"
|
|
cBold = "\033[1m"
|
|
cDim = "\033[2m"
|
|
|
|
cYellow = "\033[38;2;249;226;175m" // #f9e2af
|
|
cOrange = "\033[38;2;250;179;135m" // #fab387
|
|
cRed = "\033[38;2;243;139;168m" // #f38ba8
|
|
cGreen = "\033[38;2;148;226;213m" // #94e2d5
|
|
cBlue = "\033[38;2;180;190;254m" // #b4befe
|
|
cPink = "\033[38;2;245;178;247m" // #f5b2f7
|
|
cViolet = "\033[38;2;203;166;247m" // #cba6f7
|
|
cWhite = "\033[38;2;240;240;234m" // #f0f0ea
|
|
cGrey = "\033[38;2;170;170;187m" // #aaaabb
|
|
cDark = "\033[38;2;119;119;136m" // #777788
|
|
cDarker = "\033[38;2;68;68;85m" // #444455
|
|
)
|
|
|
|
// col wraps s in color c, but only when color output is enabled.
|
|
func col(c, s string) string {
|
|
if useColor {
|
|
return c + s + cReset
|
|
}
|
|
return s
|
|
}
|
|
|
|
// errorln prints an error/status message in red (when color is enabled).
|
|
func errorln(msg string) {
|
|
fmt.Println(col(cRed, msg))
|
|
}
|
|
|
|
// padRight pads s with trailing spaces to a width of n columns. It counts
|
|
// runes, not bytes: the overview pads fields holding ↑ ↓ ✓, each of which is
|
|
// one column wide but three bytes long.
|
|
func padRight(s string, n int) string {
|
|
if l := utf8.RuneCountInString(s); l < n {
|
|
return s + strings.Repeat(" ", n-l)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// formatRepoList renders the server listing for `list`: the name first, in a
|
|
// column wide enough for the longest one, then the date, and for archives the
|
|
// size. Names come first because that is what the eye scans for; putting the
|
|
// ragged date there instead is what made the old output hard to read.
|
|
//
|
|
// The order is left as it arrives: `ls -ltr` sorts by modification time, and
|
|
// `push` touches the bare repository, so the most recently worked-on project
|
|
// ends up closest to the prompt.
|
|
func formatRepoList(entries []lsEntry, withSize bool) string {
|
|
width := 0
|
|
for _, e := range entries {
|
|
if len(e.name) > width {
|
|
width = len(e.name)
|
|
}
|
|
}
|
|
var b strings.Builder
|
|
for _, e := range entries {
|
|
fmt.Fprintf(&b, " %s %s", col(cGreen, padRight(e.name, width)), col(cYellow, e.date))
|
|
if withSize {
|
|
fmt.Fprintf(&b, " %s", col(cDark, fmt.Sprintf("%7s", humanSize(e.size))))
|
|
}
|
|
b.WriteByte('\n')
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// humanSize renders a byte count compactly, the way `ls -h` does: a decimal
|
|
// only while it still carries information, so "3.2M" but "512K".
|
|
func humanSize(n int64) string {
|
|
const unit = 1024
|
|
if n < unit {
|
|
return strconv.FormatInt(n, 10) + "B"
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for v := n / unit; v >= unit && exp < 4; v /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
v := float64(n) / float64(div)
|
|
if v < 10 {
|
|
return fmt.Sprintf("%.1f%c", v, "KMGTP"[exp])
|
|
}
|
|
return fmt.Sprintf("%.0f%c", v, "KMGTP"[exp])
|
|
}
|