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

1"""Multi-format report output. 

2 

3Generate PDF, HTML, Markdown, DOCX from single report definition with 

4format-specific customization hooks. 

5 

6 

7References: 

8""" 

9 

10from __future__ import annotations 

11 

12from dataclasses import dataclass, field 

13from pathlib import Path 

14from typing import TYPE_CHECKING, Any, Literal 

15 

16if TYPE_CHECKING: 

17 from tracekit.reporting.core import Report 

18 

19FormatType = Literal["pdf", "html", "markdown", "docx", "json"] 

20 

21 

22@dataclass 

23class MultiFormatRenderer: 

24 """Multi-format report renderer. 

25 

26 One YAML template to multiple renderers (PDF/HTML/Markdown/DOCX). 

27 

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. 

33 

34 References: 

35 REPORT-010: Multi-Format Output 

36 """ 

37 

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 

42 

43 

44def detect_format_from_extension(path: str | Path) -> FormatType: 

45 """Detect output format from file extension. 

46 

47 Args: 

48 path: File path. 

49 

50 Returns: 

51 Detected format type. 

52 

53 Example: 

54 >>> detect_format_from_extension("report.pdf") 

55 'pdf' 

56 >>> detect_format_from_extension("report.html") 

57 'html' 

58 

59 References: 

60 REPORT-010: Multi-Format Output 

61 """ 

62 path = Path(path) 

63 ext = path.suffix.lower() 

64 

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 } 

74 

75 return format_map.get(ext, "pdf") # type: ignore[return-value] 

76 

77 

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. 

85 

86 Batch generation of all formats in single call with consistent content. 

87 

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. 

93 

94 Returns: 

95 Dictionary mapping format to output path. 

96 

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'} 

103 

104 References: 

105 REPORT-010: Multi-Format Output 

106 """ 

107 if formats is None: 

108 formats = ["pdf", "html"] 

109 

110 base_path = Path(base_path) 

111 output_paths: dict[str, str] = {} 

112 

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] 

132 

133 output_paths[fmt] = str(output_path) 

134 

135 return output_paths 

136 

137 

138def _render_pdf(report: Report, path: Path, **kwargs: Any) -> None: 

139 """Render to PDF.""" 

140 from tracekit.reporting.renderers.pdf import render_to_pdf 

141 

142 render_to_pdf(report, str(path), **kwargs) 

143 

144 

145def _render_html(report: Report, path: Path, **kwargs: Any) -> None: 

146 """Render to HTML.""" 

147 from tracekit.reporting.html import save_html_report 

148 

149 save_html_report(report, str(path)) 

150 

151 

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" 

156 

157 if hasattr(report.config, "author") and report.config.author: 

158 content += f"**Author:** {report.config.author} \n" 

159 

160 content += f"**Date:** {report.config.created.strftime('%Y-%m-%d')}\n\n" 

161 

162 # Add sections 

163 for section in report.sections: 

164 if not section.visible: 

165 continue 

166 

167 content += f"{'#' * (section.level + 1)} {section.title}\n\n" 

168 

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" 

174 

175 path.write_text(content) 

176 

177 

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}") 

182 

183 

184def _render_json(report: Report, path: Path, **kwargs: Any) -> None: 

185 """Render to JSON.""" 

186 import json 

187 

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 } 

202 

203 path.write_text(json.dumps(data, indent=2)) 

204 

205 

206__all__ = [ 

207 "MultiFormatRenderer", 

208 "detect_format_from_extension", 

209 "render_all_formats", 

210]