From 1c00cdec0e67ebdc6e1e3cb0e7bf1de9e180ca1b Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 21 Apr 2022 09:35:19 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Add=20image=20opt?= =?UTF-8?q?ions=20to=20`glue:figure`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/use/glue.md | 26 ++++++++++++++++++++-- myst_nb/core/render.py | 12 ++++++---- myst_nb/glue/directives.py | 30 ++++++++++++++++++++----- myst_nb/glue/utils.py | 45 ++++++++++++++++++++++++++++++-------- 4 files changed, 93 insertions(+), 20 deletions(-) diff --git a/docs/use/glue.md b/docs/use/glue.md index a299a5d5..66b91ea3 100644 --- a/docs/use/glue.md +++ b/docs/use/glue.md @@ -240,10 +240,24 @@ My rounded mean: {glue:text}`boot_mean:.2f` (95% CI: {glue:text}`boot_clo:.2f`/{ ### The `glue:figure` directive With `glue:figure` you can apply more formatting to figure like objects, -such as giving them a caption and referencable label: +such as giving them a caption and referenceable label: + +:::{table} `glue:figure` directive options +| Option | Type | Description | +| ------ | ---- | ----------- | +| alt | text | Alternate text of an image | +| height | length | The desired height of an image | +| width | length or percentage | The width of an image | +| scale | percentage | The uniform scaling factor of an image | +| class | text | A space-separated list of class names for the image | +| figwidth | length or percentage | The width of the figure | +| figclass | text | A space-separated list of class names for the figure | +| name | text | referenceable label for the figure | +::: ````md ```{glue:figure} boot_fig +:alt: "Alternative title" :figwidth: 300px :name: "fig-boot" @@ -252,6 +266,7 @@ This is a **caption**, with an embedded `{glue:text}` element: {glue:text}`boot_ ```` ```{glue:figure} boot_fig +:alt: "Alternative title" :figwidth: 300px :name: "fig-boot" @@ -291,7 +306,14 @@ A caption for a pandas table. The `glue:math` directive, is specific to latex math outputs (glued variables that contain a `text/latex` mimetype), and works similarly to the [sphinx math directive](https://www.sphinx-doc.org/en/1.8/usage/restructuredtext/directives.html#math). -For example: + +:::{table} `glue:math` directive options +| Option | Type | Description | +| ------ | ---- | ----------- | +| nowrap | flag | Prevent any wrapping of the given math in a math environment | +| class | text | A space-separated list of class names | +| label or name | text | referenceable label for the figure | +::: ```{code-cell} ipython3 import sympy as sym diff --git a/myst_nb/core/render.py b/myst_nb/core/render.py index 9145860f..0ef0cbe4 100644 --- a/myst_nb/core/render.py +++ b/myst_nb/core/render.py @@ -24,6 +24,7 @@ from nbformat import NotebookNode from typing_extensions import Protocol +from myst_nb.core.config import NbParserConfig from myst_nb.core.loggers import DEFAULT_LOG_TYPE, LoggerType if TYPE_CHECKING: @@ -94,6 +95,11 @@ def renderer(self) -> DocutilsNbRenderer | SphinxNbRenderer: """The renderer this output renderer is associated with.""" return self._renderer + @property + def config(self) -> NbParserConfig: + """The notebook parser config""" + return self._renderer.nb_config + @property def logger(self) -> LoggerType: """The logger for this renderer. @@ -126,7 +132,7 @@ def write_file( :returns: URI to use for referencing the file """ - output_folder = self.renderer.nb_config.output_folder + output_folder = self.config.output_folder filepath = Path(output_folder).joinpath(*path) if not output_folder: pass # do not output anything if output_folder is not set (docutils only) @@ -183,9 +189,7 @@ def render_nb_metadata(self, metadata: dict) -> dict: "body": sanitize_script_content(json.dumps(ipywidgets_mime)), }, ) - for i, (path, kwargs) in enumerate( - self.renderer.nb_config.ipywidgets_js.items() - ): + for i, (path, kwargs) in enumerate(self.config.ipywidgets_js.items()): self.add_js_file(f"ipywidgets_{i}", path, kwargs) return metadata diff --git a/myst_nb/glue/directives.py b/myst_nb/glue/directives.py index 2da64cef..6d8726b3 100644 --- a/myst_nb/glue/directives.py +++ b/myst_nb/glue/directives.py @@ -117,7 +117,7 @@ def run(self) -> List[nodes.Node]: ] # TODO this "override" feels a bit hacky - cell_key = result.nb_renderer.renderer.nb_config.cell_render_key + cell_key = result.nb_renderer.config.cell_render_key mime = MimeData( "text/markdown", result.data["text/markdown"], @@ -134,7 +134,11 @@ def run(self) -> List[nodes.Node]: class PasteFigureDirective(_PasteDirectiveBase): - """A directive for pasting code outputs from notebooks, wrapped in a figure.""" + """A directive for pasting code outputs from notebooks, wrapped in a figure. + + Mirrors: + https://github.com/docutils-mirror/docutils/blob/9649abee47b4ce4db51be1d90fcb1fb500fa78b3/docutils/parsers/rst/directives/images.py#95 + """ def align(argument): return directives.choice(argument, ("left", "center", "right")) @@ -143,6 +147,13 @@ def figwidth_value(argument): return directives.length_or_percentage_or_unitless(argument, "px") option_spec = { + # note we don't add converters for image options, + # since this is handled in `NbElementRenderer.render_image` + "alt": directives.unchanged, + "height": directives.unchanged, + "width": directives.unchanged, + "scale": directives.unchanged, + "class": directives.unchanged, "figwidth": figwidth_value, "figclass": directives.class_option, "align": align, @@ -155,7 +166,15 @@ def run(self): data = retrieve_glue_data(self.document, self.arguments[0]) except RetrievalError as exc: return [glue_warning(str(exc), self.document, self.line)] - paste_nodes = render_glue_output(data, self.document, self.line, self.source) + render: Dict[str, Any] = {} + for key in ("alt", "height", "width", "scale", "class"): + if key in self.options: + render.setdefault("image", {})[ + key.replace("classes", "class") + ] = self.options[key] + paste_nodes = render_glue_output( + data, self.document, self.line, self.source, render=render + ) # note: most of this is copied directly from sphinx.Figure @@ -204,10 +223,11 @@ class PasteMathDirective(_PasteDirectiveBase): """A directive for pasting latex outputs from notebooks as math.""" option_spec = { - "label": directives.unchanged, - "name": directives.unchanged, "class": directives.class_option, "nowrap": directives.flag, + # these are equivalent + "label": directives.unchanged, + "name": directives.unchanged, } def run(self) -> List[nodes.Node]: diff --git a/myst_nb/glue/utils.py b/myst_nb/glue/utils.py index 963db91c..c363e96b 100644 --- a/myst_nb/glue/utils.py +++ b/myst_nb/glue/utils.py @@ -86,7 +86,9 @@ def render_glue_output( document: nodes.document, line: int, source: str, - inline=False, + *, + inline: bool = False, + render: Optional[Dict[str, Any]] = None, ) -> List[nodes.Node]: """Retrive the notebook output data for this glue key, then return the docutils/sphinx nodes relevant to this data. @@ -96,16 +98,32 @@ def render_glue_output( :param line: The current source line number of the directive or role. :param source: The current source path or description. :param inline: Whether to render the output as inline (or block). + :param render: Cell-level render metadata :returns: A tuple of (was the key found, the docutils/sphinx nodes). """ + cell_metadata = {} + if render: + cell_metadata[data.nb_renderer.config.cell_render_key] = render if is_sphinx(document): _nodes = _render_output_sphinx( - data.nb_renderer, data.data, data.metadata, source, line, inline + data.nb_renderer, + data.data, + cell_metadata, + data.metadata, + source, + line, + inline, ) else: _nodes = _render_output_docutils( - data.nb_renderer, data.data, data.metadata, document, line, inline + data.nb_renderer, + data.data, + cell_metadata, + data.metadata, + document, + line, + inline, ) # TODO rendering should perhaps return if it succeeded explicitly, # and whether system_messages or not (required for roles) @@ -115,15 +133,16 @@ def render_glue_output( def _render_output_docutils( nb_renderer: NbElementRenderer, data: Dict[str, Any], - metadata: Dict[str, Any], + cell_metadata: Dict[str, Any], + output_metadata: Dict[str, Any], document: nodes.document, line: int, inline=False, ) -> List[nodes.Node]: """Render the output in docutils (select mime priority directly).""" mime_priority = get_mime_priority( - nb_renderer.renderer.nb_config.builder_name, - nb_renderer.renderer.nb_config.mime_priority_overrides, + nb_renderer.config.builder_name, + nb_renderer.config.mime_priority_overrides, ) try: mime_type = next(x for x in mime_priority if x in data) @@ -139,7 +158,8 @@ def _render_output_docutils( mime_data = MimeData( mime_type, data[mime_type], - output_metadata=metadata, + cell_metadata=cell_metadata, + output_metadata=output_metadata, line=line, ) if inline: @@ -150,7 +170,8 @@ def _render_output_docutils( def _render_output_sphinx( nb_renderer: NbElementRenderer, data: Dict[str, Any], - metadata: Dict[str, Any], + cell_metadata: Dict[str, Any], + output_metadata: Dict[str, Any], source: str, line: int, inline=False, @@ -161,7 +182,13 @@ def _render_output_sphinx( for mime_type, content in data.items(): mime_container = nodes.container(mime_type=mime_type) set_source_info(mime_container, source, line) - mime_data = MimeData(mime_type, content, output_metadata=metadata, line=line) + mime_data = MimeData( + mime_type, + content, + cell_metadata=cell_metadata, + output_metadata=output_metadata, + line=line, + ) if inline: _nodes = nb_renderer.render_mime_type_inline(mime_data) else: From d26280fb150a0a94c30192b89fc28f377179c7ce Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 21 Apr 2022 09:43:51 +0200 Subject: [PATCH 2/2] Update tests.yml --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7a0412ae..82666b83 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,10 +61,10 @@ jobs: # this is why we run `coverage xml` afterwards (required by codecov) - name: Upload to Codecov - if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.7 && matrix.sphinx == '>=3,<4' && github.repository == 'executablebooks/MyST-NB' + if: github.repository == 'executablebooks/MyST-NB' uses: codecov/codecov-action@v1 with: - name: myst-nb-pytests-py3.7 + name: myst-nb-pytests flags: pytests file: ./coverage.xml fail_ci_if_error: true