#!/usr/bin/env python3 """Render a normalized test report as Markdown, HTML, and PDF.""" import argparse import html import json from pathlib import Path from fpdf import FPDF def pdf_text(value: object) -> str: return str(value).encode("latin-1", errors="replace").decode("latin-1") def markdown(report: dict) -> str: project = report["project"] tool = report["tool"] target = report["target"] summary = report["summary"] lines = [ f"# {project['name']} Test Report", "", "| Field | Value |", "|---|---|", f"| Project ID | {project['id']} |", f"| Environment | {project['environment']} |", f"| Tool | {tool['name']} |", f"| Target | {target['id']} |", f"| Run | {report['run']['id']} |", f"| Started | {report['run']['started_at']} |", "", "## Summary", "", "| Total | Passed | Failed | Errors | Skipped | Review | Score |", "|---:|---:|---:|---:|---:|---:|---:|", f"| {summary['total']} | {summary['passed']} | {summary['failed']} | {summary['errors']} | {summary['skipped']} | {summary['review']} | {summary.get('score', 0):.2f}% |", "", "## Results", "", "| Status | Severity | ID | Title |", "|---|---|---|---|", ] for result in report["results"]: title = str(result["title"]).replace("|", "\\|") lines.append(f"| {result['status']} | {result['severity']} | {result['id']} | {title} |") return "\n".join(lines) + "\n" def html_document(markdown_text: str, report: dict) -> str: rows = "".join( "" f"{html.escape(result['status'])}" f"{html.escape(result['severity'])}" f"{html.escape(result['id'])}" f"{html.escape(result['title'])}" "" for result in report["results"] ) summary = report["summary"] return f""" {html.escape(report['project']['name'])} test report

{html.escape(report['project']['name'])} Test Report

{html.escape(report['tool']['name'])} against {html.escape(report['target']['id'])} at {html.escape(report['run']['started_at'])}

{summary['total']}Total{summary['passed']}Passed{summary['failed']}Failed{summary.get('score', 0):.2f}%Score
{rows}
StatusSeverityIDTitle
Markdown source
{html.escape(markdown_text)}
""" def pdf_document(report: dict, output: Path) -> None: pdf = FPDF() pdf.set_auto_page_break(auto=True, margin=15) pdf.add_page() pdf.set_font("Helvetica", "B", 18) pdf.multi_cell(0, 10, pdf_text(f"{report['project']['name']} Test Report"), new_x="LMARGIN", new_y="NEXT") pdf.set_font("Helvetica", size=10) pdf.multi_cell( 0, 6, pdf_text(f"Tool: {report['tool']['name']}\nTarget: {report['target']['id']}\nStarted: {report['run']['started_at']}"), new_x="LMARGIN", new_y="NEXT", ) summary = report["summary"] pdf.ln(3) pdf.set_font("Helvetica", "B", 12) pdf.cell(0, 8, f"Total {summary['total']} Passed {summary['passed']} Failed {summary['failed']} Score {summary.get('score', 0):.2f}%", new_x="LMARGIN", new_y="NEXT") pdf.set_font("Helvetica", size=9) for result in report["results"]: pdf.multi_cell( 0, 5, pdf_text(f"[{result['status'].upper()}] [{result['severity']}] {result['id']}: {result['title']}"), new_x="LMARGIN", new_y="NEXT", ) pdf.output(output) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("input", type=Path) parser.add_argument("--output-dir", type=Path, required=True) args = parser.parse_args() report = json.loads(args.input.read_text(encoding="utf-8")) args.output_dir.mkdir(parents=True, exist_ok=True) stem = f"{report['tool']['id']}-{report['target']['id']}" markdown_text = markdown(report) (args.output_dir / f"{stem}.md").write_text(markdown_text, encoding="utf-8") (args.output_dir / f"{stem}.html").write_text(html_document(markdown_text, report), encoding="utf-8") pdf_document(report, args.output_dir / f"{stem}.pdf") print(f"Rendered Markdown, HTML, and PDF reports in {args.output_dir}") return 0 if __name__ == "__main__": raise SystemExit(main())