quizcomp.external.latex
1import argparse 2import logging 3import os 4import shutil 5import subprocess 6import typing 7 8_logger = logging.getLogger(__name__) 9 10_pdflatex_bin_path: typing.Union[str, None] = None # pylint: disable=invalid-name 11_pdflatex_use_docker: bool = False # pylint: disable=invalid-name 12 13DOCKER_IMAGE: str = "ghcr.io/edulinq/pdflatex-docker:1.0.0" 14 15def set_pdflatex_bin_path(path: str) -> None: 16 """ Set the binary path to use for NodeJS (node). """ 17 18 global _pdflatex_bin_path # pylint: disable=global-statement 19 _pdflatex_bin_path = path 20 21def set_pdflatex_use_docker(pdflatex_use_docker: bool) -> None: 22 """ Set whether or not to use the pdflatex Docker container. """ 23 24 global _pdflatex_use_docker # pylint: disable=global-statement 25 _pdflatex_use_docker = pdflatex_use_docker 26 27def is_available() -> bool: 28 """ Check if LaTeX is available on this system. """ 29 30 if (_pdflatex_use_docker): 31 if (not _is_docker_available()): 32 _logger.warning("Docker is not available, cannot compile PDFs.") 33 return False 34 35 return True 36 37 if (_pdflatex_bin_path is not None): 38 return True 39 40 if (shutil.which('pdflatex') is None): 41 _logger.warning("Could not find `pdflatex`, cannot compile PDFs.") 42 return False 43 44 return True 45 46def _is_docker_available() -> bool: 47 """ Check if Docker is available on this system. """ 48 49 if (shutil.which('docker') is None): 50 return False 51 52 result = subprocess.run(["docker", "info"], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL, check = False) 53 return (result.returncode == 0) 54 55def compile(path: str) -> None: 56 """ 57 Compile a LaTeX file to PDF in its containing directory. 58 59 The caller must provide a path to a TeX file within a prepared output directory. 60 This directory should contain all necessary resources (e.g., images) and no non-relevant files. 61 Compilation may generate additional files (e.g., .aux, .log) in this directory, 62 and permissions may be modified as needed. 63 """ 64 65 if (_pdflatex_use_docker is True): 66 _compile_docker(path) 67 else: 68 _compile_local(path) 69 70def _compile_local(path: str) -> None: 71 """ Compile without using Docker. """ 72 73 bin_path = "pdflatex" 74 if (_pdflatex_bin_path is not None): 75 bin_path = _pdflatex_bin_path 76 77 tex_filename = os.path.basename(path) 78 out_dir = os.path.dirname(path) 79 80 # Need to compile twice to get positioning information. 81 for _ in range(2): 82 result = subprocess.run([bin_path, '-interaction=nonstopmode', tex_filename], 83 cwd = out_dir, text = True, capture_output = True, check = False) 84 if (result.returncode != 0): 85 raise ValueError(f"pdflatex did not exit cleanly. Stdout: '{result.stdout}', Stderr: '{result.stderr}'") 86 87def _compile_docker(path: str) -> None: 88 """ Compile using Docker. """ 89 90 tex_filename = os.path.basename(path) 91 out_dir_path = os.path.abspath(os.path.dirname(path)) 92 93 docker_cmd = [ 94 "docker", "run", "--rm", 95 "-v", f"{out_dir_path}:/work", 96 DOCKER_IMAGE, 97 tex_filename 98 ] 99 100 result = subprocess.run(docker_cmd, capture_output = True, text = True, check = False) 101 if (result.returncode != 0): 102 raise ValueError(f"Docker compilation failed with exit code '{result.returncode}'. Stdout: '{result.stdout}', Stderr: '{result.stderr}'") 103 104def set_cli_args(parser: argparse.ArgumentParser, extra_state: typing.Dict[str, typing.Any]) -> None: 105 """ Set LaTeX-related CLI options. """ 106 107 parser.add_argument('--pdflatex-bin-path', dest = 'pdflatex_bin_path', 108 action = 'store', type = str, default = None, 109 help = ('The path to the pdflatex binary to use.' 110 + ' If not specified, $PATH will be searched.' 111 + ' Used to compile PDFs.')) 112 113 parser.add_argument('--pdflatex-use-docker', dest = 'pdflatex_use_docker', 114 action = 'store_true', default = False, 115 help = ('Use Docker to compile PDFs with pdflatex.' 116 + f" The Docker image '{DOCKER_IMAGE}' will be used.")) 117 118def init_from_args(parser: argparse.ArgumentParser, args: argparse.Namespace, extra_state: typing.Dict[str, typing.Any]) -> None: 119 """ Initialize this module from the CLI options set in set_cli_args(). """ 120 121 if (args.pdflatex_use_docker): 122 set_pdflatex_use_docker(args.pdflatex_use_docker) 123 124 if (args.pdflatex_bin_path is not None): 125 set_pdflatex_bin_path(args.pdflatex_bin_path)
DOCKER_IMAGE: str =
'ghcr.io/edulinq/pdflatex-docker:1.0.0'
def
set_pdflatex_bin_path(path: str) -> None:
16def set_pdflatex_bin_path(path: str) -> None: 17 """ Set the binary path to use for NodeJS (node). """ 18 19 global _pdflatex_bin_path # pylint: disable=global-statement 20 _pdflatex_bin_path = path
Set the binary path to use for NodeJS (node).
def
set_pdflatex_use_docker(pdflatex_use_docker: bool) -> None:
22def set_pdflatex_use_docker(pdflatex_use_docker: bool) -> None: 23 """ Set whether or not to use the pdflatex Docker container. """ 24 25 global _pdflatex_use_docker # pylint: disable=global-statement 26 _pdflatex_use_docker = pdflatex_use_docker
Set whether or not to use the pdflatex Docker container.
def
is_available() -> bool:
28def is_available() -> bool: 29 """ Check if LaTeX is available on this system. """ 30 31 if (_pdflatex_use_docker): 32 if (not _is_docker_available()): 33 _logger.warning("Docker is not available, cannot compile PDFs.") 34 return False 35 36 return True 37 38 if (_pdflatex_bin_path is not None): 39 return True 40 41 if (shutil.which('pdflatex') is None): 42 _logger.warning("Could not find `pdflatex`, cannot compile PDFs.") 43 return False 44 45 return True
Check if LaTeX is available on this system.
def
compile(path: str) -> None:
56def compile(path: str) -> None: 57 """ 58 Compile a LaTeX file to PDF in its containing directory. 59 60 The caller must provide a path to a TeX file within a prepared output directory. 61 This directory should contain all necessary resources (e.g., images) and no non-relevant files. 62 Compilation may generate additional files (e.g., .aux, .log) in this directory, 63 and permissions may be modified as needed. 64 """ 65 66 if (_pdflatex_use_docker is True): 67 _compile_docker(path) 68 else: 69 _compile_local(path)
Compile a LaTeX file to PDF in its containing directory.
The caller must provide a path to a TeX file within a prepared output directory. This directory should contain all necessary resources (e.g., images) and no non-relevant files. Compilation may generate additional files (e.g., .aux, .log) in this directory, and permissions may be modified as needed.
def
set_cli_args(parser: argparse.ArgumentParser, extra_state: Dict[str, Any]) -> None:
105def set_cli_args(parser: argparse.ArgumentParser, extra_state: typing.Dict[str, typing.Any]) -> None: 106 """ Set LaTeX-related CLI options. """ 107 108 parser.add_argument('--pdflatex-bin-path', dest = 'pdflatex_bin_path', 109 action = 'store', type = str, default = None, 110 help = ('The path to the pdflatex binary to use.' 111 + ' If not specified, $PATH will be searched.' 112 + ' Used to compile PDFs.')) 113 114 parser.add_argument('--pdflatex-use-docker', dest = 'pdflatex_use_docker', 115 action = 'store_true', default = False, 116 help = ('Use Docker to compile PDFs with pdflatex.' 117 + f" The Docker image '{DOCKER_IMAGE}' will be used."))
Set LaTeX-related CLI options.
def
init_from_args( parser: argparse.ArgumentParser, args: argparse.Namespace, extra_state: Dict[str, Any]) -> None:
119def init_from_args(parser: argparse.ArgumentParser, args: argparse.Namespace, extra_state: typing.Dict[str, typing.Any]) -> None: 120 """ Initialize this module from the CLI options set in set_cli_args(). """ 121 122 if (args.pdflatex_use_docker): 123 set_pdflatex_use_docker(args.pdflatex_use_docker) 124 125 if (args.pdflatex_bin_path is not None): 126 set_pdflatex_bin_path(args.pdflatex_bin_path)
Initialize this module from the CLI options set in set_cli_args().