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

1"""Quarto export service. 

2 

3This module provides the QuartoExporter class that wraps the existing 

4QuartoExporter implementation from text_optimization.citation_formatter. 

5""" 

6 

7import io 

8import re 

9import zipfile 

10from typing import Optional 

11 

12from loguru import logger 

13 

14from .base import BaseExporter, ExportOptions, ExportResult 

15from .registry import ExporterRegistry 

16 

17# Maximum content size (50 MB) to prevent OOM errors 

18MAX_CONTENT_SIZE = 50 * 1024 * 1024 

19 

20 

21@ExporterRegistry.register 

22class QuartoExporter(BaseExporter): 

23 """Quarto exporter using the existing QuartoExporter implementation. 

24 

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. 

28 

29 The export produces a ZIP file containing: 

30 - The .qmd document 

31 - A references.bib bibliography file 

32 """ 

33 

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 ) 

40 

41 self._legacy = LegacyQuartoExporter() 

42 

43 @property 

44 def format_name(self) -> str: 

45 return "quarto" 

46 

47 @property 

48 def file_extension(self) -> str: 

49 return ".zip" 

50 

51 @property 

52 def mimetype(self) -> str: 

53 return "application/zip" 

54 

55 def export( 

56 self, 

57 markdown_content: str, 

58 options: Optional[ExportOptions] = None, 

59 ) -> ExportResult: 

60 """Convert markdown content to Quarto format. 

61 

62 Args: 

63 markdown_content: The markdown text to convert 

64 options: Optional export options (title, metadata) 

65 

66 Returns: 

67 ExportResult with ZIP content as bytes containing .qmd and .bib files 

68 

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 ) 

79 

80 options = options or ExportOptions() 

81 

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 ) 

91 

92 # Generate Quarto document 

93 exported_content = self._legacy.export_to_quarto( 

94 markdown_content, title 

95 ) 

96 

97 # Generate bibliography 

98 bib_content = self._legacy._generate_bibliography(markdown_content) 

99 

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

104 

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) 

112 

113 zip_bytes = zip_buffer.getvalue() 

114 zip_buffer.close() 

115 

116 filename = f"{safe_title}_quarto.zip" 

117 

118 logger.info( 

119 f"Generated Quarto with bibliography in memory (zip), " 

120 f"size: {len(zip_bytes)} bytes" 

121 ) 

122 

123 return ExportResult( 

124 content=zip_bytes, 

125 filename=filename, 

126 mimetype=self.mimetype, 

127 ) 

128 

129 except Exception: 

130 logger.exception("Error generating Quarto") 

131 raise