from-markdown takes a hand-written summary such as SUMMARY.md and sets it in the full report's dress: the same cover, header, footers, table of contents, headings and grids. The shared macros move to macros.html.j2 so both templates draw from them, and layout.render_pdf takes the template and document title to use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
"""The summary report: a Markdown summary, set as a PDF in the style of the full report."""
|
|
|
|
from pathlib import Path
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from ..vendor.cc_common.api import PipelineError, context
|
|
from ..vendor.cc_common.utils import print
|
|
|
|
from ..summary import render_summary
|
|
from .generate import _destination
|
|
|
|
|
|
class SummaryProperties(BaseModel):
|
|
"""Configuration properties for the from-markdown CLI command.
|
|
|
|
Attributes:
|
|
markdown: The Markdown summary to set.
|
|
scan_id: The scan it is about. Named on the cover and in the footers
|
|
when given, and left out when not.
|
|
path: Where to write the PDF. A directory is written into under the name
|
|
the renderer chose; leaving it out writes into ``reports/``.
|
|
project: The name of the scanned project. Taken from the summary's title
|
|
when not given.
|
|
manufacturer: Who makes the scanned product, as the summary states it.
|
|
"""
|
|
|
|
markdown: Path = Field(description="The Markdown summary to build the PDF from.")
|
|
scan_id: UUID | None = Field(default=None, description="The scan the summary is about.")
|
|
path: Path | None = Field(default=None, description="Where to write the PDF summary.")
|
|
project: str | None = Field(
|
|
default=None, description="The name of the scanned project. Read from the title when omitted."
|
|
)
|
|
manufacturer: str | None = Field(default=None, description="The manufacturer of the scanned product.")
|
|
|
|
|
|
class GeneratedSummary(BaseModel):
|
|
"""The summary that was written.
|
|
|
|
Attributes:
|
|
source: The Markdown it was set from.
|
|
path: The file written.
|
|
size: How large it came out, in bytes.
|
|
"""
|
|
|
|
source: Path
|
|
path: Path
|
|
size: int
|
|
|
|
def print_as_cc_common_result(self) -> None:
|
|
"""Say what was written and where."""
|
|
print(f"[bold]Summary from {self.source}[/bold]")
|
|
print(f" {self.size / 1024:.0f} kB written to {self.path}")
|
|
|
|
|
|
def from_markdown(properties: SummaryProperties) -> GeneratedSummary:
|
|
"""Build the summary report from a Markdown file.
|
|
|
|
Args:
|
|
properties: The Markdown file, what to say about it, and the destination.
|
|
|
|
Returns:
|
|
The summary that was written.
|
|
|
|
Raises:
|
|
PipelineError: The Markdown cannot be read, or the destination cannot be written.
|
|
"""
|
|
try:
|
|
markdown = properties.markdown.read_text(encoding="utf-8")
|
|
except (OSError, UnicodeDecodeError) as error:
|
|
message = f"Cannot read the summary '{properties.markdown}': {error}"
|
|
|
|
raise PipelineError(message) from error
|
|
|
|
name, content = render_summary(
|
|
markdown,
|
|
scan_id=properties.scan_id,
|
|
project=properties.project,
|
|
manufacturer=properties.manufacturer,
|
|
)
|
|
destination = _destination(properties.path, name)
|
|
|
|
try:
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
destination.write_bytes(content)
|
|
except OSError as error:
|
|
message = f"Cannot write the summary to '{destination}': {error}"
|
|
|
|
raise PipelineError(message) from error
|
|
|
|
context.logger.info(f"Summary written to {destination} ✔")
|
|
|
|
return GeneratedSummary(source=properties.markdown, path=destination, size=len(content))
|