Initial commit. monorepo deps are copied in a dirty way to the vendor folder
This commit is contained in:
commit
06593ac488
53 files changed
+7692
No files matched your search
@@ -0,0 +1,90 @@
|
||||
"""Renders the SAST/SCA report template.
|
||||
|
||||
The template and its stylesheet are the design; this module is only what turns them into a
|
||||
document. It takes a context that is already shaped for the page -- every number formatted, every
|
||||
bar already a percentage -- so that the template places strings and never computes.
|
||||
|
||||
Nothing here reads an artifact. Filling a context from a scan is :mod:`context`, which works from
|
||||
what :mod:`sources` collected; :func:`sample.sample_context` builds one by hand, which is what the
|
||||
template tests render.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
TEMPLATE_DIR = Path(__file__).resolve().parent / "template"
|
||||
|
||||
TEMPLATE_NAME = "report.html.j2"
|
||||
|
||||
STYLESHEET = TEMPLATE_DIR / "report.css"
|
||||
|
||||
# What the document calls itself, in the PDF's own metadata rather than only on its first page.
|
||||
TITLE = "SAST/SCA Report"
|
||||
|
||||
AUTHOR = "CipherCell"
|
||||
|
||||
# The shape :func:`render_html` expects. Written down because the template guards on it: a key that
|
||||
# is absent leaves its section out, which is how a scan that ran half the pipeline produces a
|
||||
# shorter report rather than one full of empty tables.
|
||||
CONTEXT = {
|
||||
"cover": "scan_id, footer_date, logo, title, identity[], severity_ledger{title, rows[]}",
|
||||
"scan": "title, overview[], stages[{title, rows[], table?}]",
|
||||
"project": "title, overview[], languages{title, rows[]}",
|
||||
"summary": "title, overview[], distributions[{title, rows[], legend}]",
|
||||
"critical": "title, table{columns[], rows[]}",
|
||||
"sections": "[{title, new_page?, groups[{title, note?, cards[], table?}]}]",
|
||||
}
|
||||
|
||||
|
||||
def render_html(context: dict[str, Any]) -> str:
|
||||
"""Render the report as an HTML document.
|
||||
|
||||
Args:
|
||||
context: The page-ready context; see :data:`CONTEXT`.
|
||||
|
||||
Returns:
|
||||
The rendered HTML.
|
||||
"""
|
||||
environment = Environment(
|
||||
loader=FileSystemLoader(TEMPLATE_DIR),
|
||||
autoescape=select_autoescape(default_for_string=True, default=True),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
|
||||
return environment.get_template(TEMPLATE_NAME).render(**context)
|
||||
|
||||
|
||||
def render_pdf(context: dict[str, Any]) -> bytes:
|
||||
"""Render the report as a PDF.
|
||||
|
||||
Args:
|
||||
context: The page-ready context; see :data:`CONTEXT`.
|
||||
|
||||
Returns:
|
||||
The PDF.
|
||||
"""
|
||||
# WeasyPrint pulls in native cairo and pango bindings. Imported here so that importing this
|
||||
# module -- to render the HTML, or merely to read CONTEXT -- does not pay for them.
|
||||
from weasyprint import CSS, HTML
|
||||
from weasyprint.text.fonts import FontConfiguration
|
||||
|
||||
# Without this the @font-face rules are ignored and Space Grotesk is silently replaced by
|
||||
# whatever the host happens to have, which is how the design sample came to be set in Carlito.
|
||||
fonts = FontConfiguration()
|
||||
|
||||
# Relative URLs in the template and the stylesheet -- the mark, the font files -- resolve
|
||||
# against the template directory.
|
||||
document = HTML(string=render_html(context), base_url=str(TEMPLATE_DIR)).render(
|
||||
stylesheets=[CSS(filename=str(STYLESHEET), font_config=fonts)],
|
||||
font_config=fonts,
|
||||
)
|
||||
|
||||
document.metadata.title = f"{TITLE} - {context.get('cover', {}).get('subject', '')}".strip(" -")
|
||||
document.metadata.authors = [AUTHOR]
|
||||
|
||||
return bytes(document.write_pdf())
|
||||
Reference in new issue
Block a user