Skip to content

Commit b153587

Browse files
authored
Add more configuration options to TryExamples directive and add documentation (#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
1 parent e664027 commit b153587

File tree

6 files changed

+294
-28
lines changed

6 files changed

+294
-28
lines changed

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
extensions = [
4+
'sphinx.ext.mathjax',
45
'jupyterlite_sphinx',
56
'myst_parser',
67
]

docs/directives/try_examples.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# TryExamples directive
2+
3+
`jupyterlite-sphinx` provides the experimental `try_examples` directive which allows
4+
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.
5+
6+
7+
```rst
8+
Examples
9+
--------
10+
.. try_examples::
11+
:button_css:
12+
background-color: #f7dc1e;
13+
border: none;
14+
padding: 5px 10px;
15+
border-radius: 15px;
16+
font-family: vibur;
17+
font-size: x-large;
18+
box-shadow: 0 2px 5px rgba(108,108,108,0.2);
19+
:button_hover_css:
20+
background-color: #fff221;
21+
transform: scale(1.02);
22+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
23+
cursor: pointer;
24+
:button_horizontal_position: right
25+
:button_vertical_position: top
26+
:button_text: Try it in a classic notebook!
27+
:min_height: 200px
28+
29+
30+
Doctest examples sections are parsed and converted to notebooks. Blocks of text
31+
like this become markdown cells. Codeblocks begin with `>>>`. Contiguous blocks
32+
of code are combined into a single code cell.
33+
34+
>>> x = 2
35+
>>> y = 2
36+
>>> x + y
37+
4
38+
39+
`...` is used to continue multiline statements.
40+
41+
>>> def f(x, y):
42+
... return x + y
43+
>>> f(2, 2)
44+
4
45+
46+
Inline LaTeX like :math:`x + y = 4` is converted, as is block LaTeX like
47+
48+
.. math::
49+
50+
\int_{x=-\infty}^{\infty}e^{-x^2}\mathrm{d}x = \sqrt{\pi}
51+
52+
If you are displaying `math output <https://www.sphinx-doc.org/en/master/usage/extensions/math.html>`_
53+
with sphinx. Sphinx links such as the one in the previous sentence are also converted to
54+
markdown format.
55+
```
56+
57+
58+
```{eval-rst}
59+
Examples
60+
--------
61+
.. try_examples::
62+
:button_css:
63+
background-color: #f7dc1e;
64+
border: none;
65+
padding: 5px 10px;
66+
border-radius: 15px;
67+
font-family: vibur;
68+
font-size: x-large;
69+
box-shadow: 0 2px 5px rgba(108,108,108,0.2);
70+
:button_hover_css:
71+
background-color: #fff221;
72+
transform: scale(1.02);
73+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
74+
cursor: pointer;
75+
:button_horizontal_position: right
76+
:button_vertical_position: top
77+
:button_text: Try it in a classic notebook!
78+
:min_height: 200px
79+
80+
Doctest examples sections are parsed and converted to notebooks. Blocks of text
81+
like this become markdown cells. Codeblocks begin with `>>>`. Contiguous blocks
82+
of code are combined into a single code cell.
83+
84+
>>> x = 2
85+
>>> y = 2
86+
>>> x + y
87+
4
88+
89+
`...` is used to continue multiline statements.
90+
91+
>>> def f(x, y):
92+
... return x + y
93+
>>> f(2, 2)
94+
4
95+
96+
Inline LaTeX like :math:`x + y = 4` is converted, as is block LaTeX like
97+
98+
.. math::
99+
100+
\int_{-\infty}^{\infty}e^{-x^2}\mathrm{d}x = \sqrt{\pi}
101+
102+
If you are displaying `math output <https://www.sphinx-doc.org/en/master/usage/extensions/math.html>`_
103+
with sphinx. Sphinx links such as the one in the previous sentence are also converted to
104+
markdown format.
105+
```
106+
107+
## Configuration
108+
109+
The button's text, position, and style can be configured to match your page design. The
110+
text can be configured with the option `:button_text:`. The options `:button_css:` and
111+
`:button_hover_css:` take lists of css properties as in the example above, and
112+
apply them to the button. `:button_horizontal_position:` can be one of `left`, `right`, or
113+
`center` and `:button_vertical_position:` can be one of `top` or `bottom`. The position
114+
is with respect to the rendered examples block / embedded notebook
115+
(depending on which is active).
116+
117+
The height of the embedded notebook's iframe container is calculated to match the height
118+
of the rendered doctest examples so that it takes up the same amount of space on the
119+
page. The `:min_height:` option can be used to ensure that the embedded notebook will not
120+
be unuseably small for very short examples blocks, though its use can cause the contents
121+
of the page to shift when the button is pressed.
122+
123+
the `:theme:` option available for other `jupyterlite-sphinx` directives is also
124+
available.
125+
126+
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
127+
can set the option
128+
129+
```python
130+
global_enable_try_examples = True
131+
```
132+
133+
in your sphinx `conf.py` in order to automatically insert the `try_examples` directive
134+
in examples sections during the `"autodoc-process-docstring"` event. Configuration values
135+
can be set globally for the inserted `try_examples` directives by setting the config values
136+
`try_examples_global_button_css`, etc. as below. All valid config values are supported
137+
by prepending `try_examples_global_`.
138+
139+
```python
140+
global_enable_try_examples = True
141+
try_examples_global_button_css = """
142+
color: white;
143+
background-color: #0054a6;
144+
border: none;
145+
padding: 5px 10px;
146+
border-radius: 5px;
147+
cursor: pointer;
148+
"""
149+
try_examples_global_button_hover_css = """
150+
background-color: #0066cc;
151+
transform: scale(1.02);
152+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
153+
"""
154+
155+
try_examples_global_button_text = "Try it in your browser!"
156+
try_examples_global_min_height = "200px"
157+
```
158+
159+
If an examples section already contains a `try_examples` directive, no additional
160+
directives will be inserted, allowing for specific cases to be separately configured
161+
if needed. Adding the comment `..! disable_try_examples` as the first non-empty line under
162+
the section header for an examples section will prevent a directive from being inserted,
163+
allowing for specification of examples sections which should not be made interactive.
164+
165+
## Other considerations
166+
If you are using the `TryExamples` directive in your documentation, you'll need to ensure
167+
that the version of the package installed in the Jupyterlite kernel you are using
168+
matches that of the version you are documenting.

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ directives/jupyterlite
4040
directives/notebooklite
4141
directives/replite
4242
directives/voici
43+
directives/try_examples
4344
full
4445
changelog
4546
```

jupyterlite_sphinx/_try_examples.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def examples_to_notebook(input_lines):
1818
1919
Examples
2020
--------
21-
>>> from jupyterlite_sphinx.generate_notebook import examples_to_notebook
21+
>>> from jupyterlite_sphinx._try_examples import examples_to_notebook
2222
2323
>>> input_lines = [
2424
>>> "Add two numbers. This block of text will appear as a\n",
@@ -136,11 +136,28 @@ def _append_markdown_cell_and_clear_lines(markdown_lines, notebook):
136136
# Convert blocks of LaTeX equations
137137
markdown_text = _process_latex(markdown_text)
138138
markdown_text = _strip_ref_identifiers(markdown_text)
139+
markdown_text = _convert_links(markdown_text)
139140
notebook.cells.append(new_markdown_cell(markdown_text))
140141
markdown_lines.clear()
141142

142143

143144
_ref_identifier_pattern = re.compile(r"\[R[a-f0-9]+-(?P<ref_num>\d+)\]_")
145+
_link_pattern = re.compile(r"`(?P<link_text>[^`<]+)<(?P<url>[^`>]+)>`_")
146+
147+
148+
def _convert_sphinx_link(match):
149+
link_text = match.group("link_text").rstrip()
150+
url = match.group("url")
151+
return f"[{link_text}]({url})"
152+
153+
154+
def _convert_links(md_text):
155+
"""Convert sphinx style links to markdown style links
156+
157+
Sphinx style links have the form `link text <url>`_. Converts to
158+
markdown format [link text](url).
159+
"""
160+
return _link_pattern.sub(_convert_sphinx_link, md_text)
144161

145162

146163
def _strip_ref_identifiers(md_text):
@@ -192,6 +209,10 @@ def _process_latex(md_text):
192209
equation_lines = []
193210
wrapped_lines.append(line)
194211

212+
# Handle the case where the text ends with a math block
213+
if in_math_block and equation_lines:
214+
wrapped_lines.append(f"$$ {' '.join(equation_lines)} $$")
215+
195216
return "\n".join(wrapped_lines)
196217

197218

jupyterlite_sphinx/jupyterlite_sphinx.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ window.jupyterliteConcatSearchParams = (iframeSrc, params) => {
4242

4343

4444
window.tryExamplesShowIframe = (
45-
examplesContainerId, iframeContainerId, iframeParentContainerId, iframeSrc
45+
examplesContainerId, iframeContainerId, iframeParentContainerId, iframeSrc,
46+
iframeMinHeight
4647
) => {
4748
const examplesContainer = document.getElementById(examplesContainerId);
4849
const iframeParentContainer = document.getElementById(iframeParentContainerId);
@@ -55,7 +56,9 @@ window.tryExamplesShowIframe = (
5556
iframe = document.createElement('iframe');
5657
iframe.src = iframeSrc;
5758
iframe.style.width = '100%';
58-
iframe.style.height = `${examples.offsetHeight}px`;
59+
minHeight = parseInt(iframeMinHeight);
60+
height = Math.max(minHeight, examples.offsetHeight);
61+
iframe.style.height = `${height}px`;
5962
iframe.classList.add('jupyterlite_sphinx_raw_iframe');
6063
examplesContainer.classList.add("hidden");
6164
iframeContainer.appendChild(iframe);

0 commit comments

Comments
 (0)