audit-labs/audit-report
Turn audit-tools evidence packages into control-mapped, auditor-ready reports.
clone: git clone https://gitbay.org/audit-labs/audit-report.git
v1.0.0: audit_report/reporters/__init__.py · raw
1"""Report renderers. Each takes a :class:`Report` and returns a string."""
2
3from __future__ import annotations
4
5from dataclasses import dataclass
6from datetime import datetime, timezone
7
8from .. import __version__
9from ..engine import Finding, control_coverage, summarize
10from ..loader import Package
11from ..rules import Ruleset
12from . import html as _html
13from . import json as _json
14from . import markdown as _markdown
15
16
17@dataclass
18class Report:
19 """Everything a renderer needs: the package, findings, and rollups."""
20
21 package: Package
22 findings: list[Finding]
23 generated_at: str
24 ruleset: Ruleset | None = None
25
26 @property
27 def counts(self) -> dict[str, int]:
28 return summarize(self.findings)
29
30 @property
31 def coverage(self) -> dict[str, dict]:
32 return control_coverage(self.findings)
33
34 @property
35 def provenance(self) -> dict[str, dict]:
36 """Tool + ruleset identity, so a report can be tied to what produced it."""
37 rs = self.ruleset
38 return {
39 "tool": {"name": "audit-report", "version": __version__},
40 "ruleset": {
41 "name": rs.name if rs else "",
42 "platform": rs.platform if rs else "",
43 "version": rs.version if rs else "",
44 "sha256": rs.sha256 if rs else "",
45 },
46 }
47
48
49def build_report(
50 package: Package, findings: list[Finding], ruleset: Ruleset | None = None
51) -> Report:
52 """Assemble a :class:`Report` with a UTC generation timestamp."""
53 stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
54 return Report(
55 package=package, findings=findings, generated_at=stamp, ruleset=ruleset
56 )
57
58
59RENDERERS = {
60 "md": _markdown.render,
61 "html": _html.render,
62 "json": _json.render,
63}
64
65# File extension per format (md and markdown both write .md).
66EXTENSIONS = {"md": "md", "html": "html", "json": "json"}
67
68
69def render(report: Report, fmt: str) -> str:
70 """Render *report* in the named format ('md', 'html', or 'json')."""
71 try:
72 return RENDERERS[fmt](report)
73 except KeyError:
74 raise ValueError(f"unknown format: {fmt!r}") from None