summaryrefslogtreecommitdiffstats
path: root/internal/formatter/formatter.go
diff options
context:
space:
mode:
authors <[email protected]>2025-11-13 14:43:15 -0500
committers <[email protected]>2025-11-13 14:43:15 -0500
commit344a6f6415c3c1b593677adec3b8844e0839971b (patch)
treeb05291ecdf21917b27e9e234eeb997c2706966d5 /internal/formatter/formatter.go
parenta5fc01a03753c9a18ddeaf13610dd99b4b311b80 (diff)
downloaddborg-344a6f6415c3c1b593677adec3b8844e0839971b.tar.gz
dborg-344a6f6415c3c1b593677adec3b8844e0839971b.zip
created pretty printing for all commandsv1.0.0
Diffstat (limited to 'internal/formatter/formatter.go')
-rw-r--r--internal/formatter/formatter.go478
1 files changed, 478 insertions, 0 deletions
diff --git a/internal/formatter/formatter.go b/internal/formatter/formatter.go
new file mode 100644
index 0000000..4d65c60
--- /dev/null
+++ b/internal/formatter/formatter.go
@@ -0,0 +1,478 @@
+package formatter
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "text/tabwriter"
+
+ "git.db.org.ai/dborg/internal/utils"
+)
+
+const (
+ ColorReset = "\033[0m"
+ ColorRed = "\033[31m"
+ ColorGreen = "\033[32m"
+ ColorYellow = "\033[33m"
+ ColorBlue = "\033[34m"
+ ColorMagenta = "\033[35m"
+ ColorCyan = "\033[36m"
+ ColorGray = "\033[90m"
+ ColorWhite = "\033[37m"
+ ColorBold = "\033[1m"
+ ColorDim = "\033[2m"
+)
+
+type Formatter interface {
+ Format(data any) (string, error)
+ Print(data any) error
+}
+
+type OutputMode int
+
+const (
+ ModeJSON OutputMode = iota
+ ModePretty
+)
+
+type BaseFormatter struct {
+ mode OutputMode
+ writer io.Writer
+}
+
+func NewFormatter(isJSON bool) *BaseFormatter {
+ mode := ModePretty
+ if isJSON {
+ mode = ModeJSON
+ }
+ return &BaseFormatter{
+ mode: mode,
+ writer: os.Stdout,
+ }
+}
+
+func (f *BaseFormatter) IsJSON() bool {
+ return f.mode == ModeJSON
+}
+
+func (f *BaseFormatter) FormatJSON(data any) error {
+ return utils.PrintJSON(data)
+}
+
+func isTerminal() bool {
+ fileInfo, err := os.Stdout.Stat()
+ if err != nil {
+ return false
+ }
+ return (fileInfo.Mode() & os.ModeCharDevice) != 0
+}
+
+func GetTerminalWidth() int {
+ return 80
+}
+
+func Colorize(text string, color string) string {
+ if !isTerminal() {
+ return text
+ }
+ return color + text + ColorReset
+}
+
+func Bold(text string) string {
+ return Colorize(text, ColorBold)
+}
+
+func Dim(text string) string {
+ return Colorize(text, ColorDim)
+}
+
+func Red(text string) string {
+ return Colorize(text, ColorRed)
+}
+
+func Green(text string) string {
+ return Colorize(text, ColorGreen)
+}
+
+func Yellow(text string) string {
+ return Colorize(text, ColorYellow)
+}
+
+func Blue(text string) string {
+ return Colorize(text, ColorBlue)
+}
+
+func Cyan(text string) string {
+ return Colorize(text, ColorCyan)
+}
+
+func Gray(text string) string {
+ return Colorize(text, ColorGray)
+}
+
+func Magenta(text string) string {
+ return Colorize(text, ColorMagenta)
+}
+
+type CreditsDisplay struct {
+ Current int64
+ Used int64
+ Remaining int64
+ Operation string
+}
+
+func FormatCredits(credits int64) string {
+ var color string
+ switch {
+ case credits > 1000:
+ color = ColorGreen
+ case credits > 100:
+ color = ColorYellow
+ default:
+ color = ColorRed
+ }
+ return Colorize(fmt.Sprintf("%d", credits), color)
+}
+
+func FormatCreditsWithLabel(credits int64, label string) string {
+ return fmt.Sprintf("%s: %s", Dim(label), FormatCredits(credits))
+}
+
+func PrintCreditsInfo(display *CreditsDisplay) {
+ if display == nil {
+ return
+ }
+
+ fmt.Printf("%s\n", Bold("Credits Information"))
+ if display.Operation != "" {
+ fmt.Printf(" %s: %s\n", Dim("Operation"), display.Operation)
+ }
+ if display.Current > 0 {
+ fmt.Printf(" %s\n", FormatCreditsWithLabel(display.Current, "Current"))
+ }
+ if display.Used > 0 {
+ fmt.Printf(" %s\n", FormatCreditsWithLabel(display.Used, "Used"))
+ }
+ if display.Remaining > 0 {
+ fmt.Printf(" %s\n", FormatCreditsWithLabel(display.Remaining, "Remaining"))
+ }
+ fmt.Println()
+}
+
+type ProgressBar struct {
+ Total int
+ Current int
+ Width int
+ Label string
+}
+
+func NewProgressBar(total int, label string) *ProgressBar {
+ return &ProgressBar{
+ Total: total,
+ Width: 40,
+ Label: label,
+ }
+}
+
+func (p *ProgressBar) Update(current int) {
+ p.Current = current
+}
+
+func (p *ProgressBar) Render() string {
+ if p.Total <= 0 {
+ return ""
+ }
+
+ percentage := float64(p.Current) / float64(p.Total)
+ filled := int(percentage * float64(p.Width))
+
+ if filled > p.Width {
+ filled = p.Width
+ }
+
+ bar := strings.Repeat("█", filled) + strings.Repeat("░", p.Width-filled)
+ percentStr := fmt.Sprintf("%.1f%%", percentage*100)
+
+ var barColor string
+ switch {
+ case percentage >= 1.0:
+ barColor = ColorGreen
+ case percentage >= 0.5:
+ barColor = ColorYellow
+ default:
+ barColor = ColorCyan
+ }
+
+ coloredBar := Colorize(bar, barColor)
+
+ if p.Label != "" {
+ return fmt.Sprintf("%s [%s] %s (%d/%d)",
+ Dim(p.Label), coloredBar, Bold(percentStr), p.Current, p.Total)
+ }
+
+ return fmt.Sprintf("[%s] %s (%d/%d)",
+ coloredBar, Bold(percentStr), p.Current, p.Total)
+}
+
+func (p *ProgressBar) Print() {
+ fmt.Printf("\r%s", p.Render())
+}
+
+func (p *ProgressBar) Finish() {
+ p.Current = p.Total
+ fmt.Printf("\r%s\n", p.Render())
+}
+
+type TableFormatter struct {
+ headers []string
+ rows [][]string
+ writer *tabwriter.Writer
+}
+
+func NewTable(headers []string) *TableFormatter {
+ return &TableFormatter{
+ headers: headers,
+ rows: make([][]string, 0),
+ writer: tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0),
+ }
+}
+
+func (t *TableFormatter) AddRow(columns ...string) {
+ t.rows = append(t.rows, columns)
+}
+
+func (t *TableFormatter) AddRows(rows [][]string) {
+ t.rows = append(t.rows, rows...)
+}
+
+func (t *TableFormatter) Render() string {
+ var buf bytes.Buffer
+ w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0)
+
+ for i, h := range t.headers {
+ if i > 0 {
+ fmt.Fprint(w, "\t")
+ }
+ fmt.Fprint(w, Bold(h))
+ }
+ fmt.Fprintln(w)
+
+ for _, row := range t.rows {
+ for i, col := range row {
+ if i > 0 {
+ fmt.Fprint(w, "\t")
+ }
+ fmt.Fprint(w, col)
+ }
+ fmt.Fprintln(w)
+ }
+
+ w.Flush()
+ return buf.String()
+}
+
+func (t *TableFormatter) Print() {
+ fmt.Print(t.Render())
+}
+
+func (t *TableFormatter) PrintJSON() error {
+ data := make([]map[string]string, 0, len(t.rows))
+
+ for _, row := range t.rows {
+ rowMap := make(map[string]string)
+ for i, col := range row {
+ if i < len(t.headers) {
+ rowMap[t.headers[i]] = col
+ }
+ }
+ data = append(data, rowMap)
+ }
+
+ return utils.PrintJSON(data)
+}
+
+type KeyValue struct {
+ Key string
+ Value string
+}
+
+func FormatKeyValue(key, value string) string {
+ return fmt.Sprintf("%s: %s", Cyan(key), value)
+}
+
+func FormatKeyValueList(items []KeyValue) string {
+ var buf bytes.Buffer
+ for i, item := range items {
+ if i > 0 {
+ buf.WriteString("\n")
+ }
+ buf.WriteString(FormatKeyValue(item.Key, item.Value))
+ }
+ return buf.String()
+}
+
+func PrintKeyValue(key, value string) {
+ fmt.Println(FormatKeyValue(key, value))
+}
+
+func PrintSection(title string) {
+ fmt.Printf("\n%s\n%s\n", Bold(title), strings.Repeat("─", len(title)))
+}
+
+func PrintDivider() {
+ if isTerminal() {
+ fmt.Println(Dim(strings.Repeat("─", 80)))
+ } else {
+ fmt.Println(strings.Repeat("-", 80))
+ }
+}
+
+type StatusIndicator int
+
+const (
+ StatusSuccess StatusIndicator = iota
+ StatusWarning
+ StatusError
+ StatusInfo
+ StatusPending
+)
+
+func (s StatusIndicator) String() string {
+ switch s {
+ case StatusSuccess:
+ return Green("✓")
+ case StatusWarning:
+ return Yellow("⚠")
+ case StatusError:
+ return Red("✗")
+ case StatusInfo:
+ return Blue("ℹ")
+ case StatusPending:
+ return Gray("●")
+ default:
+ return "?"
+ }
+}
+
+func FormatStatus(status StatusIndicator, message string) string {
+ return fmt.Sprintf("%s %s", status.String(), message)
+}
+
+func PrintStatus(status StatusIndicator, message string) {
+ fmt.Println(FormatStatus(status, message))
+}
+
+func PrintSuccess(message string) {
+ PrintStatus(StatusSuccess, message)
+}
+
+func PrintWarning(message string) {
+ PrintStatus(StatusWarning, message)
+}
+
+func PrintError(message string) {
+ PrintStatus(StatusError, message)
+}
+
+func PrintInfo(message string) {
+ PrintStatus(StatusInfo, message)
+}
+
+func FormatBytes(bytes int64) string {
+ const unit = 1024
+ if bytes < unit {
+ return fmt.Sprintf("%d B", bytes)
+ }
+ div, exp := int64(unit), 0
+ for n := bytes / unit; n >= unit; n /= unit {
+ div *= unit
+ exp++
+ }
+ return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
+}
+
+func FormatPercentage(value, total int64) string {
+ if total == 0 {
+ return "0.0%"
+ }
+ percentage := float64(value) / float64(total) * 100
+ return fmt.Sprintf("%.1f%%", percentage)
+}
+
+func TruncateString(s string, maxLen int) string {
+ if len(s) <= maxLen {
+ return s
+ }
+ if maxLen <= 3 {
+ return s[:maxLen]
+ }
+ return s[:maxLen-3] + "..."
+}
+
+func PadRight(s string, width int) string {
+ if len(s) >= width {
+ return s
+ }
+ return s + strings.Repeat(" ", width-len(s))
+}
+
+func PadLeft(s string, width int) string {
+ if len(s) >= width {
+ return s
+ }
+ return strings.Repeat(" ", width-len(s)) + s
+}
+
+func FormatList(items []string, bullet string) string {
+ if bullet == "" {
+ bullet = "•"
+ }
+ var buf bytes.Buffer
+ for i, item := range items {
+ if i > 0 {
+ buf.WriteString("\n")
+ }
+ buf.WriteString(fmt.Sprintf("%s %s", Dim(bullet), item))
+ }
+ return buf.String()
+}
+
+func PrintList(items []string) {
+ fmt.Println(FormatList(items, "•"))
+}
+
+func StreamJSON(w io.Writer, data any) error {
+ encoder := json.NewEncoder(w)
+ encoder.SetIndent("", " ")
+ return encoder.Encode(data)
+}
+
+func StreamNDJSON(w io.Writer, data any) error {
+ encoder := json.NewEncoder(w)
+ return encoder.Encode(data)
+}
+
+func PrintColorizedJSON(data any) error {
+ return utils.PrintJSON(data)
+}
+
+func FormatColorizedJSON(data any) (string, error) {
+ output, err := json.MarshalIndent(data, "", " ")
+ if err != nil {
+ return "", fmt.Errorf("failed to format JSON: %w", err)
+ }
+ return utils.ColorizeJSON(output), nil
+}
+
+func PrintJSONWithHeader(header string, data any) error {
+ fmt.Fprintf(os.Stderr, "\n%s\n", Bold(header))
+ output, err := json.MarshalIndent(data, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to format JSON: %w", err)
+ }
+ fmt.Println(utils.ColorizeJSON(output))
+ return nil
+}