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]) }