Skip to content

Commit

Permalink
Add more configuration options to TryExamples directive and add docum…
Browse files Browse the repository at this point in the history
…entation (#116)

* Fix notebooks path

* Add additional config options for try_examples

* Put button in top right

* Make TryExamples option configuration uniform

* Fix path in example

* Handle math at end of cell

* Remove now unused toolbar option

- This seems to be no longer relevant after switch to notebooklite

* Make try_examples button position top or bottom configurable

* Add documentation for TryExamples directive

* Handle sphinx link syntax in TryExamples

* Mention link conversion in try_examples docs

* Black fix code
  • Loading branch information
steppi authored Dec 22, 2023
1 parent e664027 commit b153587
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 28 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

extensions = [
'sphinx.ext.mathjax',
'jupyterlite_sphinx',
'myst_parser',
]
Expand Down
168 changes: 168 additions & 0 deletions docs/directives/try_examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# TryExamples directive

`jupyterlite-sphinx` provides the experimental `try_examples` directive which allows
docstring examples sections written in [doctestformat](https://docs.python.org/3/library/doctest.html) to be swapped with an embedded classic Notebook at the push of a button.


```rst
Examples
--------
.. try_examples::
:button_css:
background-color: #f7dc1e;
border: none;
padding: 5px 10px;
border-radius: 15px;
font-family: vibur;
font-size: x-large;
box-shadow: 0 2px 5px rgba(108,108,108,0.2);
:button_hover_css:
background-color: #fff221;
transform: scale(1.02);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
cursor: pointer;
:button_horizontal_position: right
:button_vertical_position: top
:button_text: Try it in a classic notebook!
:min_height: 200px
Doctest examples sections are parsed and converted to notebooks. Blocks of text
like this become markdown cells. Codeblocks begin with `>>>`. Contiguous blocks
of code are combined into a single code cell.
>>> x = 2
>>> y = 2
>>> x + y
4
`...` is used to continue multiline statements.
>>> def f(x, y):
... return x + y
>>> f(2, 2)
4
Inline LaTeX like :math:`x + y = 4` is converted, as is block LaTeX like
.. math::
\int_{x=-\infty}^{\infty}e^{-x^2}\mathrm{d}x = \sqrt{\pi}
If you are displaying `math output <https://www.sphinx-doc.org/en/master/usage/extensions/math.html>`_
with sphinx. Sphinx links such as the one in the previous sentence are also converted to
markdown format.
```


```{eval-rst}
Examples
--------
.. try_examples::
:button_css:
background-color: #f7dc1e;
border: none;
padding: 5px 10px;
border-radius: 15px;
font-family: vibur;
font-size: x-large;
box-shadow: 0 2px 5px rgba(108,108,108,0.2);
:button_hover_css:
background-color: #fff221;
transform: scale(1.02);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
cursor: pointer;
:button_horizontal_position: right
:button_vertical_position: top
:button_text: Try it in a classic notebook!
:min_height: 200px
Doctest examples sections are parsed and converted to notebooks. Blocks of text
like this become markdown cells. Codeblocks begin with `>>>`. Contiguous blocks
of code are combined into a single code cell.
>>> x = 2
>>> y = 2
>>> x + y
4
`...` is used to continue multiline statements.
>>> def f(x, y):
... return x + y
>>> f(2, 2)
4
Inline LaTeX like :math:`x + y = 4` is converted, as is block LaTeX like
.. math::
\int_{-\infty}^{\infty}e^{-x^2}\mathrm{d}x = \sqrt{\pi}
If you are displaying `math output <https://www.sphinx-doc.org/en/master/usage/extensions/math.html>`_
with sphinx. Sphinx links such as the one in the previous sentence are also converted to
markdown format.
```

## Configuration

The button's text, position, and style can be configured to match your page design. The
text can be configured with the option `:button_text:`. The options `:button_css:` and
`:button_hover_css:` take lists of css properties as in the example above, and
apply them to the button. `:button_horizontal_position:` can be one of `left`, `right`, or
`center` and `:button_vertical_position:` can be one of `top` or `bottom`. The position
is with respect to the rendered examples block / embedded notebook
(depending on which is active).

The height of the embedded notebook's iframe container is calculated to match the height
of the rendered doctest examples so that it takes up the same amount of space on the
page. The `:min_height:` option can be used to ensure that the embedded notebook will not
be unuseably small for very short examples blocks, though its use can cause the contents
of the page to shift when the button is pressed.

the `:theme:` option available for other `jupyterlite-sphinx` directives is also
available.

If you are using [sphinx.ext.autodoc](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html) with [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) or [sphinx.ext.napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html), you
can set the option

```python
global_enable_try_examples = True
```

in your sphinx `conf.py` in order to automatically insert the `try_examples` directive
in examples sections during the `"autodoc-process-docstring"` event. Configuration values
can be set globally for the inserted `try_examples` directives by setting the config values
`try_examples_global_button_css`, etc. as below. All valid config values are supported
by prepending `try_examples_global_`.

```python
global_enable_try_examples = True
try_examples_global_button_css = """
color: white;
background-color: #0054a6;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
"""
try_examples_global_button_hover_css = """
background-color: #0066cc;
transform: scale(1.02);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
"""

try_examples_global_button_text = "Try it in your browser!"
try_examples_global_min_height = "200px"
```

If an examples section already contains a `try_examples` directive, no additional
directives will be inserted, allowing for specific cases to be separately configured
if needed. Adding the comment `..! disable_try_examples` as the first non-empty line under
the section header for an examples section will prevent a directive from being inserted,
allowing for specification of examples sections which should not be made interactive.

## Other considerations
If you are using the `TryExamples` directive in your documentation, you'll need to ensure
that the version of the package installed in the Jupyterlite kernel you are using
matches that of the version you are documenting.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ directives/jupyterlite
directives/notebooklite
directives/replite
directives/voici
directives/try_examples
full
changelog
```
23 changes: 22 additions & 1 deletion jupyterlite_sphinx/_try_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def examples_to_notebook(input_lines):
Examples
--------
>>> from jupyterlite_sphinx.generate_notebook import examples_to_notebook
>>> from jupyterlite_sphinx._try_examples import examples_to_notebook
>>> input_lines = [
>>> "Add two numbers. This block of text will appear as a\n",
Expand Down Expand Up @@ -136,11 +136,28 @@ def _append_markdown_cell_and_clear_lines(markdown_lines, notebook):
# Convert blocks of LaTeX equations
markdown_text = _process_latex(markdown_text)
markdown_text = _strip_ref_identifiers(markdown_text)
markdown_text = _convert_links(markdown_text)
notebook.cells.append(new_markdown_cell(markdown_text))
markdown_lines.clear()


_ref_identifier_pattern = re.compile(r"\[R[a-f0-9]+-(?P<ref_num>\d+)\]_")
_link_pattern = re.compile(r"`(?P<link_text>[^`<]+)<(?P<url>[^`>]+)>`_")


def _convert_sphinx_link(match):
link_text = match.group("link_text").rstrip()
url = match.group("url")
return f"[{link_text}]({url})"


def _convert_links(md_text):
"""Convert sphinx style links to markdown style links
Sphinx style links have the form `link text <url>`_. Converts to
markdown format [link text](url).
"""
return _link_pattern.sub(_convert_sphinx_link, md_text)


def _strip_ref_identifiers(md_text):
Expand Down Expand Up @@ -192,6 +209,10 @@ def _process_latex(md_text):
equation_lines = []
wrapped_lines.append(line)

# Handle the case where the text ends with a math block
if in_math_block and equation_lines:
wrapped_lines.append(f"$$ {' '.join(equation_lines)} $$")

return "\n".join(wrapped_lines)


Expand Down
7 changes: 5 additions & 2 deletions jupyterlite_sphinx/jupyterlite_sphinx.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ window.jupyterliteConcatSearchParams = (iframeSrc, params) => {


window.tryExamplesShowIframe = (
examplesContainerId, iframeContainerId, iframeParentContainerId, iframeSrc
examplesContainerId, iframeContainerId, iframeParentContainerId, iframeSrc,
iframeMinHeight
) => {
const examplesContainer = document.getElementById(examplesContainerId);
const iframeParentContainer = document.getElementById(iframeParentContainerId);
Expand All @@ -55,7 +56,9 @@ window.tryExamplesShowIframe = (
iframe = document.createElement('iframe');
iframe.src = iframeSrc;
iframe.style.width = '100%';
iframe.style.height = `${examples.offsetHeight}px`;
minHeight = parseInt(iframeMinHeight);
height = Math.max(minHeight, examples.offsetHeight);
iframe.style.height = `${height}px`;
iframe.classList.add('jupyterlite_sphinx_raw_iframe');
examplesContainer.classList.add("hidden");
iframeContainer.appendChild(iframe);
Expand Down
Loading

0 comments on commit b153587

Please sign in to comment.