package main import ( "encoding/json" "flag" "fmt" "io" "os" "sort" "strings" ) type reportRow struct { Status string `json:"status"` Area string `json:"area"` Check string `json:"check"` Summary string `json:"summary"` Detail string `json:"detail"` Fix string `json:"fix"` Graph string `json:"graph"` Query string `json:"query"` } func main() { if len(os.Args) < 2 { usage(os.Stderr) os.Exit(2) } switch os.Args[1] { case "report-render": if err := runReportRender(os.Args[2:], os.Stdin, os.Stdout); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } case "help", "-h", "--help": usage(os.Stdout) default: usage(os.Stderr) os.Exit(2) } } func usage(out io.Writer) { fmt.Fprintln(out, "Usage: jeannie-core report-render [--title TITLE] [--details] [--only failures|warnings|problems|all] [--json]") } func runReportRender(args []string, in io.Reader, out io.Writer) error { flags := flag.NewFlagSet("report-render", flag.ContinueOnError) flags.SetOutput(io.Discard) title := flags.String("title", envDefault("REPORT_TITLE", "Jeannie Report"), "") details := flags.Bool("details", false, "") only := flags.String("only", "all", "") asJSON := flags.Bool("json", false, "") if err := flags.Parse(args); err != nil { usage(os.Stderr) return err } rows, err := parseReportRows(in) if err != nil { return err } rows = filterReportRows(rows, *only) if *asJSON { encoder := json.NewEncoder(out) encoder.SetIndent("", " ") return encoder.Encode(map[string]any{"title": *title, "rows": rows}) } renderReportText(out, *title, rows, *details, *only) return nil } func envDefault(name, fallback string) string { if value := os.Getenv(name); value != "" { return value } return fallback } func parseReportRows(in io.Reader) ([]reportRow, error) { data, err := io.ReadAll(in) if err != nil { return nil, err } var rows []reportRow for _, raw := range strings.Split(string(data), "\n") { raw = strings.TrimSuffix(raw, "\r") if strings.TrimSpace(raw) == "" { continue } parts := strings.Split(raw, "\t") for len(parts) < 8 { parts = append(parts, "") } row := reportRow{ Status: normalizeStatus(parts[0]), Area: strings.TrimSpace(parts[1]), Check: strings.TrimSpace(parts[2]), Summary: strings.TrimSpace(parts[3]), Detail: strings.TrimSpace(parts[4]), Fix: strings.TrimSpace(parts[5]), Graph: strings.TrimSpace(parts[6]), Query: strings.TrimSpace(parts[7]), } if row.Area == "" { row.Area = "General" } if row.Status == "ok" { row.Graph = "" row.Query = "" } rows = append(rows, row) } return rows, nil } func normalizeStatus(value string) string { status := strings.ToLower(strings.TrimSpace(value)) if status == "" { return "skip" } return status } func filterReportRows(rows []reportRow, only string) []reportRow { var filtered []reportRow for _, row := range rows { if includeReportRow(row, only) { filtered = append(filtered, row) } } return filtered } func includeReportRow(row reportRow, only string) bool { switch only { case "all": return true case "failures": return row.Status == "fail" case "warnings": return row.Status == "warn" case "problems": return row.Status == "fail" || row.Status == "warn" default: return true } } func renderReportText(out io.Writer, title string, rows []reportRow, details bool, only string) { counts := map[string]int{"fail": 0, "warn": 0, "ok": 0, "skip": 0} for _, row := range rows { counts[row.Status]++ } fmt.Fprintln(out, title) fmt.Fprintln(out, strings.Repeat("=", len(title))) fmt.Fprintf(out, "fail=%d warn=%d ok=%d skip=%d\n", counts["fail"], counts["warn"], counts["ok"], counts["skip"]) if len(rows) == 0 { switch only { case "problems": fmt.Fprintln(out, "\nNo failing or warning items.") case "failures": fmt.Fprintln(out, "\nNo failing items.") case "warnings": fmt.Fprintln(out, "\nNo warning items.") default: fmt.Fprintln(out, "\nNo report rows.") } return } for _, area := range orderedReportAreas(rows) { fmt.Fprintf(out, "\n== %s ==\n", area) areaRows := rowsForReportArea(rows, area) sort.SliceStable(areaRows, func(i, j int) bool { leftRank := reportStatusRank(areaRows[i].Status) rightRank := reportStatusRank(areaRows[j].Status) if leftRank != rightRank { return leftRank < rightRank } return areaRows[i].Check < areaRows[j].Check }) for _, row := range areaRows { fmt.Fprintf(out, "%-5s %-30s %s\n", row.Status, truncate(row.Check, 30), row.Summary) if details || row.Status == "fail" || row.Status == "warn" { if row.Detail != "" { fmt.Fprintf(out, " detail: %s\n", row.Detail) } if row.Fix != "" { fmt.Fprintf(out, " fix: %s\n", row.Fix) } if row.Graph != "" { fmt.Fprintf(out, " graph: %s\n", row.Graph) } if row.Query != "" { fmt.Fprintf(out, " query: %s\n", row.Query) } } } } for _, row := range rows { if row.Status == "fail" || row.Status == "warn" { fmt.Fprintln(out, "\nNext Fix") if row.Fix != "" { fmt.Fprintln(out, row.Fix) } else { fmt.Fprintf(out, "Investigate %s / %s\n", row.Area, row.Check) } return } } } func orderedReportAreas(rows []reportRow) []string { seen := map[string]bool{} var areas []string for _, row := range rows { if !seen[row.Area] { seen[row.Area] = true areas = append(areas, row.Area) } } return areas } func rowsForReportArea(rows []reportRow, area string) []reportRow { var areaRows []reportRow for _, row := range rows { if row.Area == area { areaRows = append(areaRows, row) } } return areaRows } func reportStatusRank(status string) int { switch status { case "fail": return 0 case "warn": return 1 case "skip": return 2 case "ok": return 3 default: return 9 } } func truncate(value string, max int) string { if len(value) <= max { return value } return value[:max] }