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

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 

18@ExporterRegistry.register 

19class QuartoExporter(BaseExporter): 

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

21 

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. 

25 

26 The export produces a ZIP file containing: 

27 - The .qmd document 

28 - A references.bib bibliography file 

29 """ 

30 

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 ) 

37 

38 self._legacy = LegacyQuartoExporter() 

39 

40 @property 

41 def format_name(self) -> str: 

42 return "quarto" 

43 

44 @property 

45 def file_extension(self) -> str: 

46 return ".zip" 

47 

48 @property 

49 def mimetype(self) -> str: 

50 return "application/zip" 

51 

52 def export( 

53 self, 

54 markdown_content: str, 

55 options: Optional[ExportOptions] = None, 

56 ) -> ExportResult: 

57 """Convert markdown content to Quarto format. 

58 

59 Args: 

60 markdown_content: The markdown text to convert 

61 options: Optional export options (title, metadata) 

62 

63 Returns: 

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

65 

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) 

72 

73 options = options or ExportOptions() 

74 

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 ) 

84 

85 # Generate Quarto document 

86 exported_content = self._legacy.export_to_quarto( 

87 markdown_content, title 

88 ) 

89 

90 # Generate bibliography 

91 bib_content = self._legacy._generate_bibliography(markdown_content) 

92 

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

97 

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) 

105 

106 zip_bytes = zip_buffer.getvalue() 

107 zip_buffer.close() 

108 

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

110 

111 logger.info( 

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

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

114 ) 

115 

116 return ExportResult( 

117 content=zip_bytes, 

118 filename=filename, 

119 mimetype=self.mimetype, 

120 ) 

121 

122 except Exception: 

123 logger.exception("Error generating Quarto") 

124 raise