aboutsummaryrefslogtreecommitdiff
path: root/scripts/gen-bug-report.py
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-18 17:01:52 +0200
committerAgando <roduccomm@gmail.com>2026-10-18 17:01:52 +0200
commitd7a1198aa7ac77cbe9d3f0c844245dd08a8ccd12 (patch)
tree0e6851e95a62d05f51966fe29eb784108df0fe4b /scripts/gen-bug-report.py
parent824d6cd3c61488c120c0226d1929324f2741fafc (diff)
newOS4 v4.6.37: bug-report pipeline (report.php, bug command, HTML generator, telemetry-loop fix)
Diffstat (limited to 'scripts/gen-bug-report.py')
-rw-r--r--scripts/gen-bug-report.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/scripts/gen-bug-report.py b/scripts/gen-bug-report.py
new file mode 100644
index 0000000..f995bfc
--- /dev/null
+++ b/scripts/gen-bug-report.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+"""Generate a deterministic BUG-REPORT.html from data/reports/*.json.
+
+Every field is HTML-escaped; the page is self-contained (no external assets).
+Output is stable for a given set of reports — no wall-clock timestamps.
+"""
+import glob
+import html
+import json
+import os
+import sys
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+REPORTS_DIR = os.path.join(ROOT, "data", "reports")
+OUT_FILE = os.path.join(ROOT, "downloads", "bugs", "BUG-REPORT.html")
+MAX_REPORTS = 200
+
+
+def esc(value):
+ return html.escape("" if value is None else str(value), quote=True)
+
+
+def load_reports():
+ rows = []
+ for path in sorted(glob.glob(os.path.join(REPORTS_DIR, "RPT-*.json"))):
+ try:
+ with open(path, encoding="utf-8") as fh:
+ rec = json.load(fh)
+ if isinstance(rec, dict) and rec.get("id"):
+ rows.append(rec)
+ except (OSError, ValueError):
+ continue
+ rows.sort(key=lambda r: (str(r.get("created_at", "")), str(r.get("id", ""))), reverse=True)
+ return rows[:MAX_REPORTS]
+
+
+def pretty(value, limit=6000):
+ try:
+ text = json.dumps(value, indent=2, ensure_ascii=False)
+ except (TypeError, ValueError):
+ text = str(value)
+ if len(text) > limit:
+ text = text[:limit] + "\n… (truncated)"
+ return esc(text)
+
+
+def render(rows):
+ total = len(rows)
+ latest = esc(rows[0].get("created_at", "—")) if rows else "—"
+ cards = []
+
+ for rec in rows:
+ diag = rec.get("diagnostics") if isinstance(rec.get("diagnostics"), dict) else {}
+ errors = diag.get("errors") if isinstance(diag.get("errors"), list) else []
+ cards.append(
+ f""" <article class="card">
+ <header>
+ <h3>{esc(rec.get('title', '(untitled)'))}</h3>
+ <span class="pill">{esc(rec.get('id', ''))}</span>
+ </header>
+ <p class="meta">{esc(rec.get('created_at', ''))} · version {esc(diag.get('version', 'unknown'))} · {len(errors)} error(s)</p>
+ {f'<p class="body">{esc(rec.get("body"))}</p>' if rec.get('body') else ''}
+ <details>
+ <summary>Diagnostics</summary>
+ <pre>{pretty(diag)}</pre>
+ </details>
+ </article>"""
+ )
+
+ if not cards:
+ cards.append(' <p class="empty">No reports yet. Run <code>bug &lt;title&gt;</code> in newOS4.</p>')
+
+ return f"""<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>newOS4 — Bug Reports</title>
+<style>
+ :root {{ --bg:#0b0f19; --panel:#141a29; --line:#243049; --text:#e6ebf5; --muted:#93a1bb; --accent:#6366f1; }}
+ * {{ box-sizing:border-box; }}
+ body {{ margin:0; padding:32px 20px; background:radial-gradient(circle at 20% -10%,#1b2540 0,var(--bg) 55%); color:var(--text);
+ font:15px/1.55 Inter,system-ui,-apple-system,"Segoe UI",sans-serif; }}
+ main {{ max-width:920px; margin:0 auto; }}
+ h1 {{ font-size:1.6rem; margin:0 0 6px; }}
+ .sub {{ color:var(--muted); margin:0 0 26px; }}
+ .stats {{ display:flex; gap:12px; flex-wrap:wrap; margin-bottom:24px; }}
+ .stat {{ background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:12px 18px; min-width:130px; }}
+ .stat b {{ display:block; font-size:1.4rem; color:var(--accent); }}
+ .stat span {{ color:var(--muted); font-size:.82rem; }}
+ .card {{ background:var(--panel); border:1px solid var(--line); border-radius:14px; padding:16px 18px; margin-bottom:14px; }}
+ .card header {{ display:flex; gap:10px; align-items:center; justify-content:space-between; }}
+ .card h3 {{ margin:0; font-size:1.02rem; }}
+ .pill {{ font-size:.72rem; color:var(--accent); border:1px solid var(--accent); border-radius:999px; padding:2px 9px; white-space:nowrap; }}
+ .meta {{ color:var(--muted); font-size:.82rem; margin:6px 0 0; }}
+ .body {{ margin:10px 0 0; }}
+ details {{ margin-top:12px; }}
+ summary {{ cursor:pointer; color:var(--muted); }}
+ pre {{ background:#0d1322; border:1px solid var(--line); border-radius:10px; padding:12px; overflow:auto; max-height:420px;
+ font:12.5px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; }}
+ .empty {{ color:var(--muted); }}
+ code {{ background:#0d1322; border:1px solid var(--line); border-radius:6px; padding:1px 5px; }}
+</style>
+</head>
+<body>
+<main>
+ <h1>🐞 newOS4 Bug Reports</h1>
+ <p class="sub">Generated from <code>data/reports/*.json</code> — no manual edits.</p>
+ <section class="stats">
+ <div class="stat"><b>{total}</b><span>reports</span></div>
+ <div class="stat"><b>{esc(latest)}</b><span>latest report</span></div>
+ </section>
+{chr(10).join(cards)}
+</main>
+</body>
+</html>
+"""
+
+
+def main():
+ rows = load_reports()
+ page = render(rows)
+ os.makedirs(os.path.dirname(OUT_FILE), exist_ok=True)
+ with open(OUT_FILE, "w", encoding="utf-8") as fh:
+ fh.write(page)
+ os.chmod(OUT_FILE, 0o664)
+ print(f"wrote {OUT_FILE} ({len(rows)} report(s), {len(page)} bytes)")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())