#!/usr/bin/env bash set -euo pipefail MODE="compact" ONLY="all" TITLE="${REPORT_TITLE:-Jeannie Report}" INPUT_FILE="" cleanup() { if [ -n "$INPUT_FILE" ] && [ -f "$INPUT_FILE" ]; then rm -f "$INPUT_FILE" fi } trap cleanup EXIT usage() { cat <areachecksummarydetailhint Statuses: ok, warn, fail, skip EOF } while (($# > 0)); do case "$1" in --title) TITLE="${2:-}" shift 2 ;; --details) MODE="details" shift ;; --only) ONLY="${2:-all}" shift 2 ;; --json) MODE="json" shift ;; -h|--help) usage exit 0 ;; *) usage >&2 exit 1 ;; esac done INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/jeannie-report.XXXXXX")" cat >"$INPUT_FILE" python3 - "$TITLE" "$MODE" "$ONLY" "$INPUT_FILE" <<'PY' import json import sys from collections import OrderedDict title, mode, only, input_file = sys.argv[1:5] rows = [] with open(input_file, encoding="utf-8") as handle: for raw in handle: raw = raw.rstrip("\n") if not raw: continue parts = raw.split("\t") while len(parts) < 6: parts.append("") status, area, check, summary, detail, hint = parts[:6] status = status.strip().lower() or "skip" rows.append( { "status": status, "area": area.strip() or "General", "check": check.strip(), "summary": summary.strip(), "detail": detail.strip(), "hint": hint.strip(), } ) def include(row): status = row["status"] if only == "all": return True if only == "failures": return status == "fail" if only == "warnings": return status == "warn" if only == "problems": return status in {"fail", "warn"} return True rows = [row for row in rows if include(row)] if mode == "json": print(json.dumps({"title": title, "rows": rows}, indent=2)) raise SystemExit(0) counts = {"fail": 0, "warn": 0, "ok": 0, "skip": 0} for row in rows: counts[row["status"]] = counts.get(row["status"], 0) + 1 print(title) print("=" * len(title)) print( f"fail={counts.get('fail', 0)} " f"warn={counts.get('warn', 0)} " f"ok={counts.get('ok', 0)} " f"skip={counts.get('skip', 0)}" ) groups = OrderedDict() for row in rows: groups.setdefault(row["area"], []).append(row) status_rank = {"fail": 0, "warn": 1, "skip": 2, "ok": 3} status_label = {"fail": "fail", "warn": "warn", "ok": "ok", "skip": "skip"} for area, area_rows in groups.items(): print(f"\n== {area} ==") for row in sorted(area_rows, key=lambda item: (status_rank.get(item["status"], 9), item["check"])): status = status_label.get(row["status"], row["status"]) check = row["check"][:30] summary = row["summary"] print(f"{status:<5} {check:<30} {summary}") if mode == "details" or row["status"] in {"fail", "warn"}: if row["detail"]: print(f" detail: {row['detail']}") if row["hint"]: print(f" fix: {row['hint']}") problems = [row for row in rows if row["status"] in {"fail", "warn"}] if problems: first = problems[0] print("\nNext Fix") if first["hint"]: print(first["hint"]) else: print(f"Investigate {first['area']} / {first['check']}") PY