Skip to content

Commit b80c849

Browse files
authored
Merge pull request #132 from chrisjsewell/improve-download-rebase
Fix download role
2 parents 3cfddda + e8ed506 commit b80c849

File tree

10 files changed

+67
-27
lines changed

10 files changed

+67
-27
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
name: Build documentation
1919
command: |
2020
cd doc
21-
make html
21+
make html-strict
2222
2323
- store_artifacts:
2424
path: doc/build/html/

doc/Makefile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,11 @@ help:
1616
# Catch-all target: route all unknown targets to Sphinx using the new
1717
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
1818
%: Makefile
19-
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
19+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
20+
21+
# raise warnings to errors
22+
html-strict:
23+
@$(SPHINXBUILD) -b html -nW --keep-going "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O)
24+
25+
clean:
26+
rm -r $(BUILDDIR)

doc/source/index.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,18 @@ Pygments.
317317

318318
Downloading the code as a script
319319
--------------------------------
320+
320321
Jupyter Sphinx includes 2 roles that can be used to download the code embedded in a document:
321-
``:jupyter-download:script:`` (for a raw script file) and ``:jupyter-download:notebook:`` (for
322-
a Jupyter notebook). For example, to download all the code from this document as a script we
322+
``:jupyter-download:script:`` (for a raw script file) and ``:jupyter-download:notebook:`` or ``:jupyter-download:nb:`` (for
323+
a Jupyter notebook).
324+
325+
These roles are equivalent to the standard sphinx `download role <https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-download>`__, **except** the extension of the file should not be given.
326+
For example, to download all the code from this document as a script we
323327
would use::
324328

325-
:jupyter-download:script:`index`
329+
:jupyter-download:script:`click to download <index>`
326330

327-
Which produces a link like this: :jupyter-download:script:`index`. The name that the role is
331+
Which produces a link like this: :jupyter-download:script:`click to download <index>`. The target that the role is
328332
applied to (``index`` in this case) is the name of the document for which you wish to download
329333
the code. If a document contains ``jupyter-kernel`` directives with ``:id:`` specified, then
330334
the name provided to ``:id:`` can be used to get the code for the cells belonging to the

jupyter_sphinx/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
JupyterWidgetViewNode,
2020
JupyterWidgetStateNode,
2121
WIDGET_VIEW_MIMETYPE,
22-
jupyter_download_role,
22+
JupyterDownloadRole,
2323
CellOutputsToNodes,
2424
)
2525
from .execute import JupyterKernel, ExecuteJupyterCells
@@ -272,8 +272,9 @@ def setup(app):
272272
app.add_directive("jupyter-execute", JupyterCell)
273273
app.add_directive("jupyter-kernel", JupyterKernel)
274274
app.add_directive("thebe-button", ThebeButton)
275-
app.add_role("jupyter-download:notebook", jupyter_download_role)
276-
app.add_role("jupyter-download:script", jupyter_download_role)
275+
app.add_role("jupyter-download:notebook", JupyterDownloadRole())
276+
app.add_role("jupyter-download:nb", JupyterDownloadRole())
277+
app.add_role("jupyter-download:script", JupyterDownloadRole())
277278
app.add_transform(ExecuteJupyterCells)
278279
app.add_post_transform(CellOutputsToNodes)
279280

jupyter_sphinx/ast.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
import docutils
77
from docutils.parsers.rst import Directive, directives
8-
from docutils.nodes import math_block, image
8+
from docutils.nodes import math_block, image, literal
99
from sphinx.util import parselinenos
10+
from sphinx.util.docutils import ReferenceRole
1011
from sphinx.addnodes import download_reference
1112
from sphinx.transforms import SphinxTransform
1213
from sphinx.environment.collectors.asset import ImageCollector
@@ -393,16 +394,19 @@ def attach_outputs(output_nodes, node, thebe_config, cm_language):
393394
node.children = node.children[::-1]
394395

395396

