Coverage for src / local_deep_research / exporters / quarto_exporter.py: 100%
45 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
1"""Quarto export service.
3This module provides the QuartoExporter class that wraps the existing
4QuartoExporter implementation from text_optimization.citation_formatter.
5"""
7import io
8import re
9import zipfile
10from typing import Optional
12from loguru import logger
14from .base import BaseExporter, ExportOptions, ExportResult
15from .registry import ExporterRegistry
18@ExporterRegistry.register
19class QuartoExporter(BaseExporter):
20 """Quarto exporter using the existing QuartoExporter implementation.
22 This exporter converts markdown content to Quarto (.qmd) format,
23 which can be rendered to HTML, PDF, or other formats using the
24 Quarto publishing system.
26 The export produces a ZIP file containing:
27 - The .qmd document
28 - A references.bib bibliography file
29 """
31 def __init__(self):
32 """Initialize Quarto exporter."""
33 # Import here to avoid circular imports
34 from ..text_optimization.citation_formatter import (
35 QuartoExporter as LegacyQuartoExporter,
36 )
38 self._legacy = LegacyQuartoExporter()
40 @property
41 def format_name(self) -> str:
42 return "quarto"
44 @property
45 def file_extension(self) -> str:
46 return ".zip"
48 @property
49 def mimetype(self) -> str:
50 return "application/zip"
52 def export(
53 self,
54 markdown_content: str,
55 options: Optional[ExportOptions] = None,
56 ) -> ExportResult:
57 """Convert markdown content to Quarto format.
59 Args:
60 markdown_content: The markdown text to convert
61 options: Optional export options (title, metadata)
63 Returns:
64 ExportResult with ZIP content as bytes containing .qmd and .bib files
66 Raises:
67 ValueError: If content exceeds maximum size limit
68 """
69 try:
70 # Check content size limit to prevent OOM errors
71 self._validate_content_size(markdown_content)
73 options = options or ExportOptions()
75 # Extract title from markdown if not provided
76 title = options.title
77 if not title:
78 title_match = re.search(
79 r"^#\s+(.+)$", markdown_content, re.MULTILINE
80 )
81 title = (
82 title_match.group(1) if title_match else "Research Report"
83 )
85 # Generate Quarto document
86 exported_content = self._legacy.export_to_quarto(
87 markdown_content, title
88 )
90 # Generate bibliography
91 bib_content = self._legacy._generate_bibliography(markdown_content)
93 # Use base class method for safe filename generation, then extract base name
94 base_filename = self._generate_safe_filename(options.title)
95 # Remove the .zip extension that _generate_safe_filename adds
96 safe_title = base_filename.replace(self.file_extension, "")
98 # Create a zip file in memory containing both files
99 zip_buffer = io.BytesIO()
100 with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf:
101 # Add the Quarto document
102 zipf.writestr(f"{safe_title}.qmd", exported_content)
103 # Add the bibliography file
104 zipf.writestr("references.bib", bib_content)
106 zip_bytes = zip_buffer.getvalue()
107 zip_buffer.close()
109 filename = f"{safe_title}_quarto.zip"
111 logger.info(
112 f"Generated Quarto with bibliography in memory (zip), "
113 f"size: {len(zip_bytes)} bytes"
114 )
116 return ExportResult(
117 content=zip_bytes,
118 filename=filename,
119 mimetype=self.mimetype,
120 )
122 except Exception:
123 logger.exception("Error generating Quarto")
124 raise