From b3d961c1390d226fa522d4e4221ee261d6060e52 Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Thu, 31 Mar 2016 19:32:23 -0400 Subject: [PATCH 1/7] Add 'nbinclude' reST directive --- nbsphinx.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/nbsphinx.py b/nbsphinx.py index 2104d381..286f0e73 100644 --- a/nbsphinx.py +++ b/nbsphinx.py @@ -27,7 +27,9 @@ import copy import docutils +from docutils import io from docutils.parsers import rst +from docutils.parsers.rst import directives import jinja2 import nbconvert import nbformat @@ -541,6 +543,50 @@ def run(self): return [container] +class NbInclude(rst.Directive): + """Include a notebook in a reST document. + + This directive uses :class:`NotebookParser`, which means that evaluated + notebooks will be included without modification, while notebooks with + no output cells will be evaluated before being included. + + """ + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + if not self.state.document.settings.file_insertion_enabled: + raise self.warning('"%s" directive disabled.' % self.name) + + # Read the notebook to a string + path = directives.path(self.arguments[0]) + rst_file = self.state_machine.document.attributes['source'] + rst_dir = os.path.abspath(os.path.dirname(rst_file)) + path = os.path.normpath(os.path.join(rst_dir, path)) + e_handler = self.state.document.settings.input_encoding_error_handler + try: + self.state.document.settings.record_dependencies.add(path) + include_file = io.FileInput(source_path=path, + encoding='utf-8', + error_handler=e_handler) + except IOError as error: + raise self.severe(u'Problems with "%s" directive path:\n%s.' % + (self.name, error)) + try: + rawtext = include_file.read() + except UnicodeError as error: + raise self.severe(u'Problem with "%s" directive:\n%s' % + (self.name, error)) + + # Use the NotebookParser to convert to reST and evaluate if needed + nbparser = NotebookParser() + node = docutils.utils.new_document(path, self.state.document.settings) + nbparser.parse(rawtext, node) + return node.children + + def _extract_toctree(cell): """Extract links from Markdown cell and create toctree.""" lines = ['.. toctree::'] @@ -848,6 +894,7 @@ def setup(app): app.add_directive('nbinput', NbInput) app.add_directive('nboutput', NbOutput) + app.add_directive('nbinclude', NbInclude) app.add_node(CodeNode, html=(do_nothing, depart_code_html), latex=(visit_code_latex, depart_code_latex)) From 3171b8764dafe2efc15ffe6790335a75737a92a3 Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Fri, 8 Apr 2016 11:56:50 -0400 Subject: [PATCH 2/7] FIXUP: incorporate feedback on directive --- nbsphinx.py | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/nbsphinx.py b/nbsphinx.py index 286f0e73..67c5689e 100644 --- a/nbsphinx.py +++ b/nbsphinx.py @@ -26,10 +26,9 @@ __version__ = '0.2.5' import copy +import io import docutils -from docutils import io from docutils.parsers import rst -from docutils.parsers.rst import directives import jinja2 import nbconvert import nbformat @@ -392,7 +391,8 @@ class NotebookParser(rst.Parser): Uses nbsphinx.Exporter to convert notebook content to a reStructuredText string, which is then parsed by Sphinx's built-in - reST parser. + reST parser. Evaluated notebooks will be included without modification, + while notebooks with no output will be evaluated during parsing. """ @@ -546,44 +546,41 @@ def run(self): class NbInclude(rst.Directive): """Include a notebook in a reST document. - This directive uses :class:`NotebookParser`, which means that evaluated - notebooks will be included without modification, while notebooks with - no output cells will be evaluated before being included. + This directive uses :class:`NotebookParser` so that included notebooks + will be treated the same as those included in toctrees. """ required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True option_spec = {} + has_content = False def run(self): - if not self.state.document.settings.file_insertion_enabled: - raise self.warning('"%s" directive disabled.' % self.name) + settings = self.state.document.settings + + if not settings.file_insertion_enabled: + raise self.warning('"nbinclude" directive disabled.') # Read the notebook to a string - path = directives.path(self.arguments[0]) + path = rst.directives.path(self.arguments[0]) rst_file = self.state_machine.document.attributes['source'] rst_dir = os.path.abspath(os.path.dirname(rst_file)) path = os.path.normpath(os.path.join(rst_dir, path)) - e_handler = self.state.document.settings.input_encoding_error_handler - try: - self.state.document.settings.record_dependencies.add(path) - include_file = io.FileInput(source_path=path, - encoding='utf-8', - error_handler=e_handler) - except IOError as error: - raise self.severe(u'Problems with "%s" directive path:\n%s.' % - (self.name, error)) try: - rawtext = include_file.read() - except UnicodeError as error: - raise self.severe(u'Problem with "%s" directive:\n%s' % - (self.name, error)) - - # Use the NotebookParser to convert to reST and evaluate if needed + settings.record_dependencies.add(path) + with io.open(path, encoding='utf-8') as f: + rawtext = f.read() + except (IOError, UnicodeError) as error: + raise self.severe( + u'Problems with "nbinclude" directive:\n%s.' % error) + + # Use the NotebookParser to get doctree nodes nbparser = NotebookParser() - node = docutils.utils.new_document(path, self.state.document.settings) + node = docutils.utils.new_document(path, settings) nbparser.parse(rawtext, node) + if isinstance(node.children[0], docutils.nodes.field_list): + return node.children[1:] return node.children From 1b5c2f02481ee1f3821a6ad9b9476922d2361055 Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Tue, 5 Apr 2016 11:55:45 -0400 Subject: [PATCH 3/7] Add example using the `nbinclude` directive The added notebook is included in the `rst.rst` document, but not added to any toctrees. --- doc/rst.rst | 20 +++++++++++++ doc/subdir2/included.ipynb | 59 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 doc/subdir2/included.ipynb diff --git a/doc/rst.rst b/doc/rst.rst index 0cf02a80..6be107f3 100644 --- a/doc/rst.rst +++ b/doc/rst.rst @@ -36,6 +36,26 @@ These links were created with: * "``../``" is not allowed, you have to specify the full path even if the current source file is in a subdirectory! +Sphinx Directive for Including Notebooks +---------------------------------------- + +Notebooks can also be included directly in an RST file +using the ``nbinclude`` directive. +Below, we include :ref:`subdir2/included.ipynb`. + +.. nbinclude:: subdir2/included.ipynb + +The notebook was included with: + +.. code-block:: rst + + .. nbinclude:: subdir2/included.ipynb + +.. note:: + + * "``../``" is allowed here. In fact, you can include notebooks that are + outside of the documentation directory! + Sphinx Directives for Jupyter Notebook Cells -------------------------------------------- diff --git a/doc/subdir2/included.ipynb b/doc/subdir2/included.ipynb new file mode 100644 index 00000000..bc3af168 --- /dev/null +++ b/doc/subdir2/included.ipynb @@ -0,0 +1,59 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "nbsphinx": "hidden" + }, + "source": [ + "This notebook is part of the `nbsphinx` documentation: http://nbsphinx.readthedocs.org/." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### An Included Notebook\n", + "\n", + "This notebook is included with the `nbinclude` directive.\n", + "\n", + "The cell below is stored unexecuted, but will be executed when included, just like notebooks included in a `:toctree:`. Notebooks with at least one executed cell will not be executed when included." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "6 * 7" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.1+" + }, + "nbsphinx": { + "orphan": true + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From b1255d18fbf877c8e2b0195c38b4d458ab3a55e0 Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Fri, 8 Apr 2016 18:21:20 -0400 Subject: [PATCH 4/7] Avoid orphan warning if notebook is included This is a bit of a hack, but it means that notebooks no longer need to have the `orphan: true` metadata specified for any notebook that is in the doc directory and is included through the `nbinclude` directive, but is not in any toctrees. --- nbsphinx.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nbsphinx.py b/nbsphinx.py index 67c5689e..3b884a71 100644 --- a/nbsphinx.py +++ b/nbsphinx.py @@ -575,6 +575,13 @@ def run(self): raise self.severe( u'Problems with "nbinclude" directive:\n%s.' % error) + # Add the docname to the `files_to_rebuild` dictionary, + # which maps from files to files that include that file in a toctree. + # This suppresses the warning that is usually raised when a file + # is not in any toctree. + name, _ = os.path.splitext(os.path.relpath(path, settings.env.srcdir)) + settings.env.files_to_rebuild.setdefault(name, set()) + # Use the NotebookParser to get doctree nodes nbparser = NotebookParser() node = docutils.utils.new_document(path, settings) From 387df6f7991bc3d81e17b89dab3be0c654c6d732 Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Sat, 9 Apr 2016 08:07:32 -0400 Subject: [PATCH 5/7] Execute included notebook from its directory --- doc/subdir2/included.ipynb | 3 --- nbsphinx.py | 7 ++++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/subdir2/included.ipynb b/doc/subdir2/included.ipynb index bc3af168..e7f1c25b 100644 --- a/doc/subdir2/included.ipynb +++ b/doc/subdir2/included.ipynb @@ -49,9 +49,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.1+" - }, - "nbsphinx": { - "orphan": true } }, "nbformat": 4, diff --git a/nbsphinx.py b/nbsphinx.py index 3b884a71..fc0bfad3 100644 --- a/nbsphinx.py +++ b/nbsphinx.py @@ -401,11 +401,12 @@ def get_transforms(self): return rst.Parser.get_transforms(self) + [ProcessLocalLinks, CreateSectionLabels] - def parse(self, inputstring, document): + def parse(self, inputstring, document, srcdir=None): """Parse `inputstring`, write results to `document`.""" nb = nbformat.reads(inputstring, as_version=_ipynbversion) env = document.settings.env - srcdir = os.path.dirname(env.doc2path(env.docname)) + if srcdir is None: + srcdir = os.path.dirname(env.doc2path(env.docname)) auxdir = os.path.join(env.doctreedir, 'nbsphinx') sphinx.util.ensuredir(auxdir) @@ -585,7 +586,7 @@ def run(self): # Use the NotebookParser to get doctree nodes nbparser = NotebookParser() node = docutils.utils.new_document(path, settings) - nbparser.parse(rawtext, node) + nbparser.parse(rawtext, node, srcdir=os.path.dirname(path)) if isinstance(node.children[0], docutils.nodes.field_list): return node.children[1:] return node.children From 422f65d27a53112294d2e80ecad29a2f3fb74865 Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Sat, 9 Apr 2016 08:07:57 -0400 Subject: [PATCH 6/7] FIXUP: Move nbinclude to bottom of rst.rst --- doc/rst.rst | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/rst.rst b/doc/rst.rst index 6be107f3..d182d3a6 100644 --- a/doc/rst.rst +++ b/doc/rst.rst @@ -36,26 +36,6 @@ These links were created with: * "``../``" is not allowed, you have to specify the full path even if the current source file is in a subdirectory! -Sphinx Directive for Including Notebooks ----------------------------------------- - -Notebooks can also be included directly in an RST file -using the ``nbinclude`` directive. -Below, we include :ref:`subdir2/included.ipynb`. - -.. nbinclude:: subdir2/included.ipynb - -The notebook was included with: - -.. code-block:: rst - - .. nbinclude:: subdir2/included.ipynb - -.. note:: - - * "``../``" is allowed here. In fact, you can include notebooks that are - outside of the documentation directory! - Sphinx Directives for Jupyter Notebook Cells -------------------------------------------- @@ -93,3 +73,23 @@ This was created with :execution-count: 42 42 + +Sphinx Directive for Including Notebooks +---------------------------------------- + +Notebooks can be included directly in an RST file +using the ``nbinclude`` directive. +Below, we include :ref:`subdir2/included.ipynb`. + +.. nbinclude:: subdir2/included.ipynb + +The notebook was included with: + +.. code-block:: rst + + .. nbinclude:: subdir2/included.ipynb + +.. note:: + + * "``../``" is allowed here. In fact, you can include notebooks that are + outside of the documentation directory! From 631852665671f046c511761525b41623d4c2905a Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Sat, 9 Apr 2016 09:13:28 -0400 Subject: [PATCH 7/7] FIXUP: add more things to the notebook --- doc/subdir2/included.ipynb | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/doc/subdir2/included.ipynb b/doc/subdir2/included.ipynb index e7f1c25b..ec016b83 100644 --- a/doc/subdir2/included.ipynb +++ b/doc/subdir2/included.ipynb @@ -17,7 +17,11 @@ "\n", "This notebook is included with the `nbinclude` directive.\n", "\n", - "The cell below is stored unexecuted, but will be executed when included, just like notebooks included in a `:toctree:`. Notebooks with at least one executed cell will not be executed when included." + "The cell below is stored unexecuted,\n", + "but will be executed when included,\n", + "just like notebooks included in a ``toctree``.\n", + "Notebooks with at least one executed cell will not be executed when included\n", + "(see [A Pre-Executed Notebook](../pre-executed.ipynb) for more details)." ] }, { @@ -30,6 +34,37 @@ "source": [ "6 * 7" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Local images work like notebooks in `toctree`s:\n", + "![Jupyter notebook icon](../images/notebook_icon.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import Image\n", + "Image(filename='../images/notebook_icon.png')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A link to a notebook in the parent directory: [link](../markdown-cells.ipynb).\n", + "\n", + "A link to a specific section in that notebook: [link](../markdown-cells.ipynb#Links-to-Other-Notebooks).\n", + "\n", + "A link to a local file: [link](../images/notebook_icon.png)." + ] } ], "metadata": {