Coverage for src / tracekit / reporting / core_formats / multi_format.py: 20%
68 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""Multi-format report output.
3Generate PDF, HTML, Markdown, DOCX from single report definition with
4format-specific customization hooks.
7References:
8"""
10from __future__ import annotations
12from dataclasses import dataclass, field
13from pathlib import Path
14from typing import TYPE_CHECKING, Any, Literal
16if TYPE_CHECKING:
17 from tracekit.reporting.core import Report
19FormatType = Literal["pdf", "html", "markdown", "docx", "json"]
22@dataclass
23class MultiFormatRenderer:
24 """Multi-format report renderer.
26 One YAML template to multiple renderers (PDF/HTML/Markdown/DOCX).
28 Attributes:
29 formats: List of output formats to generate.
30 format_options: Format-specific options.
31 auto_detect_features: Auto-select features per format (e.g., interactive plots in HTML).
32 consistent_content: Ensure same data across formats.
34 References:
35 REPORT-010: Multi-Format Output
36 """
38 formats: list[FormatType] = field(default_factory=lambda: ["pdf"])
39 format_options: dict[str, dict[str, Any]] = field(default_factory=dict)
40 auto_detect_features: bool = True
41 consistent_content: bool = True
44def detect_format_from_extension(path: str | Path) -> FormatType:
45 """Detect output format from file extension.
47 Args:
48 path: File path.
50 Returns:
51 Detected format type.
53 Example:
54 >>> detect_format_from_extension("report.pdf")
55 'pdf'
56 >>> detect_format_from_extension("report.html")
57 'html'
59 References:
60 REPORT-010: Multi-Format Output
61 """
62 path = Path(path)
63 ext = path.suffix.lower()
65 format_map = {
66 ".pdf": "pdf",
67 ".html": "html",
68 ".htm": "html",
69 ".md": "markdown",
70 ".markdown": "markdown",
71 ".docx": "docx",
72 ".json": "json",
73 }
75 return format_map.get(ext, "pdf") # type: ignore[return-value]
78def render_all_formats(
79 report: Report,
80 base_path: str | Path,
81 formats: list[FormatType] | None = None,
82 **kwargs: Any,
83) -> dict[str, str]:
84 """Generate report in multiple formats.
86 Batch generation of all formats in single call with consistent content.
88 Args:
89 report: Report object to render.
90 base_path: Base output path (without extension).
91 formats: List of formats to generate (default: ["pdf", "html"]).
92 **kwargs: Additional rendering options.
94 Returns:
95 Dictionary mapping format to output path.
97 Example:
98 >>> from tracekit.reporting.core import Report, ReportConfig
99 >>> report = Report(config=ReportConfig(title="Test Report"))
100 >>> paths = render_all_formats(report, "report", formats=["pdf", "html"])
101 >>> print(paths)
102 {'pdf': 'report.pdf', 'html': 'report.html'}
104 References:
105 REPORT-010: Multi-Format Output
106 """
107 if formats is None:
108 formats = ["pdf", "html"]
110 base_path = Path(base_path)
111 output_paths: dict[str, str] = {}
113 for fmt in formats:
114 # Determine output path
115 if fmt == "pdf":
116 output_path = base_path.with_suffix(".pdf")
117 _render_pdf(report, output_path, **kwargs)
118 elif fmt == "html":
119 output_path = base_path.with_suffix(".html")
120 _render_html(report, output_path, **kwargs)
121 elif fmt == "markdown":
122 output_path = base_path.with_suffix(".md")
123 _render_markdown(report, output_path, **kwargs)
124 elif fmt == "docx":
125 output_path = base_path.with_suffix(".docx")
126 _render_docx(report, output_path, **kwargs)
127 elif fmt == "json":
128 output_path = base_path.with_suffix(".json")
129 _render_json(report, output_path, **kwargs)
130 else:
131 continue # type: ignore[unreachable]
133 output_paths[fmt] = str(output_path)
135 return output_paths
138def _render_pdf(report: Report, path: Path, **kwargs: Any) -> None:
139 """Render to PDF."""
140 from tracekit.reporting.renderers.pdf import render_to_pdf
142 render_to_pdf(report, str(path), **kwargs)
145def _render_html(report: Report, path: Path, **kwargs: Any) -> None:
146 """Render to HTML."""
147 from tracekit.reporting.html import save_html_report
149 save_html_report(report, str(path))
152def _render_markdown(report: Report, path: Path, **kwargs: Any) -> None:
153 """Render to Markdown."""
154 # Generate Markdown content
155 content = f"# {report.config.title}\n\n"
157 if hasattr(report.config, "author") and report.config.author:
158 content += f"**Author:** {report.config.author} \n"
160 content += f"**Date:** {report.config.created.strftime('%Y-%m-%d')}\n\n"
162 # Add sections
163 for section in report.sections:
164 if not section.visible:
165 continue
167 content += f"{'#' * (section.level + 1)} {section.title}\n\n"
169 if isinstance(section.content, str):
170 content += section.content + "\n\n"
171 elif isinstance(section.content, list):
172 for item in section.content:
173 content += str(item) + "\n\n"
175 path.write_text(content)
178def _render_docx(report: Report, path: Path, **kwargs: Any) -> None:
179 """Render to DOCX."""
180 # Placeholder - would use python-docx
181 path.write_text(f"DOCX rendering not yet implemented for {report.config.title}")
184def _render_json(report: Report, path: Path, **kwargs: Any) -> None:
185 """Render to JSON."""
186 import json
188 data = {
189 "title": report.config.title,
190 "author": report.config.author,
191 "created": report.config.created.isoformat(),
192 "sections": [
193 {
194 "title": s.title,
195 "level": s.level,
196 "content": str(s.content),
197 }
198 for s in report.sections
199 if s.visible
200 ],
201 }
203 path.write_text(json.dumps(data, indent=2))
206__all__ = [
207 "MultiFormatRenderer",
208 "detect_format_from_extension",
209 "render_all_formats",
210]