import hashlib import subprocess import tempfile from dataclasses import dataclass from functools import lru_cache from pathlib import Path @dataclass class CompilationResult: success: bool pdf_data: bytes = b"" error_log: str = "" def _compute_hash(content: str) -> str: return hashlib.md5(content.encode()).hexdigest() @lru_cache(maxsize=128) def _cached_compile(content_hash: str, latex_content: str) -> tuple[bool, bytes, str]: with tempfile.TemporaryDirectory() as tmpdir: tmppath = Path(tmpdir) tex_file = tmppath / "document.tex" tex_file.write_text(latex_content) try: result = subprocess.run( [ "pdflatex", "-interaction=nonstopmode", "-halt-on-error", "-output-directory", str(tmppath), str(tex_file) ], capture_output=True, text=True, timeout=30 ) pdf_file = tmppath / "document.pdf" if pdf_file.exists(): return (True, pdf_file.read_bytes(), "") else: log_file = tmppath / "document.log" error_log = log_file.read_text() if log_file.exists() else result.stdout return (False, b"", _extract_errors(error_log)) except subprocess.TimeoutExpired: return (False, b"", "Compilation timed out after 30 seconds") except FileNotFoundError: return (False, b"", "pdflatex not found. Please install TeX Live.") except Exception as e: return (False, b"", f"Compilation error: {str(e)}") def _extract_errors(log: str) -> str: lines = log.split("\n") error_lines = [] capture = False for line in lines: if line.startswith("!"): capture = True if capture: error_lines.append(line) if line.strip() == "" and error_lines: capture = False if len(error_lines) > 50: break return "\n".join(error_lines) if error_lines else log[-2000:] def compile_latex(latex_content: str) -> CompilationResult: content_hash = _compute_hash(latex_content) success, pdf_data, error_log = _cached_compile(content_hash, latex_content) return CompilationResult(success=success, pdf_data=pdf_data, error_log=error_log)