From d081719ad4931a9af47791cc13162297d3052509 Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Tue, 29 May 2018 19:15:11 +0200 Subject: [PATCH 01/10] Add support for lualatex. --- latex/build.py | 19 ++++++++++++++----- tests/test_lualatex.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 tests/test_lualatex.py diff --git a/latex/build.py b/latex/build.py index 9fad818..3bd28cd 100644 --- a/latex/build.py +++ b/latex/build.py @@ -55,15 +55,18 @@ class LatexMkBuilder(LatexBuilder): ``$PATH``). :param xelatex: The path to the ``xelatex`` binary (will be looked up on ``$PATH``). + :param lualatex: The path to the ``lualatex`` binary (will be looked up on + ``$PATH``). :param variant: The LaTeX variant to use. Valid choices are - `pdflatex` and `xelatex`. Defaults to `pdflatex`. + `pdflatex`, `xelatex` and `lualatex`. Defaults to `pdflatex`. """ def __init__(self, latexmk='latexmk', pdflatex='pdflatex', - xelatex='xelatex', variant='pdflatex'): + xelatex='xelatex', lualatex='lualatex', variant='pdflatex'): self.latexmk = latexmk self.pdflatex = pdflatex self.xelatex = xelatex + self.lualatex = lualatex self.variant = variant @data('source') @@ -94,6 +97,10 @@ def build_pdf(self, source, texinputs=[]): args = [self.latexmk, '-xelatex', tmp.name, ] + elif self.variant == 'lualatex': + args = [self.latexmk, + '-lualatex', + tmp.name] else: raise ValueError('Invalid LaTeX variant: {}'.format( self.variant)) @@ -122,6 +129,8 @@ def is_available(self): return bool(which(self.pdflatex)) if self.variant == 'xelatex': return bool(which(self.xelatex)) + if self.variant == 'lualatex': + return bool(which(self.lualatex)) class PdfLatexBuilder(LatexBuilder): @@ -199,9 +208,10 @@ def is_available(self): 'latexmk': LatexMkBuilder, 'pdflatex': PdfLatexBuilder, 'xelatexmk': lambda: LatexMkBuilder(variant='xelatex'), + 'lualatexmk': lambda: LatexMkBuilder(variant='lualatex'), } -PREFERRED_BUILDERS = ('latexmk', 'pdflatex', 'xelatexmk') +PREFERRED_BUILDERS = ('latexmk', 'pdflatex', 'xelatexmk', 'lualatexmk') def build_pdf(source, texinputs=[], builder=None): @@ -220,10 +230,9 @@ def build_pdf(source, texinputs=[], builder=None): if builder is None: builders = PREFERRED_BUILDERS elif builder not in BUILDERS: - raise RuntimeError('Invalid Builder specified') + raise RuntimeError('Invalid Builder specified: {}'.format(builder)) else: builders = (builder, ) - for bld in builders: bld_cls = BUILDERS[bld] builder = bld_cls() diff --git a/tests/test_lualatex.py b/tests/test_lualatex.py new file mode 100644 index 0000000..adb3304 --- /dev/null +++ b/tests/test_lualatex.py @@ -0,0 +1,36 @@ +from latex import build_pdf +from latex.build import LatexMkBuilder + +# the example below should not compile on pdflatex, but on lualatex +min_latex = r""" +\documentclass[12pt]{article} +\usepackage{fontspec} + +\setmainfont{Times New Roman} + + \title{Sample font document} + \author{Hubert Farnsworth} + \date{this month, 2014} + +\begin{document} + + \maketitle + + This an \textit{example} of document compiled + with \textbf{xelatex} compiler. LuaLaTeX should + work fine also. + +\end{document} +""" + +def test_lualatex(): + builder = LatexMkBuilder(variant='lualatex') + pdf = builder.build_pdf(min_latex) + + assert pdf + + +def test_lualatexmk(): + pdf = build_pdf(min_latex, builder='lualatexmk') + + assert pdf From 3a7fc9f243d2a03486defcc8d4e3ac9fcb2d8215 Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Wed, 30 May 2018 16:19:01 +0200 Subject: [PATCH 02/10] Adjusted docstring for lualatex. --- latex/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latex/build.py b/latex/build.py index 3bd28cd..0d387bc 100644 --- a/latex/build.py +++ b/latex/build.py @@ -225,7 +225,7 @@ def build_pdf(source, texinputs=[], builder=None): :meth:`~latex.build.LatexBuilder.build_pdf` function. :param builder: Specify which builder should be used - ``latexmk``, - ``pdflatex`` or ``xelatexmk``. + ``pdflatex``, ``xelatexmk`` or `lualatexmk`. """ if builder is None: builders = PREFERRED_BUILDERS From 19dbf5ebbce317a98eb2d1ff6ea6ebfce1b7610b Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Fri, 7 Dec 2018 18:57:31 +0100 Subject: [PATCH 03/10] Fixed bug: Parsing of luatex logfile for errors works now. --- latex/build.py | 1 + tests/test_lualatex.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/latex/build.py b/latex/build.py index 0d387bc..aa57218 100644 --- a/latex/build.py +++ b/latex/build.py @@ -100,6 +100,7 @@ def build_pdf(self, source, texinputs=[]): elif self.variant == 'lualatex': args = [self.latexmk, '-lualatex', + '-latexoption=--file-line-error', tmp.name] else: raise ValueError('Invalid LaTeX variant: {}'.format( diff --git a/tests/test_lualatex.py b/tests/test_lualatex.py index adb3304..93af987 100644 --- a/tests/test_lualatex.py +++ b/tests/test_lualatex.py @@ -1,6 +1,7 @@ -from latex import build_pdf +from latex import build_pdf, LatexBuildError from latex.build import LatexMkBuilder + # the example below should not compile on pdflatex, but on lualatex min_latex = r""" \documentclass[12pt]{article} @@ -34,3 +35,12 @@ def test_lualatexmk(): pdf = build_pdf(min_latex, builder='lualatexmk') assert pdf + + +def test_luatextmk_errorlog(): + """Check if parsing of error lines works.""" + f_min_latex = min_latex.replace(r"\maketitle", r"\makexxx") + try: + build_pdf(f_min_latex, builder='lualatexmk') + except LatexBuildError as err: + assert err.get_errors() From 570b0370c08e9041d342124eb736af499af5f52b Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Tue, 7 Dec 2021 14:30:10 +0100 Subject: [PATCH 04/10] Add param 'halt_on_error' to PdfLatexBuilder.build_pdf --- latex/build.py | 8 ++++++-- tox.ini | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/latex/build.py b/latex/build.py index aa57218..3bf937d 100644 --- a/latex/build.py +++ b/latex/build.py @@ -155,7 +155,7 @@ def __init__(self, pdflatex='pdflatex', max_runs=15): self.max_runs = 15 @data('source') - def build_pdf(self, source, texinputs=[]): + def build_pdf(self, source, texinputs=[], halt_on_error=True): with TempDir() as tmpdir,\ source.temp_saved(suffix='.latex', dir=tmpdir) as tmp: @@ -166,7 +166,11 @@ def build_pdf(self, source, texinputs=[]): base_fn = os.path.splitext(tmp.name)[0] output_fn = base_fn + '.pdf' aux_fn = base_fn + '.aux' - args = [self.pdflatex, '-interaction=batchmode', '-halt-on-error', + if halt_on_error: + halt_opt = '-halt-on-error' + else: + halt_opt = '' + args = [self.pdflatex, '-interaction=batchmode', halt_opt, '-no-shell-escape', '-file-line-error', tmp.name] # create environment diff --git a/tox.ini b/tox.ini index dc0f1db..a9858ec 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,6 @@ [tox] -envlist = py27,py33,py34 +#envlist = py27,py33,py34 +envlist = py39 [testenv] deps=pytest From b93a03a85151fdcbeb2156891cda82b7de2dfce3 Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Tue, 7 Dec 2021 15:15:29 +0100 Subject: [PATCH 05/10] Add halt-on-error param to all builders --- latex/build.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/latex/build.py b/latex/build.py index 3bf937d..bf9581f 100644 --- a/latex/build.py +++ b/latex/build.py @@ -15,7 +15,7 @@ class LatexBuilder(object): """Base class for Latex builders.""" - def build_pdf(self, source, texinputs=[]): + def build_pdf(self, source, texinputs=[], halt_on_error=True): """Generates a PDF from LaTeX a source. If there are errors generating a ``LatexError`` is raised. @@ -70,7 +70,7 @@ def __init__(self, latexmk='latexmk', pdflatex='pdflatex', self.variant = variant @data('source') - def build_pdf(self, source, texinputs=[]): + def build_pdf(self, source, texinputs=[], halt_on_error=True): with TempDir() as tmpdir,\ source.temp_saved(suffix='.latex', dir=tmpdir) as tmp: @@ -79,14 +79,14 @@ def build_pdf(self, source, texinputs=[]): base_fn = os.path.splitext(tmp.name)[0] output_fn = base_fn + '.pdf' - latex_cmd = [shlex_quote(self.pdflatex), '-interaction=batchmode', - '-halt-on-error', '-no-shell-escape', '-file-line-error', '%O', '%S', ] + if halt_on_error: + latex_cmd.insert(2, '-halt-on-error') if self.variant == 'pdflatex': args = [self.latexmk, @@ -97,11 +97,15 @@ def build_pdf(self, source, texinputs=[]): args = [self.latexmk, '-xelatex', tmp.name, ] + if not halt_on_error: + args.insert(2, '-latexoption=-interaction=batchmode') elif self.variant == 'lualatex': args = [self.latexmk, '-lualatex', '-latexoption=--file-line-error', tmp.name] + if not halt_on_error: + args.insert(2, '-latexoption=-interaction=batchmode') else: raise ValueError('Invalid LaTeX variant: {}'.format( self.variant)) @@ -219,7 +223,7 @@ def is_available(self): PREFERRED_BUILDERS = ('latexmk', 'pdflatex', 'xelatexmk', 'lualatexmk') -def build_pdf(source, texinputs=[], builder=None): +def build_pdf(source, texinputs=[], builder=None, halt_on_error=True): """Builds a LaTeX source to PDF. Will automatically instantiate an available builder (or raise a @@ -243,7 +247,7 @@ def build_pdf(source, texinputs=[], builder=None): builder = bld_cls() if not builder.is_available(): continue - return builder.build_pdf(source, texinputs) + return builder.build_pdf(source, texinputs, halt_on_error) else: raise RuntimeError('No available builder could be instantiated. ' 'Please make sure LaTeX is installed.') From 6c0aa90abe732ae262d3a004d610b2537c143dd2 Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Tue, 7 Dec 2021 15:36:15 +0100 Subject: [PATCH 06/10] Unify handling of stop_on_error --- latex/build.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/latex/build.py b/latex/build.py index bf9581f..42680d6 100644 --- a/latex/build.py +++ b/latex/build.py @@ -170,12 +170,10 @@ def build_pdf(self, source, texinputs=[], halt_on_error=True): base_fn = os.path.splitext(tmp.name)[0] output_fn = base_fn + '.pdf' aux_fn = base_fn + '.aux' - if halt_on_error: - halt_opt = '-halt-on-error' - else: - halt_opt = '' args = [self.pdflatex, '-interaction=batchmode', halt_opt, '-no-shell-escape', '-file-line-error', tmp.name] + if halt_on_error: + args.insert(2, '-halt-on-error') # create environment newenv = os.environ.copy() From 6eb2ab9df4ba9cb5300ce66af68e573d93c236f2 Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Tue, 7 Dec 2021 17:12:42 +0100 Subject: [PATCH 07/10] Bugfix: remove undefined variable --- latex/build.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/latex/build.py b/latex/build.py index 42680d6..54a80fc 100644 --- a/latex/build.py +++ b/latex/build.py @@ -170,8 +170,7 @@ def build_pdf(self, source, texinputs=[], halt_on_error=True): base_fn = os.path.splitext(tmp.name)[0] output_fn = base_fn + '.pdf' aux_fn = base_fn + '.aux' - args = [self.pdflatex, '-interaction=batchmode', halt_opt, - '-no-shell-escape', '-file-line-error', tmp.name] + args = [self.pdflatex, '-interaction=batchmode'] if halt_on_error: args.insert(2, '-halt-on-error') From 92f26189de51cf4c69eae78c5c8145365191d9ac Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Mon, 25 Apr 2022 16:53:26 +0200 Subject: [PATCH 08/10] Add missing latex params --- latex/build.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/latex/build.py b/latex/build.py index 54a80fc..5d2922b 100644 --- a/latex/build.py +++ b/latex/build.py @@ -170,7 +170,8 @@ def build_pdf(self, source, texinputs=[], halt_on_error=True): base_fn = os.path.splitext(tmp.name)[0] output_fn = base_fn + '.pdf' aux_fn = base_fn + '.aux' - args = [self.pdflatex, '-interaction=batchmode'] + args = [self.pdflatex, '-interaction=batchmode', + '-no-shell-escape', '-file-line-error'] if halt_on_error: args.insert(2, '-halt-on-error') From 043f73fdec2e55e4e85942bc6781a300c89b8645 Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Thu, 28 Apr 2022 14:58:30 +0200 Subject: [PATCH 09/10] Do no longer raise error if halt_on_error is False --- latex/build.py | 169 ++++++++++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 73 deletions(-) diff --git a/latex/build.py b/latex/build.py index 5d2922b..5b574ff 100644 --- a/latex/build.py +++ b/latex/build.py @@ -61,80 +61,95 @@ class LatexMkBuilder(LatexBuilder): `pdflatex`, `xelatex` and `lualatex`. Defaults to `pdflatex`. """ - def __init__(self, latexmk='latexmk', pdflatex='pdflatex', - xelatex='xelatex', lualatex='lualatex', variant='pdflatex'): + def __init__( + self, + latexmk="latexmk", + pdflatex="pdflatex", + xelatex="xelatex", + lualatex="lualatex", + variant="pdflatex", + ): self.latexmk = latexmk self.pdflatex = pdflatex self.xelatex = xelatex self.lualatex = lualatex self.variant = variant - @data('source') + @data("source") def build_pdf(self, source, texinputs=[], halt_on_error=True): - with TempDir() as tmpdir,\ - source.temp_saved(suffix='.latex', dir=tmpdir) as tmp: + with TempDir() as tmpdir, source.temp_saved(suffix=".latex", dir=tmpdir) as tmp: # close temp file, so other processes can access it also on Windows tmp.close() base_fn = os.path.splitext(tmp.name)[0] - output_fn = base_fn + '.pdf' - latex_cmd = [shlex_quote(self.pdflatex), - '-interaction=batchmode', - '-no-shell-escape', - '-file-line-error', - '%O', - '%S', ] + output_fn = base_fn + ".pdf" + latex_cmd = [ + shlex_quote(self.pdflatex), + "-interaction=batchmode", + "-no-shell-escape", + "-file-line-error", + "%O", + "%S", + ] if halt_on_error: - latex_cmd.insert(2, '-halt-on-error') - - if self.variant == 'pdflatex': - args = [self.latexmk, - '-pdf', - '-pdflatex={}'.format(' '.join(latex_cmd)), - tmp.name, ] - elif self.variant == 'xelatex': - args = [self.latexmk, - '-xelatex', - tmp.name, ] + latex_cmd.insert(2, "-halt-on-error") + + if self.variant == "pdflatex": + args = [ + self.latexmk, + "-pdf", + "-pdflatex={}".format(" ".join(latex_cmd)), + tmp.name, + ] + elif self.variant == "xelatex": + args = [ + self.latexmk, + "-xelatex", + tmp.name, + ] if not halt_on_error: - args.insert(2, '-latexoption=-interaction=batchmode') - elif self.variant == 'lualatex': - args = [self.latexmk, - '-lualatex', - '-latexoption=--file-line-error', - tmp.name] + args.insert(2, "-latexoption=-interaction=batchmode") + elif self.variant == "lualatex": + args = [ + self.latexmk, + "-lualatex", + "-latexoption=--file-line-error", + tmp.name, + ] if not halt_on_error: - args.insert(2, '-latexoption=-interaction=batchmode') + args.insert(2, "-latexoption=-interaction=batchmode") else: - raise ValueError('Invalid LaTeX variant: {}'.format( - self.variant)) + raise ValueError("Invalid LaTeX variant: {}".format(self.variant)) # create environment newenv = os.environ.copy() - newenv['TEXINPUTS'] = os.pathsep.join(texinputs) + os.pathsep + newenv["TEXINPUTS"] = os.pathsep.join(texinputs) + os.pathsep try: - subprocess.check_call(args, - cwd=tmpdir, - env=newenv, - stdin=open(os.devnull, 'r'), - stdout=open(os.devnull, 'w'), - stderr=open(os.devnull, 'w'), ) + subprocess.check_call( + args, + cwd=tmpdir, + env=newenv, + stdin=open(os.devnull, "r"), + stdout=open(os.devnull, "w"), + stderr=open(os.devnull, "w"), + ) except CalledProcessError as e: - raise_from(LatexBuildError(base_fn + '.log'), e) + if halt_on_error: + raise_from(LatexBuildError(base_fn + ".log"), e) - return I(open(output_fn, 'rb').read(), encoding=None) + return I(open(output_fn, "rb").read(), encoding=None) def is_available(self): if not which(self.latexmk): return False - if self.variant == 'pdflatex': + if self.variant == "pdflatex": return bool(which(self.pdflatex)) - if self.variant == 'xelatex': + if self.variant == "xelatex": return bool(which(self.xelatex)) - if self.variant == 'lualatex': + if self.variant == "lualatex": return bool(which(self.lualatex)) @@ -154,46 +169,51 @@ class PdfLatexBuilder(LatexBuilder): ``pdflatex`` can be rerun before an exception is thrown. """ - def __init__(self, pdflatex='pdflatex', max_runs=15): + def __init__(self, pdflatex="pdflatex", max_runs=15): self.pdflatex = pdflatex self.max_runs = 15 - @data('source') + @data("source") def build_pdf(self, source, texinputs=[], halt_on_error=True): - with TempDir() as tmpdir,\ - source.temp_saved(suffix='.latex', dir=tmpdir) as tmp: + with TempDir() as tmpdir, source.temp_saved(suffix=".latex", dir=tmpdir) as tmp: # close temp file, so other processes can access it also on Windows tmp.close() # calculate output filename base_fn = os.path.splitext(tmp.name)[0] - output_fn = base_fn + '.pdf' - aux_fn = base_fn + '.aux' - args = [self.pdflatex, '-interaction=batchmode', - '-no-shell-escape', '-file-line-error'] + output_fn = base_fn + ".pdf" + aux_fn = base_fn + ".aux" + args = [ + self.pdflatex, + "-interaction=batchmode", + "-no-shell-escape", + "-file-line-error", + ] if halt_on_error: - args.insert(2, '-halt-on-error') + args.insert(2, "-halt-on-error") # create environment newenv = os.environ.copy() - newenv['TEXINPUTS'] = os.pathsep.join(texinputs) + os.pathsep + newenv["TEXINPUTS"] = os.pathsep.join(texinputs) + os.pathsep # run until aux file settles prev_aux = None runs_left = self.max_runs while runs_left: try: - subprocess.check_call(args, - cwd=tmpdir, - env=newenv, - stdin=open(os.devnull, 'r'), - stdout=open(os.devnull, 'w'), ) + subprocess.check_call( + args, + cwd=tmpdir, + env=newenv, + stdin=open(os.devnull, "r"), + stdout=open(os.devnull, "w"), + ) except CalledProcessError as e: - raise_from(LatexBuildError(base_fn + '.log'), e) + raise_from(LatexBuildError(base_fn + ".log"), e) # check aux-file - aux = open(aux_fn, 'rb').read() + aux = open(aux_fn, "rb").read() if aux == prev_aux: break @@ -202,23 +222,24 @@ def build_pdf(self, source, texinputs=[], halt_on_error=True): runs_left -= 1 else: raise RuntimeError( - 'Maximum number of runs ({}) without a stable .aux file ' - 'reached.'.format(self.max_runs)) + "Maximum number of runs ({}) without a stable .aux file " + "reached.".format(self.max_runs) + ) - return I(open(output_fn, 'rb').read(), encoding=None) + return I(open(output_fn, "rb").read(), encoding=None) def is_available(self): return bool(which(self.pdflatex)) BUILDERS = { - 'latexmk': LatexMkBuilder, - 'pdflatex': PdfLatexBuilder, - 'xelatexmk': lambda: LatexMkBuilder(variant='xelatex'), - 'lualatexmk': lambda: LatexMkBuilder(variant='lualatex'), + "latexmk": LatexMkBuilder, + "pdflatex": PdfLatexBuilder, + "xelatexmk": lambda: LatexMkBuilder(variant="xelatex"), + "lualatexmk": lambda: LatexMkBuilder(variant="lualatex"), } -PREFERRED_BUILDERS = ('latexmk', 'pdflatex', 'xelatexmk', 'lualatexmk') +PREFERRED_BUILDERS = ("latexmk", "pdflatex", "xelatexmk", "lualatexmk") def build_pdf(source, texinputs=[], builder=None, halt_on_error=True): @@ -237,9 +258,9 @@ def build_pdf(source, texinputs=[], builder=None, halt_on_error=True): if builder is None: builders = PREFERRED_BUILDERS elif builder not in BUILDERS: - raise RuntimeError('Invalid Builder specified: {}'.format(builder)) + raise RuntimeError("Invalid Builder specified: {}".format(builder)) else: - builders = (builder, ) + builders = (builder,) for bld in builders: bld_cls = BUILDERS[bld] builder = bld_cls() @@ -247,5 +268,7 @@ def build_pdf(source, texinputs=[], builder=None, halt_on_error=True): continue return builder.build_pdf(source, texinputs, halt_on_error) else: - raise RuntimeError('No available builder could be instantiated. ' - 'Please make sure LaTeX is installed.') + raise RuntimeError( + "No available builder could be instantiated. " + "Please make sure LaTeX is installed." + ) From 61063b48a008e90bfc9f41250544fb334e1cce0c Mon Sep 17 00:00:00 2001 From: Gunter Vasold Date: Fri, 29 Apr 2022 22:14:32 +0200 Subject: [PATCH 10/10] Disable LatexBuilder; tests for halt_on_error * LatexBuilder does not work reliable, so I redirect it to use latexmk instead * All cases for halt_on_error are now tested with all builders --- .gitignore | 8 ++ latex/build.py | 10 +- tests/conftest.py | 50 +++++++++ tests/test_halt_on_error_MkBuilder.py | 134 ++++++++++++++++++++++++ tests/test_halt_on_error_build_pdf.py | 142 ++++++++++++++++++++++++++ 5 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 tests/conftest.py create mode 100644 tests/test_halt_on_error_MkBuilder.py create mode 100644 tests/test_halt_on_error_build_pdf.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95ca2b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.pyc +venv/ +*.zip +*.lock +*.log +.vscode/ +latex.egg-info/ +sample.pdf \ No newline at end of file diff --git a/latex/build.py b/latex/build.py index 5b574ff..3c52300 100644 --- a/latex/build.py +++ b/latex/build.py @@ -154,7 +154,7 @@ def is_available(self): class PdfLatexBuilder(LatexBuilder): - """A simple pdflatex based buidler for LaTeX files. + """A simple pdflatex based builder for LaTeX files. Builds LaTeX files by copying them to a temporary directly and running ``pdflatex`` until the associated ``.aux`` file stops changing. @@ -182,6 +182,7 @@ def build_pdf(self, source, texinputs=[], halt_on_error=True): # calculate output filename base_fn = os.path.splitext(tmp.name)[0] + output_fn = base_fn + ".pdf" aux_fn = base_fn + ".aux" args = [ @@ -189,6 +190,7 @@ def build_pdf(self, source, texinputs=[], halt_on_error=True): "-interaction=batchmode", "-no-shell-escape", "-file-line-error", + tmp.name, ] if halt_on_error: args.insert(2, "-halt-on-error") @@ -210,7 +212,8 @@ def build_pdf(self, source, texinputs=[], halt_on_error=True): stdout=open(os.devnull, "w"), ) except CalledProcessError as e: - raise_from(LatexBuildError(base_fn + ".log"), e) + if halt_on_error: + raise_from(LatexBuildError(base_fn + ".log"), e) # check aux-file aux = open(aux_fn, "rb").read() @@ -234,7 +237,8 @@ def is_available(self): BUILDERS = { "latexmk": LatexMkBuilder, - "pdflatex": PdfLatexBuilder, + # "pdflatex": PdfLatexBuilder, # this is not reliable! + "pdflatex": LatexMkBuilder, "xelatexmk": lambda: LatexMkBuilder(variant="xelatex"), "lualatexmk": lambda: LatexMkBuilder(variant="lualatex"), } diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..3e8e44a --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,50 @@ +import pytest + + +@pytest.fixture +def good_minimal_latex(): + "Return a minimal latex code." + latex_code = r""" +\documentclass{article} +\begin{document} +Hello, world! +\end{document} +""" + return latex_code + + +@pytest.fixture +def bad_minimal_latex(good_minimal_latex): + return good_minimal_latex.replace("begin", "bgin") + + +@pytest.fixture +def good_extra_latex(): + "A LaTeX code, which only works with lua and xelatex." + min_latex = r""" +\documentclass[12pt]{article} +\usepackage{fontspec} + +\setmainfont{Times New Roman} + + \title{Sample font document} + \author{Hubert Farnsworth} + \date{this month, 2014} + +\begin{document} + + \maketitle + + This an \textit{example} of document compiled + with \textbf{xelatex} compiler. LuaLaTeX should + work fine also. + +\end{document} +""" + return min_latex + + +@pytest.fixture +def bad_extra_latex(good_extra_latex): + "A LateX code for lua and xelatex with error." + return good_extra_latex.replace("title", "ttle") diff --git a/tests/test_halt_on_error_MkBuilder.py b/tests/test_halt_on_error_MkBuilder.py new file mode 100644 index 0000000..725daeb --- /dev/null +++ b/tests/test_halt_on_error_MkBuilder.py @@ -0,0 +1,134 @@ +"""Tests for build_pdf with the new halt_on_error parameter. +""" +import pytest +from latex.build import LatexMkBuilder +from latex import LatexBuildError + + +##### builder is not specified + + +def test_default_good_true(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + builder = LatexMkBuilder() + pdf = builder.build_pdf(good_minimal_latex, halt_on_error=True) + assert pdf + + +def test_default_good_false(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + builder = LatexMkBuilder() + pdf = builder.build_pdf(good_minimal_latex, halt_on_error=False) + assert pdf + + +def test_default_bad_true(bad_minimal_latex): + "Invalid LaTeX with halt_on_error=True" + builder = LatexMkBuilder() + with pytest.raises(LatexBuildError): + builder.build_pdf(bad_minimal_latex, halt_on_error=True) + + +def test_default_bad_false(bad_minimal_latex): + "Invalid LaTeX with halt_on_error=True" + builder = LatexMkBuilder() + pdf = builder.build_pdf(bad_minimal_latex, halt_on_error=False) + assert pdf + + +##### builder=latexmk + +# this is missleading: if user specified pdflatex, latexmk ist used + + +##### builder=pdflatex + + +def test_pdflatex_good_true(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="pdflatex") + pdf = builder.build_pdf(good_minimal_latex, halt_on_error=True) + assert pdf + + +def test_pdflatex_good_false(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="pdflatex") + pdf = builder.build_pdf(good_minimal_latex, halt_on_error=False) + assert pdf + + +def test_pdflatex_bad_true(bad_minimal_latex): + "Invalid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="pdflatex") + with pytest.raises(LatexBuildError): + builder.build_pdf(bad_minimal_latex, halt_on_error=True) + + +def test_pdflatex_bad_false(bad_minimal_latex): + "Invalid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="pdflatex") + pdf = builder.build_pdf(bad_minimal_latex, halt_on_error=False) + assert pdf + + +##### builder=lualatex + + +def test_lualatex_good_true(good_extra_latex): + "Valid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="lualatex") + pdf = builder.build_pdf(good_extra_latex, halt_on_error=True) + assert pdf + + +def test_lualatex_good_false(good_extra_latex): + "Valid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="lualatex") + pdf = builder.build_pdf(good_extra_latex, halt_on_error=False) + assert pdf + + +def test_lualatex_bad_true(bad_extra_latex): + "Invalid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="lualatex") + with pytest.raises(LatexBuildError): + builder.build_pdf(bad_extra_latex, halt_on_error=True) + + +def test_lualatex_bad_false(bad_extra_latex): + "Invalid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="lualatex") + pdf = builder.build_pdf(bad_extra_latex, halt_on_error=False) + assert pdf + + +###### builder=xelatex + + +def test_xelatex_good_true(good_extra_latex): + "Valid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="xelatex") + pdf = builder.build_pdf(good_extra_latex, halt_on_error=True) + assert pdf + + +def test_xelatex_good_false(good_extra_latex): + "Valid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="xelatex") + pdf = builder.build_pdf(good_extra_latex, halt_on_error=False) + assert pdf + + +def test_xelatex_bad_true(bad_extra_latex): + "Invalid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="xelatex") + with pytest.raises(LatexBuildError): + builder.build_pdf(bad_extra_latex, halt_on_error=True) + + +def test_xelatex_bad_false(bad_extra_latex): + "Invalid LaTeX with halt_on_error=True" + builder = LatexMkBuilder(variant="xelatex") + pdf = builder.build_pdf(bad_extra_latex, halt_on_error=False) + assert pdf diff --git a/tests/test_halt_on_error_build_pdf.py b/tests/test_halt_on_error_build_pdf.py new file mode 100644 index 0000000..d7758d3 --- /dev/null +++ b/tests/test_halt_on_error_build_pdf.py @@ -0,0 +1,142 @@ +"""Tests for build_pdf with the new halt_on_error parameter. +""" + +import pytest +from latex import LatexBuildError, build_pdf + + +##### default (no builder specified) + + +def test_default_good_true(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_minimal_latex, halt_on_error=True) + assert pdf + + +def test_default_good_false(good_minimal_latex): + "If halt-on-error=False, no BuildError should be thrown." + pdf = build_pdf(good_minimal_latex, halt_on_error=False) + assert pdf + + +def test_default_bad_true(bad_minimal_latex): + "If halt-on-error=True (default), BuildError should be thrown if error arises." + with pytest.raises(LatexBuildError): + # builder.build_pdf(bad_latex) + build_pdf(bad_minimal_latex) + + +def test_default_bad_false(bad_minimal_latex): + "If halt-on-error=False, no BuildError should be thrown." + pdf = build_pdf(bad_minimal_latex, halt_on_error=False) + assert pdf + + +##### builder=latexmk + + +def test_pdflatexmk_good_true(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_minimal_latex, builder="latexmk", halt_on_error=True) + assert pdf + + +def test_pdflatexmk_good_false(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_minimal_latex, builder="latexmk", halt_on_error=False) + assert pdf + + +def test_pdflatmk_bad_true(bad_minimal_latex): + "Invalid LaTeX with halt_on_error=True" + with pytest.raises(LatexBuildError): + build_pdf(bad_minimal_latex, builder="latexmk", halt_on_error=True) + + +def test_pdflatexmk_bad_false(bad_minimal_latex): + "Invalid LaTeX with halt_on_error=True" + pdf = build_pdf(bad_minimal_latex, builder="latexmk", halt_on_error=False) + assert pdf + + +##### builder=pdflatex + + +def test_pdflatex_good_true(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_minimal_latex, builder="pdflatex", halt_on_error=True) + assert pdf + + +def test_pdflatex_good_false(good_minimal_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_minimal_latex, builder="pdflatex", halt_on_error=False) + assert pdf + + +def test_pdflatex_bad_true(bad_minimal_latex): + "Invalid LaTeX with halt_on_error=True" + with pytest.raises(LatexBuildError): + build_pdf(bad_minimal_latex, builder="pdflatex", halt_on_error=True) + + +# this one does not work because pdflatex does ot produce any output??? +def test_pdflatex_bad_false(bad_minimal_latex): + "Invalid LaTeX with halt_on_error=True" + pdf = build_pdf(bad_minimal_latex, builder="pdflatex", halt_on_error=False) + assert pdf + + +##### builder=lualatex + + +def test_lualatexmk_good_true(good_extra_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_extra_latex, builder="lualatexmk", halt_on_error=True) + assert pdf + + +def test_lualatexmk_good_false(good_extra_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_extra_latex, builder="lualatexmk", halt_on_error=False) + assert pdf + + +def test_lualatexmk_bad_true(bad_extra_latex): + "Invalid LaTeX with halt_on_error=True" + with pytest.raises(LatexBuildError): + build_pdf(bad_extra_latex, builder="lualatexmk", halt_on_error=True) + + +def test_lualatexmk_bad_false(bad_extra_latex): + "Invalid LaTeX with halt_on_error=True" + pdf = build_pdf(bad_extra_latex, builder="lualatexmk", halt_on_error=False) + assert pdf + + +##### builder=xelatex + + +def test_xelatexmk_good_true(good_extra_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_extra_latex, builder="xelatexmk", halt_on_error=True) + assert pdf + + +def test_xelatexmk_good_false(good_extra_latex): + "Valid LaTeX with halt_on_error=True" + pdf = build_pdf(good_extra_latex, builder="xelatexmk", halt_on_error=False) + assert pdf + + +def test_xelatexmk_bad_true(bad_extra_latex): + "Invalid LaTeX with halt_on_error=True" + with pytest.raises(LatexBuildError): + build_pdf(bad_extra_latex, builder="xelatexmk", halt_on_error=True) + + +def test_xelatexmk_bad_false(bad_extra_latex): + "Invalid LaTeX with halt_on_error=True" + pdf = build_pdf(bad_extra_latex, builder="lualatexmk", halt_on_error=False) + assert pdf