396-
def jupyter_download_role(name, rawtext, text, lineno, inliner):
397-
_, filetype = name.split(":")
398-
assert filetype in ("notebook", "script")
399-
ext = ".ipynb" if filetype == "notebook" else ".py"
400-
output_dir = sphinx_abs_dir(inliner.document.settings.env)
401-
download_file = text + ext
402-
node = download_reference(
403-
download_file, download_file, reftarget=os.path.join(output_dir, download_file)
404-
)
405-
return [node], []
397+
class JupyterDownloadRole(ReferenceRole):
398+
def run(self):
399+
_, filetype = self.name.split(":")
400+
401+
assert filetype in ("notebook", "nb", "script")
402+
ext = ".ipynb" if filetype in ("notebook", "nb") else ".py"
403+
download_file = self.target + ext
404+
reftarget = sphinx_abs_dir(self.env, download_file)
405+
node = download_reference(self.rawtext, reftarget=reftarget)
406+
self.set_source_info(node)
407+
title = self.title if self.has_explicit_title else download_file
408+
node += literal(self.rawtext, title, classes=["xref", "download"])
409+
return [node], []
406410

407411

408412
def get_widgets(notebook):

jupyter_sphinx/execute.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
default_notebook_names,
2323
output_directory,
2424
split_on,
25-
sphinx_abs_dir,
2625
blank_nb,
2726
)
2827
from .ast import (

jupyter_sphinx/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,14 @@ def language_info(executor):
6969
return info_msg["content"]["language_info"]
7070

7171

72-
def sphinx_abs_dir(env):
72+
def sphinx_abs_dir(env, *paths):
7373
# We write the output files into
7474
# output_directory / jupyter_execute / path relative to source directory
7575
# Sphinx expects download links relative to source file or relative to
7676
# source dir and prepended with '/'. We use the latter option.
7777
return "/" + os.path.relpath(
7878
os.path.abspath(
79-
os.path.join(output_directory(env), os.path.dirname(env.docname))
79+
os.path.join(output_directory(env), os.path.dirname(env.docname), *paths)
8080
),
8181
os.path.abspath(env.app.srcdir),
8282
)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
sphinx==1.8.5
1+
sphinx==2.4.4
22
ipywidgets>=7.0.0
33
IPython
44
nbconvert>=5.4

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
license="BSD",
2929
packages=["jupyter_sphinx"],
3030
install_requires=[
31-
"Sphinx>=1.8",
31+
"Sphinx>=2",
3232
"ipywidgets>=7.0.0",
3333
"IPython",
3434
"nbconvert>=5.5",

tests/test_execute.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import os
44
import sys
55
from io import StringIO
6+
from unittest.mock import Mock
67

7-
from sphinx.testing.util import SphinxTestApp, path
8+
from sphinx.addnodes import download_reference
9+
from sphinx.testing.util import assert_node, SphinxTestApp, path
810
from sphinx.errors import ExtensionError
9-
from docutils.nodes import raw
11+
from docutils.nodes import literal, raw
1012
from nbformat import from_dict
1113

1214
import pytest
@@ -18,6 +20,7 @@
1820
JupyterWidgetViewNode,
1921
JupyterWidgetStateNode,
2022
cell_output_to_nodes,
23+
JupyterDownloadRole,
2124
)
2225
from jupyter_sphinx.thebelab import ThebeSourceNode, ThebeOutputNode, ThebeButtonNode
2326

@@ -564,3 +567,25 @@ def test_image_mimetype_uri(doctree):
564567
cell = from_dict(cell)
565568
output_node = cell_output_to_nodes(cell["outputs"], priority, True, output_dir, None)
566569
assert output_node[0].attributes['uri'] == img_locs[index]
570+
571+
572+
@pytest.mark.parametrize('text,reftarget,caption', (
573+
('nb_name', '/../jupyter_execute/path/to/nb_name.ipynb', 'nb_name.ipynb'),
574+
('../nb_name', '/../jupyter_execute/path/nb_name.ipynb', '../nb_name.ipynb'),
575+
('text <nb_name>', '/../jupyter_execute/path/to/nb_name.ipynb', 'text'),
576+
))
577+
def test_download_role(text, reftarget, caption, tmp_path):
578+
role = JupyterDownloadRole()
579+
mock_inliner = Mock()
580+
config = {
581+
'document.settings.env.app.outdir': str(tmp_path),
582+
'document.settings.env.docname': 'path/to/docname',
583+
'document.settings.env.srcdir': str(tmp_path),
584+
'document.settings.env.app.srcdir': str(tmp_path),
585+
'reporter.get_source_and_line': lambda l: ('source', l)
586+
}
587+
mock_inliner.configure_mock(**config)
588+
ret, msg = role('jupyter-download:notebook', text, text, 0, mock_inliner)
589+
assert_node(ret[0], [download_reference], reftarget=reftarget)
590+
assert_node(ret[0][0], [literal, caption])
591+
assert msg == []

0 commit comments

Comments
 (0)