diff --git a/CHANGES.rst b/CHANGES.rst index af79573..7c96190 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,96 +7,103 @@ all releases are available on `Anaconda.org `_. +0.1.2 - 2022-xx-xx +------------------ + +- :pull:`32` implements a new interface to the compilation process which consists of + composable compilation steps. (Many thanks to :user:`axtimhaus`!:tada:) + + 0.1.1 - 2022-02-08 ------------------ -- :gh:`30` skips concurrent CI builds. -- :gh:`31` deprecates Python 3.6 and add support for Python 3.10. +- :pull:`30` skips concurrent CI builds. +- :pull:`31` deprecates Python 3.6 and add support for Python 3.10. 0.1.0 - 2021-07-21 ------------------ -- :gh:`23` updates the ``README.rst``. -- :gh:`24` replaces versioneer with setuptools-scm. -- :gh:`26` aligns pytask-latex with pytask v0.1.0. +- :pull:`23` updates the ``README.rst``. +- :pull:`24` replaces versioneer with setuptools-scm. +- :pull:`26` aligns pytask-latex with pytask v0.1.0. 0.0.12 - 2021-03-05 ------------------- -- :gh:`19` fixes some post-release issues. -- :gh:`21` adds dependencies to ``setup.py`` and install via ``conda-forge``. +- :pull:`19` fixes some post-release issues. +- :pull:`21` adds dependencies to ``setup.py`` and install via ``conda-forge``. 0.0.11 - 2021-02-25 ------------------- -- :gh:`18` prepares pytask-latex to be published on PyPI, adds versioneer and more. +- :pull:`18` prepares pytask-latex to be published on PyPI, adds versioneer and more. 0.0.10 - 2021-01-16 ------------------- -- :gh:`16` fixes the scanner by keeping only scanned dependencies which exist. Convert +- :pull:`16` fixes the scanner by keeping only scanned dependencies which exist. Convert args to strings. 0.0.9 - 2020-12-28 ------------------ -- :gh:`12` integrates the latex-dependency-scanner to automatically detect dependencies - of a LaTeX document and releases v0.0.9. -- :gh:`13` fixes the CI. +- :pull:`12` integrates the latex-dependency-scanner to automatically detect + dependencies of a LaTeX document and releases v0.0.9. +- :pull:`13` fixes the CI. 0.0.8 - 2020-10-29 ------------------ -- :gh:`11` makes pytask-latex work with pytask v0.0.9. +- :pull:`11` makes pytask-latex work with pytask v0.0.9. 0.0.7 - 2020-10-14 ------------------ -- :gh:`10` fixes error that ``outputdirectory`` has to be relative to latex document due - to security problems. +- :pull:`10` fixes error that ``outputdirectory`` has to be relative to latex document + due to security problems. 0.0.6 - 2020-10-14 ------------------ -- :gh:`9` fixes the last release and the ``pytask_collect_task_teardown`` call. +- :pull:`9` fixes the last release and the ``pytask_collect_task_teardown`` call. 0.0.5 - 2020-10-04 ------------------ -- :gh:`5` fixes some errors in the test suite due to pytask v0.0.6. -- :gh:`6` check that exit codes are equal to zero. -- :gh:`7` fixes the README. -- :gh:`8` works with pytask v0.0.7 and releases v0.0.5. +- :pull:`5` fixes some errors in the test suite due to pytask v0.0.6. +- :pull:`6` check that exit codes are equal to zero. +- :pull:`7` fixes the README. +- :pull:`8` works with pytask v0.0.7 and releases v0.0.5. 0.0.4 - 2020-08-21 ------------------ -- :gh:`4` changes the default options. latexmk will step into the source directory +- :pull:`4` changes the default options. latexmk will step into the source directory before compiling the document. Releases 0.0.4. 0.0.3 - 2020-08-12 ------------------ -- :gh:`3` prepares pytask-latex for pytask v0.0.5 and releases v0.0.3. +- :pull:`3` prepares pytask-latex for pytask v0.0.5 and releases v0.0.3. 0.0.2 - 2020-07-22 ------------------ -- :gh:`1` allowed LaTeX tasks to have more than one dependency and allows to parametrize - over latex options and latex documents. It also prepares release v0.0.2. -- :gh:`2` fixes the release. +- :pull:`1` allowed LaTeX tasks to have more than one dependency and allows to + parametrize over latex options and latex documents. It also prepares release v0.0.2. +- :pull:`2` fixes the release. 0.0.1 - 2020-07-20 diff --git a/README.rst b/README.rst index 74069b0..ac5ce14 100644 --- a/README.rst +++ b/README.rst @@ -34,11 +34,12 @@ pytask-latex ============ -pytask-latex allows you to compile LaTeX documents. +pytask-latex allows you to compile LaTeX documents with pytask -It also tries to infer the dependency of the LaTeX document such as included images, -bibliography files and other .tex files automatically to compile LaTeX documents when it -is possible. +It also uses `latex-dependency-scanner +`_ to automatically infer the +dependencies of the LaTeX document such as images, bibliographies and other ``.tex`` +files which are necessary to compile the LaTeX document. Installation @@ -65,14 +66,14 @@ following on the command line $ latexmk --help If an error is shown instead of a help page, you can install ``latexmk`` with one of the -popular LaTeX distributions, like `MiKTeX `_, `TeX Live -`_, `MacTeX `_ or others. +popular LaTeX distributions, like `TeX Live `_, `MiKTeX +`_, `MacTeX `_ or others. Usage ----- -Here is an example where you want to compile ``document.tex`` to a PDF. +Compiling your PDF can be as simple as writing the following task. .. code-block:: python @@ -85,9 +86,9 @@ Here is an example where you want to compile ``document.tex`` to a PDF. def task_compile_latex_document(): pass -When the task is executed, you find a ``document.pdf`` in the same folder as your -``document.tex``, but you could also compile the report into a another folder by -changing the path in ``produces``. +Use ``@pytask.mark.latex`` to indicate that this task compiles a LaTeX document. +``@pytask.mark.depends_on`` points to the source file which is compiled and +``@pytask.mark.produces`` is the path of the compiled PDF. Multiple dependencies and products @@ -139,46 +140,82 @@ The same applies to the compiled document which is either in the first position, the key ``"document"`` or ``0``. -Command Line Arguments -~~~~~~~~~~~~~~~~~~~~~~ +Customizing the compilation +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To customize the compilation, you can pass some command line arguments to ``latexmk`` -via the ``@pytask.mark.latex`` marker. The default is the following. +pytask-latex uses latexmk by default to compile the document because it handles most +use-cases automatically. The following is equivalent to a bare ``@pytask.mark.latex`` +decorator. .. code-block:: python - @pytask.mark.latex(["--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd"]) + @pytask.mark.latex(compilation_steps="latexmk") def task_compile_latex_document(): - pass + ... + +The ``@pytask.mark.latex`` decorator has a keyword argument called ``compilation_steps`` +which accepts which accepts strings or list of strings pointing to internally +implemented compilation steps. Using strings will use the default configuration of this +compilation step. It is equivalent to the following. + +.. code-block:: + + from pytask_latex import compilation_steps + + + @pytask.mark.latex( + compilation_steps=compilation_steps.latexmk( + options=("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd") + ) + ) + def task_compile_latex_document(): + ... -For example, to compile your document with XeLaTeX, use +In this example, ``compilation_steps.latexmk`` is a compilation step constructor which +accepts a set of options and creates a compilation step function. + +You can pass different options to change the compilation process with latexmk. Here is +an example for generating a ``.dvi``. .. code-block:: python - @pytask.mark.latex(["--xelatex", "--interaction=nonstopmode"]) + @pytask.mark.latex( + compilation_steps=compilation_steps.latexmk( + options=("--dvi", "--interaction=nonstopmode", "--synctex=1", "--cd") + ) + ) def task_compile_latex_document(): - pass + ... -The options ``--jobname``, ``--output-directory`` and the ``.tex`` file which will be -compiled are automatically handled and inferred from the ``@pytask.mark.depends_on`` and -``@pytask.mark.produces`` markers. +``compilation_step.latexmk(options)`` generates a compilation step which is a function +with the following signature: -The ``@pytask.mark.latex`` accepts both, a string or a list of strings with options. +.. code-block:: -For more options and their explanations, visit the `latexmk manual -`_ or type the following commands. + from pathlib import Path + import subprocess -.. code-block:: console - $ latexmk -h - $ latexmk -showextraoptions + def custom_compilation_step(path_to_tex: Path, path_to_document: Path) -> None: + ... + subproces.run(..., check=True) + +You can also pass your custom compilation step with the same signature to the +``compilation_steps`` keyword argument of the decorator. + +Each compilation step receives the path to the LaTeX source file and the path to the +final document which it uses to call some program on the command line to run another +step in the compilation process. + +In the future, pytask-latex will provide more compilation steps for compiling +bibliographies, glossaries and the like. Parametrization ~~~~~~~~~~~~~~~ -You can also parametrize the compilation, meaning compiling multiple .tex documents -as well as compiling a .tex document with different command line arguments. +You can also parametrize the compilation, meaning compiling multiple ``.tex`` documents +as well as compiling a ``.tex`` document with different command line arguments. The following task compiles two latex documents. @@ -195,7 +232,8 @@ The following task compiles two latex documents. If you want to compile the same document with different command line options, you have to include the latex decorator in the parametrization just like with -``@pytask.mark.depends_on`` and ``@pytask.mark.produces``. +``@pytask.mark.depends_on`` and ``@pytask.mark.produces``. Pass a dictionary for +possible compilation steps and their options. .. code-block:: python @@ -205,11 +243,19 @@ to include the latex decorator in the parametrization just like with [ ( "document.pdf", - ("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd"), + { + "compilation_steps": compilation_steps.latexmk( + ("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd") + ) + }, ), ( "document.dvi", - ("--dvi", "--interaction=nonstopmode", "--synctex=1", "--cd"), + { + "compilation_steps": compilation_steps.latexmk( + ("--dvi", "--interaction=nonstopmode", "--synctex=1", "--cd") + ) + }, ), ], ) diff --git a/environment.yml b/environment.yml index 42d89f1..9734833 100644 --- a/environment.yml +++ b/environment.yml @@ -19,7 +19,6 @@ dependencies: - black - jupyterlab - matplotlib - - pdbpp - pre-commit - pytest - pytest-cov diff --git a/src/pytask_latex/collect.py b/src/pytask_latex/collect.py index 4ab0a2b..98ac9f0 100644 --- a/src/pytask_latex/collect.py +++ b/src/pytask_latex/collect.py @@ -3,64 +3,90 @@ import copy import functools -import os -import subprocess -from pathlib import Path +import warnings +from subprocess import CalledProcessError +from typing import Any +from typing import Callable from typing import Iterable from typing import Sequence +import latex_dependency_scanner as lds from _pytask.config import hookimpl from _pytask.mark import Mark from _pytask.mark_utils import get_specific_markers_from_task -from _pytask.mark_utils import has_marker from _pytask.nodes import _collect_nodes from _pytask.nodes import FilePathNode -from _pytask.nodes import PythonFunctionTask from _pytask.parametrize import _copy_func -from latex_dependency_scanner import scan +from pytask_latex import compilation_steps as cs +from pytask_latex.utils import to_list -DEFAULT_OPTIONS = ["--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd"] +_DEPRECATION_WARNING = """The old syntax for using @pytask.mark.latex is deprecated \ +and will be removed in v0.2.0. To pass custom options to latexmk and the compilation \ +process convert + @pytask.mark.latex(options) + def task_func(): + ... -def latex(options: str | Iterable[str] | None = None): +to + + from pytask_latex import compilation_steps + + @pytask.mark.latex(compilation_steps.latexmk(options)) + def task_func(): + ... + +""" + + +def latex( + options: str | Iterable[str] | None = None, + *, + compilation_steps: str + | Callable[..., Any] + | Sequence[str | Callable[..., Any]] = None, +): """Specify command line options for latexmk. Parameters ---------- - options : Optional[Union[str, Iterable[str]]] + options One or multiple command line options passed to latexmk. + compilation_steps + Compilation steps to compile the document. """ - if options is None: - options = DEFAULT_OPTIONS.copy() - else: - options = _to_list(options) - options = [str(i) for i in options] - return options + compilation_steps = ["latexmk"] if compilation_steps is None else compilation_steps + if options is not None: + warnings.warn(_DEPRECATION_WARNING, DeprecationWarning) + out = [cs.latexmk(options)] -def compile_latex_document(latex): + else: + out = [] + for step in to_list(compilation_steps): + if isinstance(step, str): + parsed_step = getattr(cs, step) + if parsed_step is None: + raise ValueError(f"Compilation step {step!r} is unknown.") + out.append(parsed_step()) + elif callable(step): + out.append(step) + else: + raise ValueError(f"Compilation step {step!r} is not a valid step.") + + return out + + +def compile_latex_document(compilation_steps, path_to_tex, path_to_document): """Replaces the dummy function provided by the user.""" - print("Executing " + " ".join(latex) + ".") # noqa: T001 - subprocess.run(latex, check=True) - -@hookimpl -def pytask_collect_task(session, path, name, obj): - """Collect a task which is a function. - - There is some discussion on how to detect functions in this `thread - `_. :class:`types.FunctionType` does not - detect built-ins which is not possible anyway. - - """ - if name.startswith("task_") and callable(obj) and has_marker(obj, "latex"): - task = PythonFunctionTask.from_path_name_function_session( - path, name, obj, session - ) - - return task + for step in compilation_steps: + try: + step(path_to_tex=path_to_tex, path_to_document=path_to_document) + except CalledProcessError as e: + raise RuntimeError(f"Compilation step {step.__name__} failed.") from e @hookimpl @@ -88,15 +114,17 @@ def pytask_collect_task_teardown(session, task): "or .dvi file which is the compiled document." ) - latex_function = _copy_func(compile_latex_document) - latex_function.pytaskmark = copy.deepcopy(task.function.pytaskmark) + task_function = _copy_func(compile_latex_document) + task_function.pytaskmark = copy.deepcopy(task.function.pytaskmark) merged_mark = _merge_all_markers(task) - args = latex(*merged_mark.args, **merged_mark.kwargs) - options = _prepare_cmd_options(session, task, args) - latex_function = functools.partial(latex_function, latex=options) + steps = latex(*merged_mark.args, **merged_mark.kwargs) + args = get_compilation_step_args(session, task) + task_function = functools.partial( + task_function, compilation_steps=steps, **args + ) - task.function = latex_function + task.function = task_function if session.config["infer_latex_dependencies"]: task = _add_latex_dependencies_retroactively(task, session) @@ -133,7 +161,7 @@ def _add_latex_dependencies_retroactively(task, session): ) # Scan the LaTeX document for included files. - latex_dependencies = set(scan(source.path)) + latex_dependencies = set(lds.scan(source.path)) # Remove duplicated dependencies which have already been added by the user and those # which do not exist. @@ -169,22 +197,8 @@ def _merge_all_markers(task): return mark -def _prepare_cmd_options(session, task, args): - """Prepare the command line arguments to compile the LaTeX document. - - The output folder needs to be declared as a relative path to the directory where the - latex source lies. - - 1. It must be relative because bibtex / biber, which is necessary for - bibliographies, does not accept full paths as a safety measure. - 2. Due to the ``--cd`` flag, latexmk will change the directory to the one where the - source files are. Thus, relative to the latex sources. - - See this `discussion on Github - `_ - for additional information. - - """ +def get_compilation_step_args(session, task): + """Prepare arguments passe to each compilation step.""" latex_document = _get_node_from_dictionary( task.depends_on, session.config["latex_source_key"] ).value @@ -192,52 +206,4 @@ def _prepare_cmd_options(session, task, args): task.produces, session.config["latex_document_key"] ).value - # Jobname controls the name of the compiled document. No suffix! - if latex_document.stem != compiled_document.stem: - jobname = [f"--jobname={compiled_document.stem}"] - else: - jobname = [] - - # The path to the output directory must be relative from the location of the source - # file. See docstring for more information. - out_relative_to_latex_source = Path( - os.path.relpath(compiled_document.parent, latex_document.parent) - ).as_posix() - - return ( - [ - "latexmk", - *args, - ] - + jobname - + [ - f"--output-directory={out_relative_to_latex_source}", - latex_document.as_posix(), - ] - ) - - -def _to_list(scalar_or_iter): - """Convert scalars and iterables to list. - - Parameters - ---------- - scalar_or_iter : str or list - - Returns - ------- - list - - Examples - -------- - >>> _to_list("a") - ['a'] - >>> _to_list(["b"]) - ['b'] - - """ - return ( - [scalar_or_iter] - if isinstance(scalar_or_iter, str) or not isinstance(scalar_or_iter, Sequence) - else list(scalar_or_iter) - ) + return {"path_to_tex": latex_document, "path_to_document": compiled_document} diff --git a/src/pytask_latex/compilation_steps.py b/src/pytask_latex/compilation_steps.py new file mode 100644 index 0000000..ac7ba9a --- /dev/null +++ b/src/pytask_latex/compilation_steps.py @@ -0,0 +1,38 @@ +"""This module contains compilation steps for compiling a LaTeX document. + +Each compilation step must have the following signature: + +.. code-block:: + + def compilation_step(path_to_tex: Path, path_to_document: Path): + ... + +A compilation step constructor must yield a function with this signature. + +""" +from __future__ import annotations + +import subprocess + +from pytask_latex.path import relative_to +from pytask_latex.utils import to_list + + +def latexmk(options=("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd")): + """Compilation step that calls latexmk.""" + options = [str(i) for i in to_list(options)] + + def run_latexmk(path_to_tex, path_to_document): + job_name_opt = [f"--jobname={path_to_document.stem}"] + out_dir_opt = [ + f"--output-directory={relative_to(path_to_tex, path_to_document.parent)}" + ] + cmd = ( + ["latexmk", *options] + + job_name_opt + + out_dir_opt + + [path_to_tex.as_posix()] + ) + subprocess.run(cmd, check=True) + + return run_latexmk diff --git a/src/pytask_latex/parametrize.py b/src/pytask_latex/parametrize.py index 918c2c8..aa4b492 100644 --- a/src/pytask_latex/parametrize.py +++ b/src/pytask_latex/parametrize.py @@ -10,4 +10,7 @@ def pytask_parametrize_kwarg_to_marker(obj, kwargs): """Register kwargs as latex marker.""" if callable(obj): if "latex" in kwargs: - mark.latex(kwargs.pop("latex"))(obj) + if isinstance(kwargs["latex"], dict): + mark.latex(**kwargs.pop("latex"))(obj) + else: + mark.latex(kwargs.pop("latex"))(obj) diff --git a/src/pytask_latex/path.py b/src/pytask_latex/path.py new file mode 100644 index 0000000..b048ef9 --- /dev/null +++ b/src/pytask_latex/path.py @@ -0,0 +1,36 @@ +"""This module contains functions related to handling paths.""" +from __future__ import annotations + +import os +from pathlib import Path + + +def relative_to(path: Path, relative_to_: Path) -> str: + """Create a relative path from one path to another as a string. + + The output folder needs to be declared as a relative path to the directory where + the latex source lies. + + 1. It must be relative because bibtex / biber, which is necessary for + bibliographies, does not accept full paths as a safety measure. + 2. Due to the ``--cd`` flag, latexmk will change the directory to the one where the + source files are. Thus, relative to the latex sources. + + See this `discussion on Github + `_ + for additional information. + + Example + ------- + >>> import sys + >>> from pathlib import Path + >>> if sys.platform == "win32": + ... p = Path("C:/Users/user/documents/file.tex") + ... else: + ... p = Path("/home/user/documents/file.tex") + >>> out = p.parents[2].joinpath("bld", "docs") + >>> relative_to(p, out) + '../../bld/docs' + + """ + return Path(os.path.relpath(relative_to_, path.parent)).as_posix() diff --git a/src/pytask_latex/utils.py b/src/pytask_latex/utils.py new file mode 100644 index 0000000..7de13e8 --- /dev/null +++ b/src/pytask_latex/utils.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from typing import Sequence + + +def to_list(scalar_or_iter): + """Convert scalars and iterables to list. + + Parameters + ---------- + scalar_or_iter : str or list + + Returns + ------- + list + + Examples + -------- + >>> to_list("a") + ['a'] + >>> to_list(["b"]) + ['b'] + + """ + return ( + [scalar_or_iter] + if isinstance(scalar_or_iter, str) or not isinstance(scalar_or_iter, Sequence) + else list(scalar_or_iter) + ) diff --git a/tests/test_collect.py b/tests/test_collect.py index ca029ed..d94c6d1 100644 --- a/tests/test_collect.py +++ b/tests/test_collect.py @@ -1,17 +1,12 @@ from __future__ import annotations from contextlib import ExitStack as does_not_raise # noqa: N813 -from pathlib import Path import pytest from _pytask.mark import Mark from _pytask.nodes import FilePathNode from pytask_latex.collect import _get_node_from_dictionary from pytask_latex.collect import _merge_all_markers -from pytask_latex.collect import _prepare_cmd_options -from pytask_latex.collect import DEFAULT_OPTIONS -from pytask_latex.collect import latex -from pytask_latex.collect import pytask_collect_task from pytask_latex.collect import pytask_collect_task_teardown @@ -44,38 +39,6 @@ def test_merge_all_markers(marks, expected): assert out == expected -@pytest.mark.unit -@pytest.mark.parametrize( - "latex_args, expected", - [ - (None, DEFAULT_OPTIONS), - ("--some-option", ["--some-option"]), - (["--a", "--b"], ["--a", "--b"]), - ], -) -def test_latex(latex_args, expected): - options = latex(latex_args) - assert options == expected - - -@pytest.mark.unit -@pytest.mark.parametrize( - "name, expected", - [("task_dummy", True), ("invalid_name", None)], -) -def test_pytask_collect_task(name, expected): - session = DummyClass() - path = Path("some_path") - task_dummy.pytaskmark = [Mark("latex", (), {})] - - task = pytask_collect_task(session, path, name, task_dummy) - - if expected: - assert task - else: - assert not task - - @pytest.mark.unit @pytest.mark.parametrize( "depends_on, produces, expectation", @@ -138,48 +101,3 @@ def test_pytask_collect_task_teardown( def test_get_node_from_dictionary(obj, key, expected): result = _get_node_from_dictionary(obj, key) assert result == expected - - -@pytest.mark.unit -@pytest.mark.parametrize( - "args", - [ - [], - ["a"], - ["a", "b"], - ], -) -@pytest.mark.parametrize("latex_source_key", ["source", "script"]) -@pytest.mark.parametrize("latex_document_key", ["document", "compiled_doc"]) -@pytest.mark.parametrize("input_name", ["source", "main"]) -@pytest.mark.parametrize("output_name", ["source", "doc"]) -def test_prepare_cmd_options( - args, latex_source_key, latex_document_key, input_name, output_name -): - session = DummyClass() - session.config = { - "latex_source_key": latex_source_key, - "latex_document_key": latex_document_key, - } - - dependency = DummyClass() - dependency.value = Path(f"{input_name}.tex") - product = DummyClass() - product.value = Path(f"{output_name}.pdf") - task = DummyClass() - task.depends_on = {latex_source_key: dependency} - task.produces = {latex_document_key: product} - task.name = "task" - - result = _prepare_cmd_options(session, task, args) - - jobname = ( - [] - if dependency.value.stem == product.value.stem - else [f"--jobname={product.value.stem}"] - ) - - assert result == ["latexmk", *args] + jobname + [ - "--output-directory=.", - f"{input_name}.tex", - ] diff --git a/tests/test_execute.py b/tests/test_execute.py index cfd4f71..e30a9b8 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -2,7 +2,6 @@ import textwrap from contextlib import ExitStack as does_not_raise # noqa: N813 -from subprocess import CalledProcessError import pytest from _pytask.mark import Mark @@ -270,6 +269,42 @@ def task_compile_document(): """ tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) + with pytest.warns(DeprecationWarning, match="The old syntax"): + result = runner.invoke(cli, [tmp_path.as_posix()]) + + assert result.exit_code == 0 + assert tmp_path.joinpath("document.pdf").exists() + + +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_compile_latex_document_w_xelatex_new_api(runner, tmp_path): + task_source = """ + import pytask + from pytask_latex import compilation_steps + + @pytask.mark.latex( + compilation_steps=compilation_steps.latexmk( + ["--xelatex", "--interaction=nonstopmode", "--synctex=1", "--cd"] + ) + ) + @pytask.mark.depends_on("document.tex") + @pytask.mark.produces("document.pdf") + def task_compile_document(): + pass + + """ + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) + + latex_source = r""" + \documentclass{report} + \begin{document} + I got, I got, I got, I got loyalty, got royalty inside my DNA. + \end{document} + """ + tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) + result = runner.invoke(cli, [tmp_path.as_posix()]) assert result.exit_code == 0 @@ -413,11 +448,47 @@ def task_compile_document(): """ tmp_path.joinpath("sub", "document.tex").write_text(textwrap.dedent(latex_source)) + with pytest.warns(DeprecationWarning, match="The old syntax"): + session = main({"paths": tmp_path}) + + assert session.exit_code == 1 + assert len(session.tasks) == 1 + assert isinstance(session.execution_reports[0].exc_info[1], RuntimeError) + + +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_compile_document_w_wrong_flag_new_api(tmp_path): + """Test that wrong flags raise errors.""" + tmp_path.joinpath("sub").mkdir(parents=True) + + task_source = """ + import pytask + from pytask_latex import compilation_steps + + @pytask.mark.latex(compilation_steps=compilation_steps.latexmk("--wrong-flag")) + @pytask.mark.depends_on("document.tex") + @pytask.mark.produces("out/document.pdf") + def task_compile_document(): + pass + + """ + tmp_path.joinpath("sub", "task_dummy.py").write_text(textwrap.dedent(task_source)) + + latex_source = r""" + \documentclass{report} + \begin{document} + The book of love is long and boring ... + \end{document} + """ + tmp_path.joinpath("sub", "document.tex").write_text(textwrap.dedent(latex_source)) + session = main({"paths": tmp_path}) assert session.exit_code == 1 assert len(session.tasks) == 1 - assert isinstance(session.execution_reports[0].exc_info[1], CalledProcessError) + assert isinstance(session.execution_reports[0].exc_info[1], RuntimeError) @needs_latexmk diff --git a/tests/test_parallel.py b/tests/test_parallel.py index 77afd8d..401e719 100644 --- a/tests/test_parallel.py +++ b/tests/test_parallel.py @@ -1,6 +1,7 @@ """Contains test which ensure that the plugin works with pytask-parallel.""" from __future__ import annotations +import os import textwrap import time @@ -21,8 +22,12 @@ not _IS_PYTASK_PARALLEL_INSTALLED, reason="Tests require pytask-parallel." ) +xfail_on_remote = pytest.mark.xfail( + condition=os.environ.get("CI") == "true", reason="Does not succeed on CI." +) + -@pytest.mark.xfail(reason="I don't know.") +@xfail_on_remote @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end @@ -58,6 +63,7 @@ def task_compile_latex_document(): start = time.time() result = runner.invoke(cli, [tmp_path.as_posix()]) + assert result.exit_code == 0 duration_normal = time.time() - start @@ -66,13 +72,14 @@ def task_compile_latex_document(): start = time.time() result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) + assert result.exit_code == 0 duration_parallel = time.time() - start assert duration_parallel < duration_normal -@pytest.mark.xfail(reason="I don't know.") +@xfail_on_remote @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end @@ -107,8 +114,69 @@ def task_compile_latex_document(): """ tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) + start = time.time() + with pytest.warns(DeprecationWarning, match="The old syntax"): + result = runner.invoke(cli, [tmp_path.as_posix()]) + + assert result.exit_code == 0 + duration_normal = time.time() - start + + for name in ["document.pdf", "document.dvi"]: + tmp_path.joinpath(name).unlink() + + start = time.time() + with pytest.warns(DeprecationWarning, match="The old syntax"): + result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) + + assert result.exit_code == 0 + duration_parallel = time.time() - start + + assert duration_parallel < duration_normal + + +@xfail_on_remote +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_parallel_parametrization_over_source_file_new_api(runner, tmp_path): + source = """ + import pytask + from pytask_latex import compilation_steps + + @pytask.mark.depends_on("document.tex") + @pytask.mark.parametrize( + "produces, latex", + [ + ( + "document.pdf", + {"compilation_steps": compilation_steps.latexmk( + ("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd") + )} + ), + ( + "document.dvi", + {"compilation_steps": compilation_steps.latexmk( + ("--dvi", "--interaction=nonstopmode", "--synctex=1", "--cd") + )} + ), + ], + ) + def task_compile_latex_document(): + pass + """ + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(source)) + + latex_source = r""" + \documentclass{report} + \begin{document} + Ma il mio mistero e chiuso in me + \end{document} + """ + tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) + start = time.time() result = runner.invoke(cli, [tmp_path.as_posix()]) + assert result.exit_code == 0 duration_normal = time.time() - start @@ -117,6 +185,7 @@ def task_compile_latex_document(): start = time.time() result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) + assert result.exit_code == 0 duration_parallel = time.time() - start diff --git a/tests/test_parametrize.py b/tests/test_parametrize.py index c15a734..b4a9e67 100644 --- a/tests/test_parametrize.py +++ b/tests/test_parametrize.py @@ -99,6 +99,51 @@ def task_compile_latex_document(): """ tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) + with pytest.warns(DeprecationWarning, match="The old syntax"): + session = main({"paths": tmp_path}) + + assert session.exit_code == 0 + assert tmp_path.joinpath("document.pdf").exists() + assert tmp_path.joinpath("document.dvi").exists() + + +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_parametrizing_latex_options_new_api(tmp_path): + task_source = """ + import pytask + from pytask_latex import compilation_steps + + @pytask.mark.parametrize("depends_on, produces, latex", [ + ( + "document.tex", + "document.pdf", + {"compilation_steps": compilation_steps.latexmk( + ("--interaction=nonstopmode", "--pdf", "--cd")) + } + ), + ( + "document.tex", + "document.dvi", + {"compilation_steps": compilation_steps.latexmk( + ("--interaction=nonstopmode", "--dvi", "--cd")) + } + ), + ]) + def task_compile_latex_document(): + pass + """ + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) + + latex_source = r""" + \documentclass{report} + \begin{document} + I can't stop this feeling. Deep inside of me. + \end{document} + """ + tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) + session = main({"paths": tmp_path}) assert session.exit_code == 0 diff --git a/tox.ini b/tox.ini index a0b4f89..178e860 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ skipsdist = True skip_missing_interpreters = True [testenv] -passenv = GITHUB_ACTIONS +passenv = CI GITHUB_ACTIONS basepython = python [testenv:pytest]