-
Notifications
You must be signed in to change notification settings - Fork 1
feat(compile): add XeLaTeX and LuaLaTeX engine support #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,10 @@ def _compile_once( | |
| return self._run_latexmk(tex_path, shell_escape, output_dir) | ||
| elif engine == "pdflatex": | ||
| return self._run_pdflatex(tex_path, shell_escape, output_dir) | ||
| elif engine == "xelatex": | ||
| return self._run_xelatex(tex_path, shell_escape, output_dir) | ||
| elif engine == "lualatex": | ||
| return self._run_lualatex(tex_path, shell_escape, output_dir) | ||
| else: | ||
| print_error(f"Unknown engine: {engine}") | ||
| return False | ||
|
|
@@ -76,8 +80,11 @@ def _compile_watch( | |
| self, tex_path: Path, engine: str, shell_escape: bool, output_dir: Optional[str] | ||
| ) -> bool: | ||
| """Compile document and watch for changes""" | ||
| if engine == "pdflatex": | ||
| print_error("Watch mode is only supported with latexmk engine") | ||
| if engine in ["pdflatex", "xelatex", "lualatex"]: | ||
| print_error( | ||
| "Watch mode is only supported with latexmk engine. " | ||
| "Use: article-cli compile --engine latexmk --watch" | ||
| ) | ||
| return False | ||
|
|
||
| print_info("Starting watch mode. Press Ctrl+C to stop.") | ||
|
|
@@ -230,17 +237,33 @@ def _build_latexmk_command( | |
| shell_escape: bool, | ||
| output_dir: Optional[str], | ||
| continuous: bool = False, | ||
| pdf_mode: str = "pdf", | ||
| ) -> List[str]: | ||
| """Build latexmk command based on LaTeX Workshop configuration""" | ||
| """Build latexmk command based on LaTeX Workshop configuration | ||
|
|
||
| Args: | ||
| tex_path: Path to .tex file | ||
| shell_escape: Enable shell escape | ||
| output_dir: Output directory | ||
| continuous: Enable preview continuous mode | ||
| pdf_mode: PDF generation mode ("pdf", "xelatex", "lualatex") | ||
| """ | ||
| cmd = ["latexmk"] | ||
|
|
||
| # Core options (from LaTeX Workshop) | ||
| if shell_escape: | ||
| cmd.append("--shell-escape") | ||
|
|
||
| # Select PDF generation engine | ||
| if pdf_mode == "xelatex": | ||
| cmd.append("-xelatex") | ||
| elif pdf_mode == "lualatex": | ||
| cmd.append("-lualatex") | ||
| else: | ||
| cmd.append("-pdf") # Default: pdflatex | ||
|
|
||
| cmd.extend( | ||
| [ | ||
| "-pdf", | ||
| "-interaction=nonstopmode", | ||
| "-synctex=1", | ||
| ] | ||
|
|
@@ -281,6 +304,164 @@ def _build_pdflatex_command( | |
|
|
||
| return cmd | ||
|
|
||
| def _run_xelatex( | ||
| self, tex_path: Path, shell_escape: bool, output_dir: Optional[str] | ||
| ) -> bool: | ||
| """Run xelatex compilation (multiple passes for cross-references)""" | ||
| cmd = self._build_xelatex_command(tex_path, shell_escape, output_dir) | ||
|
|
||
| try: | ||
| # Run multiple passes for cross-references, bibliography, etc. | ||
| passes = ["First pass", "Second pass", "Third pass"] | ||
|
|
||
| for i, pass_name in enumerate(passes): | ||
| print_info(f"{pass_name} (xelatex)...") | ||
| result = subprocess.run( | ||
| cmd, | ||
| cwd=tex_path.parent, | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=120, # 2 minute timeout per pass | ||
| ) | ||
|
|
||
| if result.returncode != 0: | ||
| print_error(f"❌ {pass_name} failed") | ||
| if result.stdout: | ||
| print("STDOUT:") | ||
| print(result.stdout) | ||
| if result.stderr: | ||
| print("STDERR:") | ||
| print(result.stderr) | ||
| return False | ||
|
|
||
| # Check if we need to run bibtex/biber | ||
| if i == 0: # After first pass | ||
| self._run_bibliography_if_needed(tex_path, result.stdout) | ||
|
|
||
| pdf_name = tex_path.with_suffix(".pdf").name | ||
| if output_dir: | ||
| pdf_path = Path(output_dir) / pdf_name | ||
| else: | ||
| pdf_path = tex_path.with_suffix(".pdf") | ||
|
|
||
| if pdf_path.exists(): | ||
| print_success(f"✅ Compilation successful: {pdf_path}") | ||
| self._show_pdf_info(pdf_path) | ||
| return True | ||
| else: | ||
| print_error("Compilation reported success but PDF not found") | ||
| return False | ||
|
|
||
| except subprocess.TimeoutExpired: | ||
| print_error("Compilation timed out") | ||
| return False | ||
| except Exception as e: | ||
| print_error(f"Compilation error: {e}") | ||
| return False | ||
|
Comment on lines
+307
to
+360
|
||
|
|
||
| def _build_xelatex_command( | ||
| self, tex_path: Path, shell_escape: bool, output_dir: Optional[str] | ||
| ) -> List[str]: | ||
| """Build xelatex command""" | ||
| cmd = ["xelatex"] | ||
|
|
||
| if shell_escape: | ||
| cmd.append("--shell-escape") | ||
|
|
||
| cmd.extend( | ||
| [ | ||
| "-synctex=1", | ||
| "-interaction=nonstopmode", | ||
| "-file-line-error", | ||
| ] | ||
| ) | ||
|
|
||
| if output_dir: | ||
| cmd.extend(["-output-directory", output_dir]) | ||
|
|
||
| cmd.append(str(tex_path)) | ||
|
|
||
| return cmd | ||
|
Comment on lines
+362
to
+384
|
||
|
|
||
| def _run_lualatex( | ||
| self, tex_path: Path, shell_escape: bool, output_dir: Optional[str] | ||
| ) -> bool: | ||
| """Run lualatex compilation (multiple passes for cross-references)""" | ||
| cmd = self._build_lualatex_command(tex_path, shell_escape, output_dir) | ||
|
|
||
| try: | ||
| # Run multiple passes for cross-references, bibliography, etc. | ||
| passes = ["First pass", "Second pass", "Third pass"] | ||
|
|
||
| for i, pass_name in enumerate(passes): | ||
| print_info(f"{pass_name} (lualatex)...") | ||
| result = subprocess.run( | ||
| cmd, | ||
| cwd=tex_path.parent, | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=120, # 2 minute timeout per pass | ||
| ) | ||
|
|
||
| if result.returncode != 0: | ||
| print_error(f"❌ {pass_name} failed") | ||
| if result.stdout: | ||
| print("STDOUT:") | ||
| print(result.stdout) | ||
| if result.stderr: | ||
| print("STDERR:") | ||
| print(result.stderr) | ||
| return False | ||
|
|
||
| # Check if we need to run bibtex/biber | ||
| if i == 0: # After first pass | ||
| self._run_bibliography_if_needed(tex_path, result.stdout) | ||
|
|
||
| pdf_name = tex_path.with_suffix(".pdf").name | ||
| if output_dir: | ||
| pdf_path = Path(output_dir) / pdf_name | ||
| else: | ||
| pdf_path = tex_path.with_suffix(".pdf") | ||
|
|
||
| if pdf_path.exists(): | ||
| print_success(f"✅ Compilation successful: {pdf_path}") | ||
| self._show_pdf_info(pdf_path) | ||
| return True | ||
| else: | ||
| print_error("Compilation reported success but PDF not found") | ||
| return False | ||
|
|
||
| except subprocess.TimeoutExpired: | ||
| print_error("Compilation timed out") | ||
| return False | ||
| except Exception as e: | ||
| print_error(f"Compilation error: {e}") | ||
| return False | ||
|
Comment on lines
+386
to
+439
|
||
|
|
||
| def _build_lualatex_command( | ||
| self, tex_path: Path, shell_escape: bool, output_dir: Optional[str] | ||
| ) -> List[str]: | ||
| """Build lualatex command""" | ||
| cmd = ["lualatex"] | ||
|
|
||
| if shell_escape: | ||
| cmd.append("--shell-escape") | ||
|
|
||
| cmd.extend( | ||
| [ | ||
| "-synctex=1", | ||
| "-interaction=nonstopmode", | ||
| "-file-line-error", | ||
| ] | ||
| ) | ||
|
|
||
| if output_dir: | ||
| cmd.extend(["-output-directory", output_dir]) | ||
|
|
||
| cmd.append(str(tex_path)) | ||
|
|
||
| return cmd | ||
|
Comment on lines
+441
to
+463
|
||
|
|
||
| def _run_bibliography_if_needed(self, tex_path: Path, latex_output: str) -> None: | ||
| """Run bibtex or biber if needed based on latex output""" | ||
| base_name = tex_path.stem | ||
|
|
@@ -332,6 +513,8 @@ def check_dependencies(self) -> Dict[str, bool]: | |
| tools = { | ||
| "latexmk": self._check_command("latexmk"), | ||
| "pdflatex": self._check_command("pdflatex"), | ||
| "xelatex": self._check_command("xelatex"), | ||
| "lualatex": self._check_command("lualatex"), | ||
| "bibtex": self._check_command("bibtex"), | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring mentions LaTeX Workshop configuration, but the function now has additional functionality (pdf_mode parameter) that isn't specifically related to LaTeX Workshop. Consider updating the main description to be more generic or explain that the pdf_mode extension allows for different LaTeX engines.