diff --git a/docs/computation/execute.md b/docs/computation/execute.md index 0b280224..4c6e6df1 100644 --- a/docs/computation/execute.md +++ b/docs/computation/execute.md @@ -227,3 +227,27 @@ which produces: ```{nb-exec-table} ``` + +(execute/builder-dep)= +## Builder-dependent execution + +```{warning} +This is an experimental feature that is **not** part of the core `MyST` markup specification, and may be removed in the future. Using `:only:` may also not work well with caching and may require deleting previously built files when switching builders. +``` + +It may be desirable to execute different code depending on the Sphinx builder being used. +For example, one may want to have different setup code for plots to be displayed in a website compared to those in a PDF file obtained via LaTeX. +One can use the `only` option in situations like these, like in the following example: + +````md +```{code-cell} +:only: html +import matplotlib.pyplot as plt +plt.style.use('ggplot') +``` + +```{code-cell} +:only: latex +plt.style.use('fivethirtyeight') +``` +```` diff --git a/myst_nb/core/read.py b/myst_nb/core/read.py index 6951f34a..6683beb3 100644 --- a/myst_nb/core/read.py +++ b/myst_nb/core/read.py @@ -1,4 +1,5 @@ """Module for reading notebook formats from a string input.""" + from __future__ import annotations import dataclasses as dc @@ -86,6 +87,7 @@ def create_nb_reader( config=md_config, add_source_map=True, path=path, + builder=nb_config.builder_name, ), md_config, {"type": "plugin", "name": "myst_nb_md"}, @@ -176,6 +178,7 @@ def read_myst_markdown_notebook( raw_directive="{raw-cell}", add_source_map=False, path: str | Path | None = None, + builder: str = "", ) -> nbf.NotebookNode: """Convert text written in the myst format to a notebook. @@ -260,9 +263,12 @@ def _flush_markdown(start_line, token, md_metadata): ) meta = nbf.from_dict(options) source_map.append(token_map[0] + 1) - notebook.cells.append( - nbf_version.new_code_cell(source="\n".join(body_lines), metadata=meta) - ) + if "only" not in options or options["only"] == builder: + notebook.cells.append( + nbf_version.new_code_cell( + source="\n".join(body_lines), metadata=meta + ) + ) md_metadata = {} md_start_line = token_map[1] diff --git a/myst_nb/sphinx_.py b/myst_nb/sphinx_.py index 4885d9ff..80f4a68d 100644 --- a/myst_nb/sphinx_.py +++ b/myst_nb/sphinx_.py @@ -1,4 +1,5 @@ """The sphinx parser implementation for myst-nb.""" + from __future__ import annotations from collections import defaultdict @@ -80,6 +81,7 @@ def parse(self, inputstring: str, document: nodes.document) -> None: # get notebook rendering configuration nb_config: NbParserConfig = self.env.mystnb_config + nb_config.builder_name = self.env.app.builder.name # create a reader for the notebook nb_reader = create_nb_reader(document_path, md_config, nb_config, inputstring) diff --git a/tests/conftest.py b/tests/conftest.py index 7d8f6436..f7921492 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -104,6 +104,14 @@ def get_html(self, index=0): pytest.fail("html not output") return bs4.BeautifulSoup(_path.read_text(), "html.parser") + def get_latex(self, index=0): + """Return the built LaTeX file.""" + name = self.files[index][0] + _path = self.app.outdir / (name + ".tex") + if not _path.exists(): + pytest.fail("tex not output") + return _path.read_text(encoding="utf-8") + def get_nb(self, index=0): """Return the output notebook (after any execution).""" name = self.files[index][0] diff --git a/tests/notebooks/with_only.md b/tests/notebooks/with_only.md new file mode 100644 index 00000000..013c3491 --- /dev/null +++ b/tests/notebooks/with_only.md @@ -0,0 +1,24 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: '0.8' + jupytext_version: 1.4.1+dev +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Test the `:only:` option + +```{code-cell} +:only: html +1+1 +``` + +```{code-cell} +:only: latex +2+2 +``` diff --git a/tests/test_execute.py b/tests/test_execute.py index f39d1df0..847b6840 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -1,4 +1,5 @@ """Test sphinx builds which execute notebooks.""" + import os from pathlib import Path @@ -327,6 +328,55 @@ def test_nb_exec_table(sphinx_run, file_regression): assert any("nb_exec_table" in row.text for row in rows) +@pytest.mark.sphinx_params( + "with_only.md", conf={"nb_execution_mode": "auto"}, buildername="html" +) +def test_only_html(sphinx_run, file_regression): + """Test that the table gets output into the HTML, + including a row for the executed notebook. + """ + sphinx_run.build() + # print(sphinx_run.status()) + assert not sphinx_run.warnings() + file_regression.check( + sphinx_run.get_doctree().pformat(), extension=".xml", encoding="utf-8" + ) + # print(sphinx_run.get_html()) + output = sphinx_run.get_html().select("div.output div.highlight pre") + assert len(output) == 1 # check that other cell is not present + assert "2" in output[0].text # check value to ensure correct cell is present + + +@pytest.mark.sphinx_params( + "with_only.md", + conf={ + "nb_execution_mode": "auto", + "latex_documents": [('with_only', 'with_only.tex', "project", "author", 'manual')] + }, + buildername="latex" +) +def test_only_latex(sphinx_run, file_regression): + """Test that the table gets output into the HTML, + including a row for the executed notebook. + """ + sphinx_run.build() + # print(sphinx_run.status()) + assert not sphinx_run.warnings() + file_regression.check( + sphinx_run.get_doctree().pformat(), extension=".xml", encoding="utf-8" + ) + # print(sphinx_run.get_html()) + output = sphinx_run.get_latex() + correct = ( + r"\begin{sphinxVerbatim}[commandchars=\\\{\}]" "\n4\n" r"\end{sphinxVerbatim}" + ) + assert correct in output # check value to ensure correct cell is present + wrong = ( + r"\begin{sphinxVerbatim}[commandchars=\\\{\}]" "\n2\n" r"\end{sphinxVerbatim}" + ) + assert wrong not in output # check value to ensure other cell is not present + + @pytest.mark.sphinx_params( "custom-formats.Rmd", conf={ diff --git a/tests/test_execute/test_only_html.xml b/tests/test_execute/test_only_html.xml new file mode 100644 index 00000000..73925712 --- /dev/null +++ b/tests/test_execute/test_only_html.xml @@ -0,0 +1,16 @@ + +
+ + Test the + <literal> + :only: + option + <container cell_index="1" cell_metadata="{'only': 'html'}" classes="cell" exec_count="1" nb_element="cell_code"> + <container classes="cell_input" nb_element="cell_code_source"> + <literal_block language="ipython3" xml:space="preserve"> + 1+1 + <container classes="cell_output" nb_element="cell_code_output"> + <container nb_element="mime_bundle"> + <container mime_type="text/plain"> + <literal_block classes="output text_plain" language="myst-ansi" xml:space="preserve"> + 2 diff --git a/tests/test_execute/test_only_latex.xml b/tests/test_execute/test_only_latex.xml new file mode 100644 index 00000000..8279ec51 --- /dev/null +++ b/tests/test_execute/test_only_latex.xml @@ -0,0 +1,16 @@ +<document source="with_only"> + <section ids="test-the-only-option" names="test\ the\ :only:\ option"> + <title> + Test the + <literal> + :only: + option + <container cell_index="1" cell_metadata="{'only': 'latex'}" classes="cell" exec_count="1" nb_element="cell_code"> + <container classes="cell_input" nb_element="cell_code_source"> + <literal_block language="ipython3" xml:space="preserve"> + 2+2 + <container classes="cell_output" nb_element="cell_code_output"> + <container nb_element="mime_bundle"> + <container mime_type="text/plain"> + <literal_block classes="output text_plain" language="myst-ansi" xml:space="preserve"> + 4