Skip to content

Commit

Permalink
Handle links to local files
Browse files Browse the repository at this point in the history
  • Loading branch information
mgeier committed Jan 20, 2016
1 parent 488b41a commit 9579f2b
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 37 deletions.
17 changes: 17 additions & 0 deletions doc/markdown-cells.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,23 @@
"[beginning of this section](#Links-to-Other-Notebooks)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Links to Local Files\n",
"\n",
"Links to local files (other than Jupyter notebooks) are also possible, e.g. [requirements.txt](requirements.txt).\n",
"\n",
"This was simply created with:\n",
"\n",
"```\n",
"[requirements.txt](requirements.txt)\n",
"```\n",
"\n",
"The linked files are automatically copied to the HTML output directory."
]
}
],
"metadata": {
Expand Down
4 changes: 2 additions & 2 deletions doc/orphan.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# An Orphan Notebook\n",
"# An Orphan Notebook (HTML Only)\n",
"\n",
"This means that is doesn't appear in the Sphinx toctree (see `doc/index.rst`), but other pages can still link to it ...\n",
"This means that is doesn't appear in the Sphinx toctree (see [index.rst](index.rst)), but other pages can still link to it ...\n",
"\n",
"* ... from a Markdown cell of another notebook using\n",
"\n",
Expand Down
6 changes: 4 additions & 2 deletions doc/subdir/another.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"A link to a notebook in the parent directory: [link](../example.ipynb)\n",
"A link to a notebook in the parent directory: [link](../markdown-cells.ipynb).\n",
"\n",
"A link to a local file: [link](../images/notebook_icon.png).\n",
"\n",
"## A Sub-Section\n",
"\n",
"This is for testing inter-notebook links, see [this section](../example.ipynb#Links-to-Other-Notebooks)."
"This is for testing inter-notebook links, see [this section](../markdown-cells.ipynb#Links-to-Other-Notebooks)."
]
}
],
Expand Down
6 changes: 3 additions & 3 deletions doc/usage.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"Answer the questions that appear on the screen. In case of doubt, just press the `<Return>` key to take the default values.\n",
"\n",
"After that, there will be a few brand-new files in the current directory.\n",
"You'll have to make a few changes to the file `conf.py`. You should at least check if those two variables contain the right things:\n",
"You'll have to make a few changes to the file named [conf.py](conf.py). You should at least check if those two variables contain the right things:\n",
"\n",
"```python\n",
"extensions = [\n",
Expand All @@ -38,7 +38,7 @@
"exclude_patterns = ['_build', '**.ipynb_checkpoints']\n",
"```\n",
"\n",
"Once your `conf.py` is in place, edit the file `index.rst` and add the file names of your notebooks (without the `.ipynb` extension) to the `toctree` directive."
"Once your `conf.py` is in place, edit the file named [index.rst](index.rst) and add the file names of your notebooks (without the `.ipynb` extension) to the `toctree` directive."
]
},
{
Expand Down Expand Up @@ -79,7 +79,7 @@
"\n",
"1. Create an account on https://readthedocs.org/ and add your Github/Bitbucket repository (or any publicly available Git/Subversion/Mercurial/Bazaar repository).\n",
"\n",
"1. Create a file named `requirements.txt` (or whatever name you wish) in your repository containing the required pip packages:\n",
"1. Create a file named [requirements.txt](requirements.txt) (or whatever name you wish) in your repository containing the required pip packages:\n",
"\n",
" nbsphinx\n",
" ipykernel\n",
Expand Down
109 changes: 79 additions & 30 deletions nbsphinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ class NotebookParser(rst.Parser):

def get_transforms(self):
"""List of transforms for documents parsed by this parser."""
return rst.Parser.get_transforms(self) + [RewriteNotebookLinks,
return rst.Parser.get_transforms(self) + [ProcessLocalLinks,
CreateSectionLabels]

def parse(self, inputstring, document):
Expand Down Expand Up @@ -485,8 +485,11 @@ def _set_emtpy_lines(node, options):
node.attributes[attr] = value


class RewriteNotebookLinks(docutils.transforms.Transform):
"""Turn links to local notebooks into ``:doc:``/``:ref:`` links.
class ProcessLocalLinks(docutils.transforms.Transform):
"""Process links to local files.
Marks local files to be copied to the HTML output directory and
turns links to local notebooks into ``:doc:``/``:ref:`` links.
Links to subsections are possible with ``...#Subsection-Title``.
These links use the labels created by CreateSectionLabels.
Expand All @@ -510,38 +513,58 @@ def apply(self):
env = self.document.settings.env
for node in self.document.traverse(docutils.nodes.reference):
uri = node.get('refuri', '')
if '://' not in uri:
if uri.lower().endswith('.ipynb'):
target = uri[:-len('.ipynb')]
target_ext = ''
reftype = 'doc'
refdomain = None
elif '.ipynb#' in uri.lower():
idx = uri.lower().find('.ipynb#')
target = uri[:idx]
target_ext = uri[idx:]
reftype = 'ref'
refdomain = 'std'
else:
continue # Not a local notebook
target_docname = os.path.normpath(
os.path.join(os.path.dirname(env.docname), target))
if target_docname in env.found_docs:
if target_ext:
target = target_docname + target_ext
target = target.lower()
linktext = node.astext()
xref = sphinx.addnodes.pending_xref(
reftype=reftype, reftarget=target, refdomain=refdomain,
refwarn=True, refexplicit=True, refdoc=env.docname)
xref += docutils.nodes.Text(linktext, linktext)
node.replace_self(xref)
if not uri:
continue # No URI (e.g. named reference)
elif '://' in uri:
continue # Not a local link
elif uri.startswith('#'):
continue # Nothing to be done
elif uri.lower().endswith('.ipynb'):
target = uri[:-len('.ipynb')]
target_ext = ''
reftype = 'doc'
refdomain = None
elif '.ipynb#' in uri.lower():
idx = uri.lower().find('.ipynb#')
target = uri[:idx]
target_ext = uri[idx:]
reftype = 'ref'
refdomain = 'std'
else:
file = os.path.normpath(
os.path.join(os.path.dirname(env.docname), uri))
if not os.path.isfile(os.path.join(env.srcdir, file)):
env.app.warn('file not found: {!r}'.format(file),
env.doc2path(env.docname))
continue # Link is ignored
elif file.startswith('..'):
env.app.warn(
'link outside of source directory: {!r}'.format(file),
env.doc2path(env.docname))
continue # Link is ignored
if not hasattr(env, 'nbsphinx_files'):
env.nbsphinx_files = {}
env.nbsphinx_files.setdefault(env.docname, []).append(file)
continue # We're done here

target_docname = os.path.normpath(
os.path.join(os.path.dirname(env.docname), target))
if target_docname in env.found_docs:
if target_ext:
target = target_docname + target_ext
target = target.lower()
linktext = node.astext()
xref = sphinx.addnodes.pending_xref(
reftype=reftype, reftarget=target, refdomain=refdomain,
refwarn=True, refexplicit=True, refdoc=env.docname)
xref += docutils.nodes.Text(linktext, linktext)
node.replace_self(xref)


class CreateSectionLabels(docutils.transforms.Transform):
"""Make labels for each notebook and each section thereof.
These labels are referenced in RewriteNotebookLinks.
These labels are referenced in ProcessLocalLinks.
Note: Sphinx lower-cases the HTML section IDs, Jupyter doesn't.
"""
Expand Down Expand Up @@ -591,6 +614,30 @@ def html_page_context(app, pagename, templatename, context, doctree):
context['body'] = style + body


def html_collect_pages(app):
"""This event handler is abused to copy local files around."""
files = set()
for file_list in getattr(app.env, 'nbsphinx_files', {}).values():
files.update(file_list)
for file in app.status_iterator(files, 'copying linked files... ',
sphinx.util.console.brown, len(files)):
target = os.path.join(app.builder.outdir, file)
sphinx.util.ensuredir(os.path.dirname(target))
try:
sphinx.util.copyfile(os.path.join(app.env.srcdir, file), target)
except OSError as err:
app.warn('cannot copy local file {!r}: {}'.format(file, err))
return [] # No new HTML pages are created


def env_purge_doc(app, env, docname):
"""Remove list of local files for a given document."""
try:
del env.nbsphinx_files[docname]
except (AttributeError, KeyError):
pass


def depart_code_html(self, node):
"""Add empty lines before and after the code."""
text = self.body[-1]
Expand Down Expand Up @@ -684,5 +731,7 @@ def setup(app):
latex=(visit_code_latex, depart_code_latex))
app.connect('builder-inited', builder_inited)
app.connect('html-page-context', html_page_context)
app.connect('html-collect-pages', html_collect_pages)
app.connect('env-purge-doc', env_purge_doc)

return {'version': __version__, 'parallel_read_safe': True}

0 comments on commit 9579f2b

Please sign in to comment.