mirror of
https://github.com/bikini/patchwork
synced 2026-06-27 08:08:41 +00:00
132 lines
4.0 KiB
Python
132 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
import html
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def _escape(value: object) -> str:
|
|
return html.escape(str(value), quote=True)
|
|
|
|
|
|
def _indicator_rows(audit: dict[str, Any]) -> str:
|
|
indicators = audit.get("review", {}).get("indicators", [])
|
|
rows: list[str] = []
|
|
if isinstance(indicators, list):
|
|
for indicator in indicators:
|
|
if not isinstance(indicator, dict):
|
|
continue
|
|
rows.append(
|
|
"<tr>"
|
|
f"<td>{_escape(indicator.get('line', ''))}</td>"
|
|
f"<td>{_escape(indicator.get('kind', ''))}</td>"
|
|
f"<td>{_escape(indicator.get('name', ''))}</td>"
|
|
f"<td>{_escape(indicator.get('reason', ''))}</td>"
|
|
"</tr>"
|
|
)
|
|
return "\n".join(rows) or '<tr><td colspan="4">No review indicators found.</td></tr>'
|
|
|
|
|
|
def _option_rows(options: dict[str, object]) -> str:
|
|
return "\n".join(
|
|
f"<tr><td>{_escape(key)}</td><td><code>{_escape(value)}</code></td></tr>"
|
|
for key, value in sorted(options.items())
|
|
)
|
|
|
|
|
|
def render_report(*, audit: dict[str, Any], options: dict[str, object], stats: dict[str, object] | None = None) -> str:
|
|
review = audit.get("review", {})
|
|
stats = stats or {}
|
|
return f"""<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Patchwork Build Report</title>
|
|
<style>
|
|
:root {{
|
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
line-height: 1.45;
|
|
color: #15181f;
|
|
background: #f5f7fa;
|
|
}}
|
|
body {{
|
|
margin: 0;
|
|
}}
|
|
main {{
|
|
max-width: 1080px;
|
|
margin: 0 auto;
|
|
padding: 32px 20px 48px;
|
|
}}
|
|
section {{
|
|
background: #ffffff;
|
|
border: 1px solid #dce2ea;
|
|
border-radius: 8px;
|
|
margin: 16px 0;
|
|
padding: 18px;
|
|
}}
|
|
table {{
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
background: #ffffff;
|
|
}}
|
|
th, td {{
|
|
border-bottom: 1px solid #e7ebf0;
|
|
padding: 9px;
|
|
text-align: left;
|
|
vertical-align: top;
|
|
font-size: 14px;
|
|
}}
|
|
th {{
|
|
background: #edf1f6;
|
|
}}
|
|
code {{
|
|
font-family: Consolas, "Liberation Mono", monospace;
|
|
overflow-wrap: anywhere;
|
|
}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<h1>Patchwork Build Report</h1>
|
|
<section>
|
|
<h2>Source Audit</h2>
|
|
<p><strong>Path:</strong> {_escape(audit.get('path'))}</p>
|
|
<p><strong>SHA-256:</strong> <code>{_escape(audit.get('sha256'))}</code></p>
|
|
<p><strong>Lines:</strong> {_escape(audit.get('lines'))}</p>
|
|
<p><strong>AST nodes:</strong> {_escape(audit.get('ast_nodes'))}</p>
|
|
<p><strong>Review indicators:</strong> {_escape(review.get('indicator_count', 0))}</p>
|
|
</section>
|
|
<section>
|
|
<h2>Output Stats</h2>
|
|
<p><strong>Output SHA-256:</strong> <code>{_escape(stats.get('output_sha256', 'not generated'))}</code></p>
|
|
<p><strong>Input bytes:</strong> {_escape(stats.get('input_bytes', 'not generated'))}</p>
|
|
<p><strong>Output bytes:</strong> {_escape(stats.get('output_bytes', 'not generated'))}</p>
|
|
<p><strong>Expansion ratio:</strong> {_escape(stats.get('ratio', 'not generated'))}</p>
|
|
</section>
|
|
<section>
|
|
<h2>Options</h2>
|
|
<table>
|
|
<thead><tr><th>Option</th><th>Value</th></tr></thead>
|
|
<tbody>{_option_rows(options)}</tbody>
|
|
</table>
|
|
</section>
|
|
<section>
|
|
<h2>Review Indicators</h2>
|
|
<table>
|
|
<thead><tr><th>Line</th><th>Kind</th><th>Name</th><th>Reason</th></tr></thead>
|
|
<tbody>{_indicator_rows(audit)}</tbody>
|
|
</table>
|
|
</section>
|
|
</main>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
def write_report(path: str | Path, *, audit: dict[str, Any], options: dict[str, object], stats: dict[str, object] | None = None) -> Path:
|
|
output_path = Path(path)
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
output_path.write_text(render_report(audit=audit, options=options, stats=stats), encoding="utf-8")
|
|
return output_path
|