package main import ( "encoding/json" "fmt" "io" "os" "strings" "text/tabwriter" ) // newTabWriter returns a tabwriter for stdout. func newTabWriter() *tabwriter.Writer { return tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) } // printTable writes a header + rows to a tabwriter. // headers: e.g. []string{"ID", "TITLE", "TYPE"} // rows: [][]string func printTable(w io.Writer, headers []string, rows [][]string) { tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, strings.Join(headers, "\t")) for _, row := range rows { fmt.Fprintln(tw, strings.Join(row, "\t")) } tw.Flush() } // printJSON marshals v to JSON and writes to stdout. func printJSON(v any) { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") if err := enc.Encode(v); err != nil { fmt.Fprintln(os.Stderr, "json encode error:", err) os.Exit(1) } } // truncate returns s truncated to maxLen chars, with "..." if truncated. func truncate(s string, maxLen int) string { if len(s) <= maxLen { return s } if maxLen <= 3 { return s[:maxLen] } return s[:maxLen-3] + "..." } // depsStatus returns "OK" or "blocked: X,Y" for display. func depsStatus(issue Issue) string { if len(issue.Depends) == 0 { return "OK" } if issue.DepsResolved { return "OK" } return "blocked: " + strings.Join(issue.Depends, ",") } // joinStrings joins a slice with commas, or returns "-" if empty. func joinStrings(ss []string) string { if len(ss) == 0 { return "-" } return strings.Join(ss, ",") } // pctBar returns a compact string like "75%" or "0%". func pctBar(p int) string { return fmt.Sprintf("%d%%", p) } // statusShort normalizes status for display. func statusShort(s string) string { switch strings.ToLower(s) { case "in-progress": return "in-progress" case "completado", "done", "completed": return "completado" case "pendiente", "pending": return "pendiente" case "bloqueado", "blocked": return "bloqueado" case "deferred": return "deferred" default: if s == "" { return "-" } return s } }