Coverage for src / local_deep_research / exporters / quarto_exporter.py: 100%
47 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-25 01:07 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-25 01:07 +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
17# Maximum content size (50 MB) to prevent OOM errors
18MAX_CONTENT_SIZE = 50 * 1024 * 1024
21@ExporterRegistry.register
22class QuartoExporter(BaseExporter):
23 """Quarto exporter using the existing QuartoExporter implementation.
25 This exporter converts markdown content to Quarto (.qmd) format,
26 which can be rendered to HTML, PDF, or other formats using the
27 Quarto publishing system.
29 The export produces a ZIP file containing:
30 - The .qmd document
31 - A references.bib bibliography file
32 """
34 def __init__(self):
35 """Initialize Quarto exporter."""
36 # Import here to avoid circular imports
37 from ..text_optimization.citation_formatter import (
38 QuartoExporter as LegacyQuartoExporter,
39 )
41 self._legacy = LegacyQuartoExporter()
43 @property
44 def format_name(self) -> str:
45 return "quarto"
47 @property
48 def file_extension(self) -> str:
49 return ".zip"
51 @property
52 def mimetype(self) -> str:
53 return "application/zip"
55 def export(
56 self,
57 markdown_content: str,
58 options: Optional[ExportOptions] = None,
59 ) -> ExportResult:
60 """Convert markdown content to Quarto format.
62 Args:
63 markdown_content: The markdown text to convert
64 options: Optional export options (title, metadata)
66 Returns:
67 ExportResult with ZIP content as bytes containing .qmd and .bib files
69 Raises:
70 ValueError: If content exceeds maximum size limit
71 """
72 try:
73 # Check content size limit to prevent OOM errors
74 if len(markdown_content) > MAX_CONTENT_SIZE:
75 raise ValueError(
76 f"Content exceeds maximum size of "
77 f"{MAX_CONTENT_SIZE // (1024 * 1024)} MB"
78 )
80 options = options or ExportOptions()
82 # Extract title from markdown if not provided
83 title = options.title
84 if not title:
85 title_match = re.search(
86 r"^#\s+(.+)$", markdown_content, re.MULTILINE
87 )
88 title = (
89 title_match.group(1) if title_match else "Research Report"
90 )
92 # Generate Quarto document
93 exported_content = self._legacy.export_to_quarto(
94 markdown_content, title
95 )
97 # Generate bibliography
98 bib_content = self._legacy._generate_bibliography(markdown_content)
100 # Use base class method for safe filename generation, then extract base name
101 base_filename = self._generate_safe_filename(options.title)
102 # Remove the .zip extension that _generate_safe_filename adds
103 safe_title = base_filename.replace(self.file_extension, "")
105 # Create a zip file in memory containing both files
106 zip_buffer = io.BytesIO()
107 with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf:
108 # Add the Quarto document
109 zipf.writestr(f"{safe_title}.qmd", exported_content)
110 # Add the bibliography file
111 zipf.writestr("references.bib", bib_content)
113 zip_bytes = zip_buffer.getvalue()
114 zip_buffer.close()
116 filename = f"{safe_title}_quarto.zip"
118 logger.info(
119 f"Generated Quarto with bibliography in memory (zip), "
120 f"size: {len(zip_bytes)} bytes"
121 )
123 return ExportResult(
124 content=zip_bytes,
125 filename=filename,
126 mimetype=self.mimetype,
127 )
129 except Exception:
130 logger.exception("Error generating Quarto")
131 raise