diff --git a/docs/conf.py b/docs/conf.py index 262e234..55d9a77 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,6 +11,7 @@ jupyterlite_contents = "./custom_contents" jupyterlite_bind_ipynb_suffix = False +strip_tagged_cells = True master_doc = "index" diff --git a/docs/configuration.md b/docs/configuration.md index 25111c7..38ba2a7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -53,6 +53,72 @@ to your JupyterLite deployment. jupyterlite_config = "jupyterlite_config.json" ``` +## Strip particular tagged cells from IPython Notebooks + +When using the `NotebookLite`, `JupyterLite`, or `Voici` directives with a notebook passed to them, you can +strip particular tagged cells from the notebook before rendering it in the JupyterLite console. + +This behaviour can be enabled by setting the following config: + +```python +strip_tagged_cells = True +``` + +and then by tagging the cells you want to strip with the tag `jupyterlite_sphinx_strip` in the JSON metadata +of the cell, like this: + +```json +{ + "tags": [ + "jupyterlite_sphinx_strip": "true" + ] +} +``` + +This is useful when you want to remove some cells from the rendered notebook in the JupyterLite +console, for example, cells that are used for adding reST-based directives or other +Sphinx-specific content. + +For example, you can use this feature to remove the `toctree` directive from the rendered notebook +in the JupyterLite console: + +```json +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "jupyterlite_sphinx_strip": "true" + ] + }, + "source": [ + "# Table of Contents\n", + "\n", + "```{toctree}\n", + ":maxdepth: 2\n", + "\n", + "directives/jupyterlite\n", + "directives/notebooklite\n", + "directives/replite\n", + "directives/voici\n", + "directives/try_examples\n", + "full\n", + "changelog\n", + "```" + ] + } + ] +} +``` + +where the cell with the `toctree` directive will be removed from the rendered notebook in +the JupyterLite console. + +Note that this feature is only available for the `NotebookLite`, `JupyterLite`, and the +`Voici` directives and works with the `.ipynb` files passed to them. It is not implemented +for the `TryExamples` directive. + ## Disable the `.ipynb` docs source binding By default, jupyterlite-sphinx binds the `.ipynb` source suffix so that it renders Notebooks included in the doctree with JupyterLite. diff --git a/jupyterlite_sphinx/jupyterlite_sphinx.py b/jupyterlite_sphinx/jupyterlite_sphinx.py index 0f3b1af..c645cca 100644 --- a/jupyterlite_sphinx/jupyterlite_sphinx.py +++ b/jupyterlite_sphinx/jupyterlite_sphinx.py @@ -24,6 +24,8 @@ from ._try_examples import examples_to_notebook, insert_try_examples_directive +import nbformat + try: import voici except ImportError: @@ -338,12 +340,32 @@ def run(self): notebooks_dir = Path(self.env.app.srcdir) / CONTENT_DIR / notebook_name - # Copy the Notebook for NotebookLite to find + notebook_is_stripped: bool = self.env.config.strip_tagged_cells + + # Create a folder to copy the notebooks to and for NotebookLite to find os.makedirs(os.path.dirname(notebooks_dir), exist_ok=True) - try: - shutil.copyfile(notebook, str(notebooks_dir)) - except shutil.SameFileError: - pass + + if notebook_is_stripped: + # Note: the directives meant to be stripped must be inside their own + # cell so that the cell itself gets removed from the notebook. This + # is so that we don't end up removing useful data or directives that + # are not meant to be removed. + + nb = nbformat.read(notebook, as_version=4) + print(f"Opened {notebook_name}") + nb = nbformat.read(notebook, as_version=4) + nb.cells = [ + cell + for cell in nb.cells + if "true" not in cell.metadata.get("strip", []) + ] + nbformat.write(nb, notebooks_dir, version=4) + + # If notebook_is_stripped is False, then copy the notebook(s) to notebooks_dir. + # If it is True, then they have already been copied to notebooks_dir by the + # nbformat.write() function above. + else: + shutil.copy(notebook, notebooks_dir) else: notebook_name = None @@ -749,6 +771,7 @@ def setup(app): app.add_config_value("jupyterlite_contents", None, rebuild="html") app.add_config_value("jupyterlite_bind_ipynb_suffix", True, rebuild="html") app.add_config_value("jupyterlite_silence", True, rebuild=True) + app.add_config_value("strip_tagged_cells", False, rebuild=True) # Pass a dictionary of additional options to the JupyterLite build command app.add_config_value("jupyterlite_build_command_options", None, rebuild="html")