-
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
Conversation
- Add xelatex and lualatex as valid engine options in CLI - Implement _run_xelatex() and _run_lualatex() methods for direct compilation - Add _build_xelatex_command() and _build_lualatex_command() helpers - Update _build_latexmk_command() with pdf_mode parameter for -xelatex/-lualatex flags - Add xelatex and lualatex to dependency checks - Update sample config documentation with new engine options"
Refs #1 - Add test_latex_compiler.py with 29 tests for Issue #1 - Test _build_latexmk_command with pdf_mode parameter - Test _build_xelatex_command and _build_lualatex_command - Test _compile_once routing for all engines - Test _compile_watch rejection for direct engines - Test check_dependencies includes xelatex and lualatex - Test execution success, failure, and timeout scenarios
4a050df to
d146c40
Compare
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.
Pull request overview
This PR adds support for XeLaTeX and LuaLaTeX compilation engines to the article-cli tool, expanding beyond the existing pdflatex and latexmk options. These additional engines enable users to compile LaTeX documents that require advanced font support and Unicode handling, which are common in presentations and documents with custom fonts.
Key Changes
- Added
xelatexandlualatexas valid engine choices in CLI arguments - Implemented direct compilation methods
_run_xelatex()and_run_lualatex()with multi-pass support - Extended
_build_latexmk_command()withpdf_modeparameter to support different backend engines - Updated dependency checking to include the new engines
- Added comprehensive test coverage for all new functionality
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
src/article_cli/cli.py |
Updated CLI argument parser to accept xelatex and lualatex as engine options with helpful description |
src/article_cli/config.py |
Updated sample config documentation to list all four engine options with usage guidance |
src/article_cli/latex_compiler.py |
Implemented core functionality: routing logic, command builders, and execution methods for XeLaTeX and LuaLaTeX engines; updated latexmk command builder with pdf_mode parameter; added new engines to dependency checks |
tests/test_latex_compiler.py |
Added comprehensive test suite covering command building, routing, watch mode restrictions, execution success/failure, timeouts, and dependency checking for new engines |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """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") | ||
| """ |
Copilot
AI
Dec 8, 2025
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.
| 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 |
Copilot
AI
Dec 8, 2025
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 _run_xelatex method is almost identical to _run_pdflatex (lines 179-232) with only the engine name and command builder method differing. Consider extracting the common logic into a shared helper method like _run_engine_multi_pass(engine_name, build_command_func, tex_path, shell_escape, output_dir) to reduce code duplication and improve maintainability.
| 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 |
Copilot
AI
Dec 8, 2025
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 _run_lualatex method is almost identical to _run_xelatex and _run_pdflatex, with only the engine name and command builder method differing. This is a third instance of duplicated logic. Consider extracting the common logic into a shared helper method to reduce code duplication and improve maintainability.
| 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 |
Copilot
AI
Dec 8, 2025
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 _build_xelatex_command method is almost identical to _build_pdflatex_command with only the command name differing. Consider extracting the common logic into a shared helper method like _build_engine_command(engine_name, tex_path, shell_escape, output_dir) to reduce code duplication.
| 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 |
Copilot
AI
Dec 8, 2025
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 _build_lualatex_command method is almost identical to _build_xelatex_command and _build_pdflatex_command with only the command name differing. This is a third instance of the same duplicated logic. Consider extracting the common logic into a shared helper method to reduce code duplication.
| """ | ||
|
|
||
| import subprocess | ||
| from pathlib import Path |
Copilot
AI
Dec 8, 2025
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.
Import of 'Path' is not used.
| from pathlib import Path |
Summary
Add support for XeLaTeX and LuaLaTeX compilation engines alongside the existing pdflatex and latexmk options.
Changes
xelatexandlualatexas valid engine options in CLI_run_xelatex()and_run_lualatex()methods for direct compilation_build_xelatex_command()and_build_lualatex_command()helpers_build_latexmk_command()withpdf_modeparameter for-xelatex/-lualatexflagsUsage
Closes #1