diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9dbe9d4..72a5962 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,24 +1,21 @@ ---- +______________________________________________________________________ -name: Bug Report -about: Create a bug report to help us improve pytask-latex -title: "BUG:" -labels: "bug" +name: Bug Report about: Create a bug report to help us improve pytask-latex title: +"BUG:" labels: "bug" ---- +______________________________________________________________________ - [ ] I have checked that this issue has not already been reported. - [ ] I have confirmed this bug exists on the latest version of pytask-latex. -- [ ] (optional) I have confirmed this bug exists on the `main` branch of - pytask-latex. +- [ ] (optional) I have confirmed this bug exists on the `main` branch of pytask-latex. ---- +______________________________________________________________________ -**Note**: Please read [this -guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) detailing -how to provide the necessary information for us to reproduce your bug. +**Note**: Please read +[this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) +detailing how to provide the necessary information for us to reproduce your bug. #### Code Sample, a copy-pastable example diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md index 2a6cb13..a7419fd 100644 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -1,11 +1,9 @@ ---- +______________________________________________________________________ -name: Documentation Improvement -about: Report wrong or missing documentation -title: "DOC:" -labels: "documentation" +name: Documentation Improvement about: Report wrong or missing documentation title: +"DOC:" labels: "documentation" ---- +______________________________________________________________________ #### Location of the documentation diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md index e09dfaf..6409c05 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -1,16 +1,14 @@ ---- +______________________________________________________________________ -name: Enhancement -about: Suggest an idea for pytask-latex -title: "ENH:" -labels: "enhancement" +name: Enhancement about: Suggest an idea for pytask-latex title: "ENH:" labels: +"enhancement" ---- +______________________________________________________________________ #### Is your feature request related to a problem? -Provide a description of what the problem is, e.g. "I wish I could use pytask-latex -to do [...]". +Provide a description of what the problem is, e.g. "I wish I could use pytask-latex to +do \[...\]". #### Describe the solution you'd like diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 073ae3b..530c08a 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,17 +1,15 @@ ---- +______________________________________________________________________ -name: Submit Question -about: Ask a general question about pytask-latex -title: "QST:" +name: Submit Question about: Ask a general question about pytask-latex title: "QST:" labels: "question" ---- +______________________________________________________________________ #### Question about pytask-latex -**Note**: If you'd still like to submit a question, please read [this guide]( -https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) detailing how to -provide the necessary information for us to reproduce your question. +**Note**: If you'd still like to submit a question, please read +[this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) +detailing how to provide the necessary information for us to reproduce your question. ```python # Your code here, if applicable diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1817a2f..b627f37 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,4 +6,4 @@ Provide a description and/or bullet points to describe the changes in this PR. - [ ] Reference issues which can be closed due to this PR with "Closes #x". - [ ] Review whether the documentation needs to be updated. -- [ ] Document PR in docs/changes.rst. +- [ ] Document PR in CHANGES.md. diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/main.yml similarity index 97% rename from .github/workflows/continuous-integration-workflow.yml rename to .github/workflows/main.yml index c35a940..c6e429d 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Continuous Integration Workflow +name: main # Automatically cancel a previous run. concurrency: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d27758..07e365d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,9 +24,6 @@ repos: - id: python-no-eval - id: python-no-log-warn - id: python-use-type-annotations - - id: rst-backticks - - id: rst-directive-colons - - id: rst-inline-touching-normal - id: text-unicode-replacement-char - repo: https://github.com/asottile/pyupgrade rev: v2.32.0 @@ -72,15 +69,20 @@ repos: pydocstyle, Pygments, ] -- repo: https://github.com/PyCQA/doc8 - rev: 0.11.1 - hooks: - - id: doc8 - repo: https://github.com/econchick/interrogate rev: 1.5.0 hooks: - id: interrogate args: [-v, --fail-under=40, src, tests] +- repo: https://github.com/executablebooks/mdformat + rev: 0.7.14 + hooks: + - id: mdformat + additional_dependencies: [ + mdformat-gfm, + mdformat-black, + ] + args: [--wrap, "88"] - repo: https://github.com/codespell-project/codespell rev: v2.1.0 hooks: @@ -90,6 +92,8 @@ repos: rev: "0.48" hooks: - id: check-manifest + args: [--no-build-isolation] + additional_dependencies: [setuptools-scm, toml] - repo: meta hooks: - id: check-hooks-apply diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..0c549f2 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,88 @@ +# Changes + +This is a record of all past pytask-latex releases and what went into them in reverse +chronological order. Releases follow [semantic versioning](https://semver.org/) and all +releases are available on [Anaconda.org](https://anaconda.org/conda-forge/pytask-latex). + +## 0.2.0 - 2022-04-16 + +- {pull}`33` aligns pytask-latex with the pytask v0.2. +- {pull}`34` deprecates the old api. + +## 0.1.2 - 2022-03-26 + +- {pull}`32` implements a new interface to the compilation process which consists of + composable compilation steps. (Many thanks to `axtimhaus`{.interpreted-text + role="user"}!:tada:) +- {pull}`36` fixes some issues. +- {pull}`37` updates the release notes. + +## 0.1.1 - 2022-02-08 + +- {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 + +- {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 + +- {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 + +- {pull}`18` prepares pytask-latex to be published on PyPI, adds versioneer and more. + +## 0.0.10 - 2021-01-16 + +- {pull}`16` fixes the scanner by keeping only scanned dependencies which exist. Convert + args to strings. + +## 0.0.9 - 2020-12-28 + +- {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 + +- {pull}`11` makes pytask-latex work with pytask v0.0.9. + +## 0.0.7 - 2020-10-14 + +- {pull}`10` fixes error that `outputdirectory` has to be relative to latex document due + to security problems. + +## 0.0.6 - 2020-10-14 + +- {pull}`9` fixes the last release and the `pytask_collect_task_teardown` call. + +## 0.0.5 - 2020-10-04 + +- {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 + +- {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 + +- {pull}`3` prepares pytask-latex for pytask v0.0.5 and releases v0.0.3. + +## 0.0.2 - 2020-07-22 + +- {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 + +- Releases v0.0.1. diff --git a/CHANGES.rst b/CHANGES.rst deleted file mode 100644 index 6699b50..0000000 --- a/CHANGES.rst +++ /dev/null @@ -1,120 +0,0 @@ -Changes -======= - -This is a record of all past pytask-latex releases and what went into them in reverse -chronological order. Releases follow `semantic versioning `_ and -all releases are available on `Anaconda.org -`_. - - -0.2.0 - 2022-xx-xx ------------------- - -- :pull:`34` deprecates the old api. - - -0.1.2 - 2022-03-26 ------------------- - -- :pull:`32` implements a new interface to the compilation process which consists of - composable compilation steps. (Many thanks to :user:`axtimhaus`!:tada:) -- :pull:`36` fixes some issues. -- :pull:`37` updates the release notes. - - -0.1.1 - 2022-02-08 ------------------- - -- :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 ------------------- - -- :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 -------------------- - -- :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 -------------------- - -- :pull:`18` prepares pytask-latex to be published on PyPI, adds versioneer and more. - - -0.0.10 - 2021-01-16 -------------------- - -- :pull:`16` fixes the scanner by keeping only scanned dependencies which exist. Convert - args to strings. - - -0.0.9 - 2020-12-28 ------------------- - -- :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 ------------------- - -- :pull:`11` makes pytask-latex work with pytask v0.0.9. - - -0.0.7 - 2020-10-14 ------------------- - -- :pull:`10` fixes error that ``outputdirectory`` has to be relative to latex document - due to security problems. - - -0.0.6 - 2020-10-14 ------------------- - -- :pull:`9` fixes the last release and the ``pytask_collect_task_teardown`` call. - - -0.0.5 - 2020-10-04 ------------------- - -- :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 ------------------- - -- :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 ------------------- - -- :pull:`3` prepares pytask-latex for pytask v0.0.5 and releases v0.0.3. - - -0.0.2 - 2020-07-22 ------------------- - -- :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 ------------------- - -- Releases v0.0.1. diff --git a/MANIFEST.in b/MANIFEST.in index 2897539..dd67fec 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,12 +1,9 @@ -prune .conda prune tests -exclude *.rst +exclude *.md exclude *.yml exclude *.yaml exclude tox.ini -include README.rst +include README.md include LICENSE -include versioneer.py -include src/pytask_latex/_version.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d72645 --- /dev/null +++ b/README.md @@ -0,0 +1,205 @@ +# pytask-latex + +[![PyPI](https://img.shields.io/pypi/v/pytask-latex?color=blue)](https://pypi.org/project/pytask-latex) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytask-latex)](https://pypi.org/project/pytask-latex) +[![image](https://img.shields.io/conda/vn/conda-forge/pytask-latex.svg)](https://anaconda.org/conda-forge/pytask-latex) +[![image](https://img.shields.io/conda/pn/conda-forge/pytask-latex.svg)](https://anaconda.org/conda-forge/pytask-latex) +[![PyPI - License](https://img.shields.io/pypi/l/pytask-latex)](https://pypi.org/project/pytask-latex) +[![image](https://img.shields.io/github/workflow/status/pytask-dev/pytask-latex/main/main)](https://github.com/pytask-dev/pytask-latex/actions?query=branch%3Amain) +[![image](https://codecov.io/gh/pytask-dev/pytask-latex/branch/main/graph/badge.svg)](https://codecov.io/gh/pytask-dev/pytask-latex) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pytask-dev/pytask-latex/main.svg)](https://results.pre-commit.ci/latest/github/pytask-dev/pytask-latex/main) +[![image](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +______________________________________________________________________ + +pytask-latex allows you to compile LaTeX documents with pytask + +It also uses +[latex-dependency-scanner](https://github.com/pytask-dev/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 + +pytask-latex is available on [PyPI](https://pypi.org/project/pytask-latex) and +[Anaconda.org](https://anaconda.org/conda-forge/pytask-latex). Install it with + +```console +$ pip install pytask-latex + +# or + +$ conda install -c conda-forge pytask-latex +``` + +You also need to have `latexmk` installed which determines the necessary number of +compilation steps ([here](https://tex.stackexchange.com/a/249243/194826) is an +explanation for what latexmk achieves). To test whether it is installed, type the +following on the command line + +```console +$ latexmk --help +``` + +If an error is shown instead of a help page, you can install `latexmk` with one of the +popular LaTeX distributions, like [TeX Live](https://www.tug.org/texlive/), +[MiKTeX](https://miktex.org/), [MacTeX](http://www.tug.org/mactex/) or others. + +## Usage + +Compiling your PDF can be as simple as writing the following task. + +```python +import pytask + + +@pytask.mark.latex(script="document.tex", document="document.pdf") +def task_compile_latex_document(): + pass +``` + +Use `@pytask.mark.latex` to indicate that this task compiles a LaTeX document. The +`script` and the `document` keywords provide absolute paths or paths relative to the +task module to the LaTeX file and the compiled document. + +### Dependencies and Products + +Dependencies and products can be added as with a normal pytask task using the +`@pytask.mark.depends_on` and `@pytask.mark.produces` decorators. which is explained in +this +[tutorial](https://pytask-dev.readthedocs.io/en/stable/tutorials/defining_dependencies_products.html). + +### Customizing the compilation + +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. + +```python +@pytask.mark.latex( + script="document.tex", + document="document.pdf", + compilation_steps="latexmk", +) +def task_compile_latex_document(): + ... +``` + +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. + +```python +from pytask_latex import compilation_steps as cs + + +@pytask.mark.latex( + script="document.tex", + document="document.pdf", + compilation_steps=cs.latexmk( + options=("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd") + ), +) +def task_compile_latex_document(): + ... +``` + +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`. + +```python +@pytask.mark.latex( + script="document.tex", + document="document.pdf", + compilation_steps=cs.latexmk( + options=("--dvi", "--interaction=nonstopmode", "--synctex=1", "--cd") + ), +) +def task_compile_latex_document(): + ... +``` + +`compilation_step.latexmk(options)` generates a compilation step which is a function +with the following signature: + +```python +from pathlib import Path +import subprocess + + +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. + +### Repeating tasks with different scripts or inputs + +You can compile multiple LaTeX documents as well as compiling a single `.tex` document +with different command line arguments. + +The following task compiles two latex documents. + +```python +for i in range(2): + + @pytask.mark.task + @pytask.mark.latex(script=f"document_{i}.tex", document=f"document_{i}.pdf") + def task_compile_latex_document(): + pass +``` + +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`. Pass a dictionary for possible +compilation steps and their options. + +```python +for format_ in ("pdf", "dvi"): + + @pytask.mark.task + @pytask.mark.latex( + script="document.tex", + document=f"document.{format_}", + compilation_steps=cs.latexmk( + (f"--{format_}", "--interaction=nonstopmode", "--synctex=1", "--cd") + ), + ) + def task_compile_latex_document(): + pass +``` + +## Configuration + +*`infer_latex_dependencies`* + +pytask-latex tries to scan your LaTeX document for included files with the help of +[latex-dependency-scanner](https://github.com/pytask-dev/latex-dependency-scanner) if +the following configuration value is true which is also the default. + +```toml +[tool.pytask.ini_options] +infer_latex_dependencies = true +``` + +Since the package is in its early development phase and LaTeX provides a myriad of ways +to include files as well as providing shortcuts for paths (e.g., `\graphicspath`), there +are definitely some rough edges left. File an issue here or in the other project in case +of a problem. + +## Changes + +Consult the [release notes](CHANGES.md) to find out about what is new. diff --git a/README.rst b/README.rst deleted file mode 100644 index 3a4dc40..0000000 --- a/README.rst +++ /dev/null @@ -1,303 +0,0 @@ -.. image:: https://img.shields.io/pypi/v/pytask-latex?color=blue - :alt: PyPI - :target: https://pypi.org/project/pytask-latex - -.. image:: https://img.shields.io/pypi/pyversions/pytask-latex - :alt: PyPI - Python Version - :target: https://pypi.org/project/pytask-latex - -.. image:: https://img.shields.io/conda/vn/conda-forge/pytask-latex.svg - :target: https://anaconda.org/conda-forge/pytask-latex - -.. image:: https://img.shields.io/conda/pn/conda-forge/pytask-latex.svg - :target: https://anaconda.org/conda-forge/pytask-latex - -.. image:: https://img.shields.io/pypi/l/pytask-latex - :alt: PyPI - License - :target: https://pypi.org/project/pytask-latex - -.. image:: https://img.shields.io/github/workflow/status/pytask-dev/pytask-latex/Continuous%20Integration%20Workflow/main - :target: https://github.com/pytask-dev/pytask-latex/actions?query=branch%3Amain - -.. image:: https://codecov.io/gh/pytask-dev/pytask-latex/branch/main/graph/badge.svg - :target: https://codecov.io/gh/pytask-dev/pytask-latex - -.. image:: https://results.pre-commit.ci/badge/github/pytask-dev/pytask-latex/main.svg - :target: https://results.pre-commit.ci/latest/github/pytask-dev/pytask-latex/main - :alt: pre-commit.ci status - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - ------- - -pytask-latex -============ - -pytask-latex allows you to compile LaTeX documents with pytask - -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 ------------- - -pytask-latex is available on `PyPI `_ and -`Anaconda.org `_. Install it with - -.. code-block:: console - - $ pip install pytask-latex - - # or - - $ conda install -c conda-forge pytask-latex - -You also need to have ``latexmk`` installed which determines the necessary number of -compilation steps (`here `_ is an -explanation for what latexmk achieves). To test whether it is installed, type the -following on the command line - -.. code-block:: console - - $ latexmk --help - -If an error is shown instead of a help page, you can install ``latexmk`` with one of the -popular LaTeX distributions, like `TeX Live `_, `MiKTeX -`_, `MacTeX `_ or others. - - -Usage ------ - -Compiling your PDF can be as simple as writing the following task. - -.. code-block:: python - - import pytask - - - @pytask.mark.latex - @pytask.mark.depends_on("document.tex") - @pytask.mark.produces("document.pdf") - def task_compile_latex_document(): - pass - -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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In general, you might not need to add dependencies other than the main LaTeX file -because pytask-latex tries to infer other dependencies automatically. See the -explanation for ``infer_latex_dependencies`` below. - -What happens if you need to add more dependencies to a task because they are not found -automatically? You could use a list, but ensure that the LaTeX document which should be -compiled is in the first position of the list. - -.. code-block:: python - - @pytask.mark.latex - @pytask.mark.depends_on(["document.tex", "image.png"]) - @pytask.mark.produces("document.pdf") - def task_compile_latex_document(): - pass - -If you use a dictionary to pass dependencies to the task, pytask-latex will, first, look -for a ``"source"`` key in the dictionary and, secondly, under the key ``0``. - -.. code-block:: python - - @pytask.mark.depends_on({"source": "document.tex", "image": "image.png"}) - def task_compile_document(): - pass - - - # or - - - @pytask.mark.depends_on({0: "document.tex", "image": "image.png"}) - def task_compile_document(): - pass - - - # or two decorators for the function, if you do not assign a name to the image. - - - @pytask.mark.depends_on({"source": "document.tex"}) - @pytask.mark.depends_on("image.png") - def task_compile_document(): - pass - -The same applies to the compiled document which is either in the first position, under -the key ``"document"`` or ``0``. - - -Customizing the compilation -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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(compilation_steps="latexmk") - def task_compile_latex_document(): - ... - -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:: python - - from pytask_latex import compilation_steps as cs - - - @pytask.mark.latex( - compilation_steps=cs.latexmk( - options=("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd") - ) - ) - def task_compile_latex_document(): - ... - -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( - compilation_steps=compilation_steps.latexmk( - options=("--dvi", "--interaction=nonstopmode", "--synctex=1", "--cd") - ) - ) - def task_compile_latex_document(): - ... - -``compilation_step.latexmk(options)`` generates a compilation step which is a function -with the following signature: - -.. code-block:: python - - from pathlib import Path - import subprocess - - - 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. - -The following task compiles two latex documents. - -.. code-block:: python - - @pytask.mark.latex - @pytask.mark.parametrize( - "depends_on, produces", - [("document_1.tex", "document_1.pdf"), ("document_2.tex", "document_2.pdf")], - ) - def task_compile_latex_document(): - pass - - -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``. Pass a dictionary for -possible compilation steps and their options. - -.. code-block:: python - - @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 - - -Configuration -------------- - -latex_source_key - If you want to change the name of the key which identifies the source file, change - the following default configuration in your pytask configuration file. - - .. code-block:: ini - - latex_source_key = source - -latex_document_key - If you want to change the name of the key which identifies the compiled document, - change the following default configuration in your pytask configuration file. - - .. code-block:: ini - - latex_source_key = source - -infer_latex_dependencies - pytask-latex tries to scan your LaTeX document for included files with the help of - `latex-dependency-scanner `_ - if the following configuration value is true which is also the default. - - .. code-block:: ini - - infer_latex_dependencies = true - - Since the package is in its early development phase and LaTeX provides a myriad of - ways to include files as well as providing shortcuts for paths (e.g., - ``\graphicspath``), there are definitely some rough edges left. File an issue here - or in the other project in case of a problem. - - -Changes -------- - -Consult the `release notes `_ to find out about what is new. diff --git a/codecov.yml b/codecov.yml index e6cec4d..b37c2a8 100644 --- a/codecov.yml +++ b/codecov.yml @@ -21,6 +21,4 @@ coverage: ignore: - ".tox/**/*" - - "setup.py" - - "versioneer.py" - "src/pytask_latex/_version.py" diff --git a/environment.yml b/environment.yml index 9734833..8d14852 100644 --- a/environment.yml +++ b/environment.yml @@ -11,9 +11,10 @@ dependencies: - toml # Package dependencies - - pytask >= 0.1.0 - - pytask-parallel >= 0.1.0 - - latex-dependency-scanner + - pytask >= 0.2 + - pytask-parallel >= 0.1 + - latex-dependency-scanner >=0.1.1 + - pybaum >=0.1.1 # Misc - black diff --git a/pyproject.toml b/pyproject.toml index 44cd0b7..b2714c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [build-system] requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.0"] +build-backend = "setuptools.build_meta" [tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg index 42a0ae6..1995fc9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,8 @@ [metadata] name = pytask_latex description = Compile LaTeX documents with pytask. -long_description = file: README.rst -long_description_content_type = text/x-rst +long_description = file: README.md +long_description_content_type = text/markdown url = https://github.com/pytask-dev/pytask-latex author = Tobias Raabe author_email = raabe@posteo.de @@ -20,7 +20,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 project_urls = - Changelog = https://github.com/pytask-dev/pytask-latex/blob/main/CHANGES.rst + Changelog = https://github.com/pytask-dev/pytask-latex/blob/main/CHANGES.md Documentation = https://github.com/pytask-dev/pytask-latex Github = https://github.com/pytask-dev/pytask-latex Tracker = https://github.com/pytask-dev/pytask-latex/issues @@ -30,7 +30,8 @@ packages = find: install_requires = click latex-dependency-scanner>=0.1.1 - pytask>=0.1.7 + pybaum>=0.1.1 + pytask>=0.2 python_requires = >=3.7 include_package_data = True package_dir = =src diff --git a/setup.py b/setup.py deleted file mode 100644 index c21a9ee..0000000 --- a/setup.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -from setuptools import setup - - -if __name__ == "__main__": - setup() diff --git a/src/pytask_latex/__init__.py b/src/pytask_latex/__init__.py index f493641..dcc8e73 100644 --- a/src/pytask_latex/__init__.py +++ b/src/pytask_latex/__init__.py @@ -2,7 +2,7 @@ try: from ._version import version as __version__ -except ImportError: +except ImportError: # pragma: no cover # broken installation, we don't even try unknown only works because we do poor mans # version compare __version__ = "unknown" diff --git a/src/pytask_latex/collect.py b/src/pytask_latex/collect.py index c30fc2c..66b561d 100644 --- a/src/pytask_latex/collect.py +++ b/src/pytask_latex/collect.py @@ -1,30 +1,75 @@ """Collect tasks.""" from __future__ import annotations -import copy import functools +from pathlib import Path from subprocess import CalledProcessError +from types import FunctionType from typing import Any from typing import Callable 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.nodes import _collect_nodes -from _pytask.nodes import FilePathNode -from _pytask.parametrize import _copy_func +from pybaum.tree_util import tree_map +from pytask import depends_on +from pytask import FilePathNode +from pytask import has_mark +from pytask import hookimpl +from pytask import Mark +from pytask import MetaNode +from pytask import NodeNotCollectedError +from pytask import parse_nodes +from pytask import produces +from pytask import remove_marks +from pytask import Session +from pytask import Task from pytask_latex import compilation_steps as cs from pytask_latex.utils import to_list +_ERROR_MSG = """The old syntax for @pytask.mark.latex was suddenly deprecated starting \ +with pytask-latex v0.2 to provide a better user experience. Thank you for your \ +understanding! + +It is recommended to upgrade to the new syntax, so you enjoy all the benefits of v0.2 of +pytask and a better interface for pytask-latex. + +You can find a manual here: \ +https://github.com/pytask-dev/pytask-latex/blob/v0.2.0/README.md + +Upgrading can be as easy as rewriting your current task from + + @pytask.mark.latex("--some-option") + @pytask.mark.depends_on({"source": "script.tex") + @pytask.mark.produces("document.pdf") + def task_latex(): + ... + +to + + @pytask.mark.latex( + script="script.tex", + document="document.pdf", + options="--some-options" + ) + def task_latex(): + ... + +You can also fix the version of pytask and pytask-latex to <0.2, so you do not have to \ +to upgrade. At the same time, you will not enjoy the improvements released with \ +version v0.2 of pytask and pytask-latex. + +""" + + def latex( *, + script: str | Path = None, + document: str | Path = None, compilation_steps: str | Callable[..., Any] | Sequence[str | Callable[..., Any]] = None, -): +) -> tuple[str | Path | None, list[Callable[..., Any]]]: """Specify command line options for latexmk. Parameters @@ -35,26 +80,13 @@ def latex( Compilation steps to compile the document. """ - compilation_steps = ["latexmk"] if compilation_steps is None else compilation_steps - - 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 + if script is None or document is None: + raise RuntimeError(_ERROR_MSG) + return script, document, compilation_steps def compile_latex_document(compilation_steps, path_to_tex, path_to_document): """Replaces the dummy function provided by the user.""" - for step in compilation_steps: try: step(path_to_tex=path_to_tex, path_to_document=path_to_document) @@ -63,50 +95,89 @@ def compile_latex_document(compilation_steps, path_to_tex, path_to_document): @hookimpl -def pytask_collect_task_teardown(session, task): +def pytask_collect_task(session, path, name, obj): """Perform some checks.""" - if get_specific_markers_from_task(task, "latex"): - source = _get_node_from_dictionary( - task.depends_on, session.config["latex_source_key"] - ) - if not (isinstance(source, FilePathNode) and source.value.suffix == ".tex"): + __tracebackhide__ = True + + if ( + (name.startswith("task_") or has_mark(obj, "task")) + and callable(obj) + and has_mark(obj, "latex") + ): + obj, marks = remove_marks(obj, "latex") + + if len(marks) > 1: raise ValueError( - "The first or sole dependency of a LaTeX task must be the document " - "which will be compiled and has a .tex extension." + f"Task {name!r} has multiple @pytask.mark.latex marks, but only one is " + "allowed." ) + latex_mark = marks[0] + script, document, compilation_steps = latex(**latex_mark.kwargs) + + parsed_compilation_steps = _parse_compilation_steps(compilation_steps) + + obj.pytask_meta.markers.append(latex_mark) + + dependencies = parse_nodes(session, path, name, obj, depends_on) + products = parse_nodes(session, path, name, obj, produces) - document = _get_node_from_dictionary( - task.produces, session.config["latex_document_key"] + markers = obj.pytask_meta.markers if hasattr(obj, "pytask_meta") else [] + kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {} + + task = Task( + base_name=name, + path=path, + function=_copy_func(compile_latex_document), + depends_on=dependencies, + produces=products, + markers=markers, + kwargs=kwargs, + ) + + script_node = session.hook.pytask_collect_node( + session=session, path=path, node=script + ) + document_node = session.hook.pytask_collect_node( + session=session, path=path, node=document ) + if not ( - isinstance(document, FilePathNode) - and document.value.suffix in [".pdf", ".ps", ".dvi"] + isinstance(script_node, FilePathNode) and script_node.value.suffix == ".tex" ): raise ValueError( - "The first or sole product of a LaTeX task must point to a .pdf, .ps " - "or .dvi file which is the compiled document." + "The 'script' keyword of the @pytask.mark.latex decorator must point " + "to LaTeX file with the .tex suffix." ) - task_function = _copy_func(compile_latex_document) - task_function.pytaskmark = copy.deepcopy(task.function.pytaskmark) + if not ( + isinstance(document_node, FilePathNode) + and document_node.value.suffix in [".pdf", ".ps", ".dvi"] + ): + raise ValueError( + "The 'document' keyword of the @pytask.mark.latex decorator must point " + "to a .pdf, .ps or .dvi file." + ) - merged_mark = _merge_all_markers(task) - 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 - ) + if isinstance(task.depends_on, dict): + task.depends_on["__script"] = script_node + else: + task.depends_on = {0: task.depends_on, "__script": script_node} + if isinstance(task.produces, dict): + task.produces["__document"] = document_node + else: + task.produces = {0: task.produces, "__document": document_node} - task.function = task_function + task.function = functools.partial( + task.function, + compilation_steps=parsed_compilation_steps, + path_to_tex=script_node.path, + path_to_document=document_node.path, + ) if session.config["infer_latex_dependencies"]: task = _add_latex_dependencies_retroactively(task, session) - -def _get_node_from_dictionary(obj, key, fallback=0): - if isinstance(obj, dict): - obj = obj.get(key) or obj.get(fallback) - return obj + return task def _add_latex_dependencies_retroactively(task, session): @@ -125,16 +196,12 @@ def _add_latex_dependencies_retroactively(task, session): ---------- task The LaTeX task. - session : _pytask.session.Session + session : pytask.Session The session. """ - source = _get_node_from_dictionary( - task.depends_on, session.config["latex_source_key"] - ) - # Scan the LaTeX document for included files. - latex_dependencies = set(lds.scan(source.path)) + latex_dependencies = set(lds.scan(task.depends_on["__script"].path)) # Remove duplicated dependencies which have already been added by the user and those # which do not exist. @@ -143,17 +210,13 @@ def _add_latex_dependencies_retroactively(task, session): } new_deps = latex_dependencies - existing_paths new_existing_deps = {i for i in new_deps if i.exists()} - - # Put scanned dependencies in a dictionary with incrementing keys. - used_integer_keys = [i for i in task.depends_on if isinstance(i, int)] - max_int = max(used_integer_keys) if used_integer_keys else 0 - new_existing_deps = dict(enumerate(new_existing_deps, max_int + 1)) + new_numbered_deps = dict(enumerate(new_existing_deps)) # Collect new dependencies and add them to the task. - collected_dependencies = _collect_nodes( - session, task.path, task.name, new_existing_deps + collected_dependencies = tree_map( + lambda x: _collect_node(session, task.path, task.name, x), new_numbered_deps ) - task.depends_on = {**task.depends_on, **collected_dependencies} + task.depends_on["__scanned_dependencies"] = collected_dependencies # Mark the task as being delayed to avoid conflicts with unmatched dependencies. task.markers.append(Mark("try_last", (), {})) @@ -161,22 +224,87 @@ def _add_latex_dependencies_retroactively(task, session): return task -def _merge_all_markers(task): - """Combine all information from markers for the compile latex function.""" - latex_marks = get_specific_markers_from_task(task, "latex") - mark = latex_marks[0] - for mark_ in latex_marks[1:]: - mark = mark.combined_with(mark_) - return mark +def _copy_func(func: FunctionType) -> FunctionType: + """Create a copy of a function. + + Based on https://stackoverflow.com/a/13503277/7523785. + + Example + ------- + >>> def _func(): pass + >>> copied_func = _copy_func(_func) + >>> _func is copied_func + False + + """ + new_func = FunctionType( + func.__code__, + func.__globals__, + name=func.__name__, + argdefs=func.__defaults__, + closure=func.__closure__, + ) + new_func = functools.update_wrapper(new_func, func) + new_func.__kwdefaults__ = func.__kwdefaults__ + return new_func + + +def _collect_node( + session: Session, path: Path, name: str, node: str | Path +) -> dict[str, MetaNode]: + """Collect nodes for a task. + + Parameters + ---------- + session : pytask.Session + The session. + path : Path + The path to the task whose nodes are collected. + name : str + The name of the task. + nodes : Dict[str, Union[str, Path]] + A dictionary of nodes parsed from the ``depends_on`` or ``produces`` markers. + + Returns + ------- + Dict[str, MetaNode] + A dictionary of node names and their paths. + + Raises + ------ + NodeNotCollectedError + If the node could not collected. + + """ + collected_node = session.hook.pytask_collect_node( + session=session, path=path, node=node + ) + if collected_node is None: + raise NodeNotCollectedError( + f"{node!r} cannot be parsed as a dependency or product for task " + f"{name!r} in {path!r}." + ) + + return collected_node + +def _parse_compilation_steps(compilation_steps): + """Parse compilation steps.""" + __tracebackhide__ = True -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 - compiled_document = _get_node_from_dictionary( - task.produces, session.config["latex_document_key"] - ).value + compilation_steps = ["latexmk"] if compilation_steps is None else compilation_steps + + parsed_compilation_steps = [] + for step in to_list(compilation_steps): + if isinstance(step, str): + try: + parsed_step = getattr(cs, step) + except AttributeError: + raise ValueError(f"Compilation step {step!r} is unknown.") + parsed_compilation_steps.append(parsed_step()) + elif callable(step): + parsed_compilation_steps.append(step) + else: + raise ValueError(f"Compilation step {step!r} is not a valid step.") - return {"path_to_tex": latex_document, "path_to_document": compiled_document} + return parsed_compilation_steps diff --git a/src/pytask_latex/config.py b/src/pytask_latex/config.py index 3414499..c3e964f 100644 --- a/src/pytask_latex/config.py +++ b/src/pytask_latex/config.py @@ -1,22 +1,62 @@ """Configure pytask.""" from __future__ import annotations -from _pytask.config import hookimpl -from _pytask.shared import convert_truthy_or_falsy_to_bool -from _pytask.shared import get_first_non_none_value +from typing import Any +from typing import Callable + +from pytask import hookimpl @hookimpl def pytask_parse_config(config, config_from_file): """Register the latex marker in the configuration.""" config["markers"]["latex"] = "Tasks which compile LaTeX documents." - config["latex_source_key"] = config_from_file.get("latex_source_key", "source") - config["latex_document_key"] = config_from_file.get( - "latex_document_key", "document" - ) - config["infer_latex_dependencies"] = get_first_non_none_value( + config["infer_latex_dependencies"] = _get_first_non_none_value( config_from_file, key="infer_latex_dependencies", - callback=convert_truthy_or_falsy_to_bool, + callback=_convert_truthy_or_falsy_to_bool, default=True, ) + + +def _convert_truthy_or_falsy_to_bool(x: bool | str | None) -> bool: + """Convert truthy or falsy value in .ini to Python boolean.""" + if x in [True, "True", "true", "1"]: + out = True + elif x in [False, "False", "false", "0"]: + out = False + elif x in [None, "None", "none"]: + out = None + else: + raise ValueError( + f"Input {x!r} is neither truthy (True, true, 1) or falsy (False, false, 0)." + ) + return out + + +def _get_first_non_none_value( + *configs: dict[str, Any], + key: str, + default: Any | None = None, + callback: Callable[..., Any] | None = None, +) -> Any: + """Get the first non-None value for a key from a list of dictionaries. + + This function allows to prioritize information from many configurations by changing + the order of the inputs while also providing a default. + + Examples + -------- + >>> _get_first_non_none_value({"a": None}, {"a": 1}, key="a") + 1 + >>> _get_first_non_none_value({"a": None}, {"a": None}, key="a", default="default") + 'default' + >>> _get_first_non_none_value({}, {}, key="a", default="default") + 'default' + >>> _get_first_non_none_value({"a": None}, {"a": "b"}, key="a") + 'b' + + """ + callback = (lambda x: x) if callback is None else callback # noqa: E731 + processed_values = (callback(config.get(key)) for config in configs) + return next((value for value in processed_values if value is not None), default) diff --git a/src/pytask_latex/execute.py b/src/pytask_latex/execute.py index 87ec03e..e378532 100644 --- a/src/pytask_latex/execute.py +++ b/src/pytask_latex/execute.py @@ -3,14 +3,14 @@ import shutil -from _pytask.config import hookimpl -from _pytask.mark_utils import get_specific_markers_from_task +from pytask import has_mark +from pytask import hookimpl @hookimpl def pytask_execute_task_setup(task): """Check that latexmk is found on the PATH if a LaTeX task should be executed.""" - if get_specific_markers_from_task(task, "latex"): + if has_mark(task, "latex"): if shutil.which("latexmk") is None: raise RuntimeError( "latexmk is needed to compile LaTeX documents, but it is not found on " diff --git a/src/pytask_latex/parametrize.py b/src/pytask_latex/parametrize.py index aa4b492..5041761 100644 --- a/src/pytask_latex/parametrize.py +++ b/src/pytask_latex/parametrize.py @@ -1,8 +1,8 @@ """Parametrize tasks.""" from __future__ import annotations -from _pytask.config import hookimpl -from _pytask.mark import MARK_GEN as mark # noqa: N811 +import pytask +from pytask import hookimpl @hookimpl @@ -10,7 +10,4 @@ def pytask_parametrize_kwarg_to_marker(obj, kwargs): """Register kwargs as latex marker.""" if callable(obj): if "latex" in kwargs: - if isinstance(kwargs["latex"], dict): - mark.latex(**kwargs.pop("latex"))(obj) - else: - mark.latex(kwargs.pop("latex"))(obj) + pytask.mark.latex(**kwargs.pop("latex"))(obj) diff --git a/src/pytask_latex/plugin.py b/src/pytask_latex/plugin.py index 3325a1f..ca67c7a 100644 --- a/src/pytask_latex/plugin.py +++ b/src/pytask_latex/plugin.py @@ -1,7 +1,7 @@ """Entry-point for the plugin.""" from __future__ import annotations -from _pytask.config import hookimpl +from pytask import hookimpl from pytask_latex import collect from pytask_latex import config from pytask_latex import execute diff --git a/tests/conftest.py b/tests/conftest.py index 1fdb7db..1ae27ba 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,7 @@ ) skip_on_github_actions_with_win = pytest.mark.skipif( - os.environ.get("GITHUB_ACTIONS", "false") == "true" and sys.platform == "win32", + condition=os.environ.get("CI") == "true" and sys.platform == "win32", reason="TinyTeX does not work on Windows.", ) diff --git a/tests/test_collect.py b/tests/test_collect.py index d94c6d1..efc3ddd 100644 --- a/tests/test_collect.py +++ b/tests/test_collect.py @@ -3,101 +3,41 @@ from contextlib import ExitStack as does_not_raise # noqa: N813 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 pytask_collect_task_teardown - - -class DummyClass: - pass - - -def task_dummy(): - pass +from pytask_latex.collect import latex @pytest.mark.unit @pytest.mark.parametrize( - "marks, expected", + "kwargs, expectation, expected", [ + ({}, pytest.raises(RuntimeError, match="The old syntax"), None), ( - [Mark("latex", ("--a",), {}), Mark("latex", ("--b",), {})], - Mark("latex", ("--a", "--b"), {}), + {"document": "document.pdf"}, + pytest.raises(RuntimeError, match="The old syntax"), + None, ), ( - [Mark("latex", ("--a",), {}), Mark("latex", (), {"latex": "--b"})], - Mark("latex", ("--a",), {"latex": "--b"}), + {"script": "script.tex"}, + pytest.raises(RuntimeError, match="The old syntax"), + None, + ), + ( + {"script": "script.tex", "document": "document.pdf"}, + does_not_raise(), + ("script.tex", "document.pdf", None), + ), + ( + { + "script": "script.tex", + "document": "document.pdf", + "compilation_steps": "latexmk", + }, + does_not_raise(), + ("script.tex", "document.pdf", "latexmk"), ), ], ) -def test_merge_all_markers(marks, expected): - task = DummyClass() - task.markers = marks - out = _merge_all_markers(task) - assert out == expected - - -@pytest.mark.unit -@pytest.mark.parametrize( - "depends_on, produces, expectation", - [ - (["document.tex"], ["document.pdf"], does_not_raise()), - (["document.tex"], ["document.ps"], does_not_raise()), - (["document.tex"], ["document.dvi"], does_not_raise()), - (["document.txt"], ["document.pdf"], pytest.raises(ValueError)), - (["document.txt"], ["document.ps"], pytest.raises(ValueError)), - (["document.txt"], ["document.dvi"], pytest.raises(ValueError)), - (["document.tex"], ["document.txt"], pytest.raises(ValueError)), - (["document.txt", "document.tex"], ["document.pdf"], pytest.raises(ValueError)), - (["document.tex"], ["document.out", "document.pdf"], pytest.raises(ValueError)), - ], -) -@pytest.mark.parametrize("latex_source_key", ["source", "script", "main"]) -@pytest.mark.parametrize("latex_document_key", ["document", "compiled_doc"]) -@pytest.mark.parametrize("infer_latex_dependencies", [True, False]) -def test_pytask_collect_task_teardown( - tmp_path, - depends_on, - produces, - expectation, - latex_source_key, - latex_document_key, - infer_latex_dependencies, -): - if infer_latex_dependencies: - tmp_path.joinpath(depends_on[0]).touch() - - session = DummyClass() - session.config = { - "latex_source_key": latex_source_key, - "latex_document_key": latex_document_key, - "infer_latex_dependencies": infer_latex_dependencies, - } - - task = DummyClass() - task.path = tmp_path / "task_dummy.py" - task.name = tmp_path.as_posix() + "task_dummy.py::task_dummy" - task.depends_on = { - i: FilePathNode.from_path(tmp_path / n) for i, n in enumerate(depends_on) - } - task.produces = { - i: FilePathNode.from_path(tmp_path / n) for i, n in enumerate(produces) - } - task.markers = [Mark("latex", (), {})] - task.function = task_dummy - task.function.pytaskmark = task.markers - +def test_latex(kwargs, expectation, expected): with expectation: - pytask_collect_task_teardown(session, task) - - -@pytest.mark.unit -@pytest.mark.parametrize( - "obj, key, expected", - [(1, "asds", 1), (1, None, 1), ({"a": 1}, "a", 1), ({0: 1}, "a", 1)], -) -def test_get_node_from_dictionary(obj, key, expected): - result = _get_node_from_dictionary(obj, key) - assert result == expected + result = latex(**kwargs) + assert result == expected diff --git a/tests/test_config.py b/tests/test_config.py index 40976b4..3ed5b90 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,11 +1,37 @@ from __future__ import annotations +from contextlib import ExitStack as does_not_raise # noqa: N813 + import pytest from pytask import main +from pytask_latex.config import _convert_truthy_or_falsy_to_bool @pytest.mark.end_to_end def test_marker_is_configured(tmp_path): session = main({"paths": tmp_path}) - assert "latex" in session.config["markers"] + + +@pytest.mark.unit +@pytest.mark.parametrize( + "value, expectation, expected", + [ + (True, does_not_raise(), True), + ("True", does_not_raise(), True), + ("true", does_not_raise(), True), + ("1", does_not_raise(), True), + (False, does_not_raise(), False), + ("False", does_not_raise(), False), + ("false", does_not_raise(), False), + ("0", does_not_raise(), False), + (None, does_not_raise(), None), + ("None", does_not_raise(), None), + ("none", does_not_raise(), None), + ("asklds", pytest.raises(ValueError, match="Input"), None), + ], +) +def test_convert_truthy_or_falsy_to_bool(value, expectation, expected): + with expectation: + result = _convert_truthy_or_falsy_to_bool(value) + assert result == expected diff --git a/tests/test_execute.py b/tests/test_execute.py index 5a61973..5321196 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -1,76 +1,47 @@ from __future__ import annotations import textwrap -from contextlib import ExitStack as does_not_raise # noqa: N813 +from pathlib import Path import pytest -from _pytask.mark import Mark from conftest import needs_latexmk from conftest import skip_on_github_actions_with_win from conftest import TEST_RESOURCES from pytask import cli +from pytask import ExitCode from pytask import main +from pytask import Mark +from pytask import Task from pytask_latex.execute import pytask_execute_task_setup -class DummyTask: - pass - - @pytest.mark.unit -@pytest.mark.parametrize( - "found_latexmk, expectation", - [ - (True, does_not_raise()), - (None, pytest.raises(RuntimeError)), - ], -) -def test_pytask_execute_task_setup(monkeypatch, found_latexmk, expectation): +def test_pytask_execute_task_setup(monkeypatch): """Make sure that the task setup raises errors.""" # Act like latexmk is installed since we do not test this. monkeypatch.setattr( - "pytask_latex.execute.shutil.which", lambda x: found_latexmk # noqa: U100 + "pytask_latex.execute.shutil.which", lambda x: None # noqa: U100 ) - - task = DummyTask() - task.markers = [Mark("latex", (), {})] - - with expectation: + task = Task( + base_name="example", path=Path(), function=None, markers=[Mark("latex", (), {})] + ) + with pytest.raises(RuntimeError, match="latexmk is needed"): pytask_execute_task_setup(task) @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end -@pytest.mark.parametrize( - "depends_on", - [ - "'document.tex'", - {"source": "document.tex"}, - {0: "document.tex"}, - {"script": "document.tex"}, - ], -) -@pytest.mark.parametrize( - "produces", - [ - "'document.pdf'", - {"document": "document.pdf"}, - {0: "document.pdf"}, - {"compiled_doc": "document.pdf"}, - ], -) -def test_compile_latex_document(runner, tmp_path, depends_on, produces): +def test_compile_latex_document_raise_error_old_api(runner, tmp_path): """Test simple compilation.""" - task_source = f""" + task_source = """ import pytask @pytask.mark.latex - @pytask.mark.depends_on({depends_on}) - @pytask.mark.produces({produces}) + @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)) @@ -82,21 +53,36 @@ def task_compile_document(): """ tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) - config = "[pytask]\n" - if ( - isinstance(depends_on, dict) - and "source" not in depends_on - and 0 not in depends_on - ): - config += "latex_source_key = script\n" - if isinstance(produces, dict) and "document" not in produces and 0 not in produces: - config += "latex_document_key = compiled_doc\n" - tmp_path.joinpath("pytask.ini").write_text(config) - result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == 0 - assert tmp_path.joinpath("document.pdf").exists() + assert result.exit_code == ExitCode.COLLECTION_FAILED + assert "The old syntax for @pytask.mark.latex" in result.output + + +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_compile_latex_document(runner, tmp_path): + """Test simple compilation.""" + task_source = """ + import pytask + + @pytask.mark.latex(script="document.tex", document="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 was tired of my lady + \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 == ExitCode.OK @needs_latexmk @@ -107,9 +93,10 @@ def test_compile_latex_document_w_relative(runner, tmp_path): task_source = f""" import pytask - @pytask.mark.latex - @pytask.mark.depends_on("document.tex") - @pytask.mark.produces("{tmp_path.joinpath("bld", "document.pdf").as_posix()}") + @pytask.mark.latex( + script="document.tex", + document="{tmp_path.joinpath("bld", "document.pdf").as_posix()}" + ) def task_compile_document(): pass @@ -127,9 +114,7 @@ def task_compile_document(): tmp_path.joinpath("src", "document.tex").write_text(textwrap.dedent(latex_source)) result = runner.invoke(cli, [tmp_path.as_posix()]) - - assert result.exit_code == 0 - assert tmp_path.joinpath("bld", "document.pdf").exists() + assert result.exit_code == ExitCode.OK @needs_latexmk @@ -140,9 +125,7 @@ def test_compile_latex_document_to_different_name(runner, tmp_path): task_source = """ import pytask - @pytask.mark.latex - @pytask.mark.depends_on("in.tex") - @pytask.mark.produces("out.pdf") + @pytask.mark.latex(script="in.tex", document="out.pdf") def task_compile_document(): pass @@ -158,25 +141,21 @@ def task_compile_document(): tmp_path.joinpath("in.tex").write_text(textwrap.dedent(latex_source)) result = runner.invoke(cli, [tmp_path.as_posix()]) - - assert result.exit_code == 0 - assert tmp_path.joinpath("out.pdf").exists() + assert result.exit_code == ExitCode.OK @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end -def test_compile_w_bibliography(tmp_path): +def test_compile_w_bibliography(runner, tmp_path): """Compile a LaTeX document with bibliography.""" task_source = """ import pytask - @pytask.mark.latex - @pytask.mark.depends_on(["in_w_bib.tex", "references.bib"]) - @pytask.mark.produces("out_w_bib.pdf") + @pytask.mark.latex(script="in_w_bib.tex", document="out_w_bib.pdf") + @pytask.mark.depends_on("references.bib") def task_compile_document(): pass - """ tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) @@ -201,10 +180,8 @@ def task_compile_document(): """ tmp_path.joinpath("references.bib").write_text(textwrap.dedent(bib_source)) - session = main({"paths": tmp_path}) - - assert session.exit_code == 0 - assert tmp_path.joinpath("out_w_bib.pdf").exists() + session = runner.invoke(cli, [tmp_path.as_posix()]) + assert session.exit_code == ExitCode.OK @needs_latexmk @@ -214,12 +191,9 @@ def test_raise_error_if_latexmk_is_not_found(tmp_path, monkeypatch): task_source = """ import pytask - @pytask.mark.latex - @pytask.mark.depends_on("document.tex") - @pytask.mark.produces("document.pdf") + @pytask.mark.latex(script="document.tex", document="document.pdf") def task_compile_document(): pass - """ tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) @@ -239,7 +213,7 @@ def task_compile_document(): session = main({"paths": tmp_path}) - assert session.exit_code == 1 + assert session.exit_code == ExitCode.FAILED assert isinstance(session.execution_reports[0].exc_info[1], RuntimeError) @@ -252,12 +226,12 @@ def test_compile_latex_document_w_xelatex(runner, tmp_path): from pytask_latex import compilation_steps @pytask.mark.latex( + script="document.tex", + document="document.pdf", 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 @@ -274,7 +248,7 @@ def task_compile_document(): result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == 0 + assert result.exit_code == ExitCode.OK assert tmp_path.joinpath("document.pdf").exists() @@ -285,12 +259,10 @@ def test_compile_latex_document_w_two_dependencies(runner, tmp_path): task_source = """ import pytask - @pytask.mark.latex - @pytask.mark.depends_on(["document.tex", "in.txt"]) - @pytask.mark.produces("document.pdf") + @pytask.mark.latex(script="document.tex", document="document.pdf") + @pytask.mark.depends_on("in.txt") def task_compile_document(): pass - """ tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) @@ -301,28 +273,24 @@ def task_compile_document(): \end{document} """ tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) - tmp_path.joinpath("in.txt").touch() result = runner.invoke(cli, [tmp_path.as_posix()]) - - assert result.exit_code == 0 + assert result.exit_code == ExitCode.OK assert tmp_path.joinpath("document.pdf").exists() @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end -def test_fail_because_latex_document_is_not_first_dependency(tmp_path): +def test_fail_because_script_is_not_latex(tmp_path): task_source = """ import pytask - @pytask.mark.latex - @pytask.mark.depends_on(["in.txt", "document.tex"]) - @pytask.mark.produces("document.pdf") + @pytask.mark.latex(script="document.text", document="document.pdf") + @pytask.mark.depends_on("in.txt") def task_compile_document(): pass - """ tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) @@ -333,12 +301,10 @@ def task_compile_document(): \end{document} """ tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) - tmp_path.joinpath("in.txt").touch() session = main({"paths": tmp_path}) - - assert session.exit_code == 3 + assert session.exit_code == ExitCode.COLLECTION_FAILED assert isinstance(session.collection_reports[0].exc_info[1], ValueError) @@ -359,12 +325,10 @@ def test_compile_document_to_out_if_document_has_relative_resources(tmp_path): task_source = """ import pytask - @pytask.mark.latex - @pytask.mark.depends_on(["document.tex", "resources/content.tex"]) - @pytask.mark.produces("out/document.pdf") + @pytask.mark.latex(script="document.tex", document="out/document.pdf") + @pytask.mark.depends_on("resources/content.tex") def task_compile_document(): pass - """ tmp_path.joinpath("sub", "task_dummy.py").write_text(textwrap.dedent(task_source)) @@ -383,8 +347,7 @@ def task_compile_document(): tmp_path.joinpath("sub", "resources", "content.tex").write_text(resources) session = main({"paths": tmp_path}) - - assert session.exit_code == 0 + assert session.exit_code == ExitCode.OK assert len(session.tasks) == 1 @@ -399,9 +362,11 @@ def test_compile_document_w_wrong_flag(tmp_path): 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") + @pytask.mark.latex( + script="document.tex", + document="out/document.pdf", + compilation_steps=compilation_steps.latexmk("--wrong-flag"), + ) def task_compile_document(): pass @@ -417,8 +382,7 @@ def task_compile_document(): tmp_path.joinpath("sub", "document.tex").write_text(textwrap.dedent(latex_source)) session = main({"paths": tmp_path}) - - assert session.exit_code == 1 + assert session.exit_code == ExitCode.FAILED assert len(session.tasks) == 1 assert isinstance(session.execution_reports[0].exc_info[1], RuntimeError) @@ -437,12 +401,9 @@ def task_create_image(): "{tmp_path.joinpath("image.png").as_posix()}" ) - @pytask.mark.latex - @pytask.mark.depends_on("document.tex") - @pytask.mark.produces("document.pdf") + @pytask.mark.latex(script="document.tex", document="document.pdf") def task_compile_document(): pass - """ tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) @@ -456,5 +417,170 @@ def task_compile_document(): tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) result = runner.invoke(cli, [tmp_path.as_posix()]) + assert result.exit_code == ExitCode.OK + + +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_compile_latex_document_w_multiple_marks(runner, tmp_path): + """Test simple compilation.""" + task_source = """ + import pytask + + @pytask.mark.latex(script="document.text") + @pytask.mark.latex(script="document.tex", document="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 was tired of my lady + \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 == ExitCode.COLLECTION_FAILED + assert "has multiple @pytask.mark.latex marks" in result.output + - assert result.exit_code == 0 +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_compile_latex_document_with_wrong_extension(runner, tmp_path): + """Test simple compilation.""" + task_source = """ + import pytask + + @pytask.mark.latex(script="document.tex", document="document.file") + 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 was tired of my lady + \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 == ExitCode.COLLECTION_FAILED + assert "The 'document' keyword of the" in result.output + + +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_compile_w_bibliography_and_keep_bbl(runner, tmp_path): + """Compile a LaTeX document with bibliography.""" + task_source = """ + import pytask + + @pytask.mark.produces("out_w_bib.bbl") + @pytask.mark.latex( + script="in_w_bib.tex", + document="out_w_bib.pdf", + ) + @pytask.mark.depends_on("references.bib") + def task_compile_document(): + pass + """ + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) + + latex_source = r""" + \documentclass{report} + \usepackage{natbib} + \begin{document} + \cite{pytask} + \bibliographystyle{plain} + \bibliography{references} + \end{document} + """ + tmp_path.joinpath("in_w_bib.tex").write_text(textwrap.dedent(latex_source)) + + bib_source = r""" + @Article{pytask, + author = {Tobias Raabe}, + title = {pytask}, + journal = {Unpublished}, + year = {2020}, + } + """ + tmp_path.joinpath("references.bib").write_text(textwrap.dedent(bib_source)) + + session = runner.invoke(cli, [tmp_path.as_posix()]) + assert session.exit_code == ExitCode.OK + + +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +@pytest.mark.parametrize( + "step, message", + [ + ("'unknown'", "Compilation step 'unknown' is unknown."), + (1, "Compilation step 1 is not a valid step."), + ], +) +def test_compile_latex_document_w_unknown_compilation_step( + runner, tmp_path, step, message +): + """Test simple compilation.""" + task_source = f""" + import pytask + + @pytask.mark.latex( + script="document.tex", + document="document.pdf", + compilation_steps={step}, + ) + 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 was tired of my lady + \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 == ExitCode.COLLECTION_FAILED + assert message in result.output + + +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_compile_latex_document_with_task_decorator(runner, tmp_path): + """Test simple compilation.""" + task_source = """ + import pytask + + @pytask.mark.latex(script="document.tex", document="document.pdf") + @pytask.mark.task + def compile_document(): + pass + """ + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) + + latex_source = r""" + \documentclass{report} + \begin{document} + I was tired of my lady + \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 == ExitCode.OK diff --git a/tests/test_latex_dependency_scanner.py b/tests/test_latex_dependency_scanner.py index 760389e..0c33aaf 100644 --- a/tests/test_latex_dependency_scanner.py +++ b/tests/test_latex_dependency_scanner.py @@ -5,22 +5,21 @@ import pytest from conftest import needs_latexmk from conftest import skip_on_github_actions_with_win +from pytask import ExitCode from pytask import main @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end -def test_infer_dependencies_from_task(tmp_path): +@pytest.mark.parametrize("infer_dependencies", [True, False]) +def test_infer_dependencies_from_task(tmp_path, infer_dependencies): task_source = """ import pytask - @pytask.mark.latex - @pytask.mark.depends_on("document.tex") - @pytask.mark.produces("document.pdf") + @pytask.mark.latex(script="document.tex", document="document.pdf") def task_compile_document(): pass - """ tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) @@ -33,8 +32,14 @@ def task_compile_document(): tmp_path.joinpath("document.tex").write_text(textwrap.dedent(latex_source)) tmp_path.joinpath("sub_document.tex").write_text("Lorem ipsum.") - session = main({"paths": tmp_path}) + tmp_path.joinpath("pytask.ini").write_text( + f"[pytask]\ninfer_latex_dependencies = {infer_dependencies}" + ) - assert session.exit_code == 0 + session = main({"paths": tmp_path}) + assert session.exit_code == ExitCode.OK assert len(session.tasks) == 1 - assert len(session.tasks[0].depends_on) == 2 + if infer_dependencies: + assert len(session.tasks[0].depends_on) == 2 + else: + assert len(session.tasks[0].depends_on) == 1 diff --git a/tests/test_normal_execution_w_plugin.py b/tests/test_normal_execution_w_plugin.py index ee19252..4a810eb 100644 --- a/tests/test_normal_execution_w_plugin.py +++ b/tests/test_normal_execution_w_plugin.py @@ -5,6 +5,7 @@ import pytest from pytask import cli +from pytask import ExitCode @pytest.mark.end_to_end @@ -35,4 +36,4 @@ def task_dummy(depends_on, produces): tmp_path.joinpath(dependency).touch() result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == 0 + assert result.exit_code == ExitCode.OK diff --git a/tests/test_parallel.py b/tests/test_parallel.py index 60f7a5d..3162d53 100644 --- a/tests/test_parallel.py +++ b/tests/test_parallel.py @@ -9,10 +9,12 @@ from conftest import needs_latexmk from conftest import skip_on_github_actions_with_win from pytask import cli +from pytask import ExitCode + try: import pytask_parallel # noqa: F401 -except ImportError: +except ImportError: # pragma: no cover _IS_PYTASK_PARALLEL_INSTALLED = False else: _IS_PYTASK_PARALLEL_INSTALLED = True @@ -31,14 +33,16 @@ @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end -def test_parallel_parametrization_over_source_files(runner, tmp_path): +def test_parallel_parametrization_over_source_files_w_parametrize(runner, tmp_path): source = """ import pytask - @pytask.mark.latex @pytask.mark.parametrize( - "depends_on, produces", - [("document_1.tex", "document_1.pdf"), ("document_2.tex", "document_2.pdf")], + "latex", + [ + {"script": "document_1.tex", "document": "document_1.pdf"}, + {"script": "document_2.tex", "document": "document_2.pdf"} + ], ) def task_compile_latex_document(): pass @@ -64,7 +68,58 @@ def task_compile_latex_document(): start = time.time() result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == 0 + assert result.exit_code == ExitCode.OK + duration_normal = time.time() - start + + for name in ["document_1.pdf", "document_2.pdf"]: + tmp_path.joinpath(name).unlink() + + start = time.time() + result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) + + assert result.exit_code == ExitCode.OK + 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_files_w_loop(runner, tmp_path): + source = """ + import pytask + + for i in range(1, 3): + + @pytask.mark.task + @pytask.mark.latex(script=f"document_{i}.tex", document=f"document_{i}.pdf") + def task_compile_latex_document(): + pass + """ + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(source)) + + latex_source = r""" + \documentclass{report} + \begin{document} + He said yeah. + \end{document} + """ + tmp_path.joinpath("document_1.tex").write_text(textwrap.dedent(latex_source)) + + latex_source = r""" + \documentclass{report} + \begin{document} + You better come out with your hands up. + \end{document} + """ + tmp_path.joinpath("document_2.tex").write_text(textwrap.dedent(latex_source)) + + start = time.time() + result = runner.invoke(cli, [tmp_path.as_posix()]) + + assert result.exit_code == ExitCode.OK duration_normal = time.time() - start for name in ["document_1.pdf", "document_2.pdf"]: @@ -73,7 +128,7 @@ def task_compile_latex_document(): start = time.time() result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) - assert result.exit_code == 0 + assert result.exit_code == ExitCode.OK duration_parallel = time.time() - start assert duration_parallel < duration_normal @@ -83,28 +138,29 @@ def task_compile_latex_document(): @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end -def test_parallel_parametrization_over_source_file(runner, tmp_path): +def test_parallel_parametrization_over_source_file_w_parametrize(runner, tmp_path): source = """ import pytask - from pytask_latex import compilation_steps + from pytask_latex import compilation_steps as cs - @pytask.mark.depends_on("document.tex") @pytask.mark.parametrize( - "produces, latex", + "latex", [ - ( - "document.pdf", - {"compilation_steps": compilation_steps.latexmk( + { + "script": "document.tex", + "document": "document.pdf", + "compilation_steps": cs.latexmk( ("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd") - )} - ), - ( - "document.dvi", - {"compilation_steps": compilation_steps.latexmk( + ), + }, + { + "script": "document.tex", + "document": "document.dvi", + "compilation_steps": cs.latexmk( ("--dvi", "--interaction=nonstopmode", "--synctex=1", "--cd") - )} - ), - ], + ), + } + ] ) def task_compile_latex_document(): pass @@ -122,7 +178,57 @@ def task_compile_latex_document(): start = time.time() result = runner.invoke(cli, [tmp_path.as_posix()]) - assert result.exit_code == 0 + assert result.exit_code == ExitCode.OK + duration_normal = time.time() - start + + for name in ["document.pdf", "document.dvi"]: + tmp_path.joinpath(name).unlink() + + start = time.time() + result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) + + assert result.exit_code == ExitCode.OK + 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_w_loop(runner, tmp_path): + source = """ + import pytask + from pytask_latex import compilation_steps as cs + + for ending in ("pdf", "dvi"): + + @pytask.mark.task + @pytask.mark.latex( + script="document.tex", + document=f"document.{ending}", + compilation_steps=cs.latexmk( + (f"--{ending}", "--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 == ExitCode.OK duration_normal = time.time() - start for name in ["document.pdf", "document.dvi"]: @@ -131,7 +237,7 @@ def task_compile_latex_document(): start = time.time() result = runner.invoke(cli, [tmp_path.as_posix(), "-n", 2]) - assert result.exit_code == 0 + assert result.exit_code == ExitCode.OK duration_parallel = time.time() - start assert duration_parallel < duration_normal diff --git a/tests/test_parametrize.py b/tests/test_parametrize.py index 153c60e..6209986 100644 --- a/tests/test_parametrize.py +++ b/tests/test_parametrize.py @@ -5,48 +5,60 @@ import pytest from conftest import needs_latexmk from conftest import skip_on_github_actions_with_win +from pytask import ExitCode from pytask import main -from pytask_latex.parametrize import pytask_parametrize_kwarg_to_marker -def func(): - pass +@needs_latexmk +@skip_on_github_actions_with_win +@pytest.mark.end_to_end +def test_parametrized_compilation_of_latex_documents_w_parametrize(tmp_path): + task_source = """ + import pytask + @pytask.mark.parametrize("latex", [ + {"script": "document_1.tex", "document": "document_1.pdf"}, + {"script": "document_2.tex", "document": "document_2.pdf"}, + ]) + def task_compile_latex_document(): + pass + """ + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) -@pytest.mark.unit -@pytest.mark.parametrize( - "obj, kwargs, expected", - [(len, {}, None), (func, {"latex": ["--dummy-option"]}, func), (func, {}, None)], -) -def test_pytask_generate_tasks_add_marker(obj, kwargs, expected): - pytask_parametrize_kwarg_to_marker(obj, kwargs) + for name, content in [ + ("document_1.tex", "Like a worn out recording"), + ("document_2.tex", "Of a favorite song"), + ]: + latex_source = rf""" + \documentclass{{report}} + \begin{{document}} + {content} + \end{{document}} + """ + tmp_path.joinpath(name).write_text(textwrap.dedent(latex_source)) - if expected is None: - assert not hasattr(obj, "pytaskmark") - else: - assert obj.pytaskmark + session = main({"paths": tmp_path}) - # Cleanup necessary since func is changed in-place. - if hasattr(obj, "pytaskmark"): - delattr(obj, "pytaskmark") + assert session.exit_code == ExitCode.OK + assert tmp_path.joinpath("document_1.pdf").exists() + assert tmp_path.joinpath("document_2.pdf").exists() @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end -def test_parametrized_compilation_of_latex_documents(tmp_path): - task_source = """ +def test_parametrized_compilation_of_latex_documents_w_loop(tmp_path): + source = """ import pytask - @pytask.mark.latex - @pytask.mark.parametrize("depends_on, produces", [ - ("document_1.tex", "document_1.pdf"), - ("document_2.tex", "document_2.pdf"), - ]) - def task_compile_latex_document(): - pass + for i in range(1, 3): + + @pytask.mark.task + @pytask.mark.latex(script=f"document_{i}.tex", document=f"document_{i}.pdf") + def task_compile_latex_document(): + pass """ - tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(task_source)) + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(source)) for name, content in [ ("document_1.tex", "Like a worn out recording"), @@ -62,7 +74,7 @@ def task_compile_latex_document(): session = main({"paths": tmp_path}) - assert session.exit_code == 0 + assert session.exit_code == ExitCode.OK assert tmp_path.joinpath("document_1.pdf").exists() assert tmp_path.joinpath("document_2.pdf").exists() @@ -70,27 +82,30 @@ def task_compile_latex_document(): @needs_latexmk @skip_on_github_actions_with_win @pytest.mark.end_to_end -def test_parametrizing_latex_options(tmp_path): +def test_parametrizing_latex_options_w_parametrize(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")) + from pytask_latex import compilation_steps as cs + + @pytask.mark.parametrize( + "latex", + [ + { + "script": "document.tex", + "document": "document.pdf", + "compilation_steps": cs.latexmk( + ("--pdf", "--interaction=nonstopmode", "--synctex=1", "--cd") + ), + }, + { + "script": "document.tex", + "document": "document.dvi", + "compilation_steps": cs.latexmk( + ("--dvi", "--interaction=nonstopmode", "--synctex=1", "--cd") + ), } - ), - ( - "document.tex", - "document.dvi", - {"compilation_steps": compilation_steps.latexmk( - ("--interaction=nonstopmode", "--dvi", "--cd")) - } - ), - ]) + ] + ) def task_compile_latex_document(): pass """ @@ -106,6 +121,44 @@ def task_compile_latex_document(): session = main({"paths": tmp_path}) - assert session.exit_code == 0 + assert session.exit_code == ExitCode.OK + 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_w_loop(tmp_path): + source = """ + import pytask + from pytask_latex import compilation_steps as cs + + for ending in ("pdf", "dvi"): + + @pytask.mark.task + @pytask.mark.latex( + script="document.tex", + document=f"document.{ending}", + compilation_steps=cs.latexmk( + (f"--{ending}", "--interaction=nonstopmode", "--synctex=1", "--cd") + ) + ) + def compile_latex_document(): + pass + """ + tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(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 == ExitCode.OK assert tmp_path.joinpath("document.pdf").exists() assert tmp_path.joinpath("document.dvi").exists() diff --git a/tox.ini b/tox.ini index 178e860..a2f750d 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,13 @@ skipsdist = True skip_missing_interpreters = True [testenv] -passenv = CI GITHUB_ACTIONS +passenv = CI basepython = python [testenv:pytest] conda_deps = - pytask >=0.1.0 - pytask-parallel >=0.1.0 + pytask >=0.2 + pytask-parallel >=0.1 latex-dependency-scanner pytest pytest-cov @@ -22,10 +22,6 @@ commands = pip install -e . --no-deps pytest {posargs} -[doc8] -ignore = D002, D004 -max-line-length = 89 - [flake8] docstring-convention = numpy ignore =