"""
adapted from nbsphinx
"""
import re
import os
try:
from urllib.parse import unquote # Python 3.x
except ImportError:
from urllib2 import unquote # Python 2.x
import docutils
import nbconvert
from ipypublish.sphinx.utils import import_sphinx
[docs]class CreateNotebookSectionAnchors(docutils.transforms.Transform):
"""Create section anchors for Jupyter notebooks.
Note: Sphinx lower-cases the HTML section IDs, Jupyter doesn't.
This transform creates anchors in the Jupyter style.
# TODO do this in pandoc filter?
"""
default_priority = 200 # Before CreateSectionLabels (250)
[docs] def apply(self):
for section in self.document.traverse(docutils.nodes.section):
title = section.children[0].astext()
link_id = title.replace(" ", "-")
section["ids"] = [link_id]
def _local_file_from_reference(node, document):
"""Get local file path from reference and split it into components."""
# NB: Anonymous hyperlinks must be already resolved at this point!
refuri = node.get("refuri")
if not refuri:
refname = node.get("refname")
if refname:
refid = document.nameids.get(refname)
else:
# NB: This can happen for anonymous hyperlinks
refid = node.get("refid")
target = document.ids.get(refid)
if not target:
# No corresponding target, Sphinx may warn later
return "", "", ""
refuri = target.get("refuri")
if not refuri:
# Target doesn't have URI
return "", "", ""
if "://" in refuri:
# Not a local link
return "", "", ""
elif refuri.startswith("#") or refuri.startswith("mailto:"):
# Not a local link
return "", "", ""
# NB: We look for "fragment identifier" before unquoting
match = re.match(r"^([^#]+)(\.[^#]+)(#.+)$", refuri)
if match:
base = unquote(match.group(1))
# NB: The suffix and "fragment identifier" are not unquoted
suffix = match.group(2)
fragment = match.group(3)
else:
base, suffix = os.path.splitext(refuri)
base = unquote(base)
fragment = ""
return base, suffix, fragment
[docs]class RewriteLocalLinks(docutils.transforms.Transform):
"""Turn links to source files into ``:doc:``/``:ref:`` links.
Links to subsections are possible with ``...#Subsection-Title``.
These links use the labels created by CreateSectionLabels.
Links to subsections use ``:ref:``, links to whole source files use
``:doc:``. Latter can be useful if you have an ``index.rst`` but
also want a distinct ``index.ipynb`` for use with Jupyter.
In this case you can use such a link in a notebook::
[Back to main page](index.ipynb)
In Jupyter, this will create a "normal" link to ``index.ipynb``, but
in the files generated by Sphinx, this will become a link to the
main page created from ``index.rst``.
"""
default_priority = 500 # After AnonymousHyperlinks (440)
[docs] def apply(self):
sphinx = import_sphinx()
env = self.document.settings.env
for node in self.document.traverse(docutils.nodes.reference):
base, suffix, fragment = _local_file_from_reference(node, self.document)
if not base:
continue
for s in env.config.source_suffix:
if suffix.lower() == s.lower():
target = base
if fragment:
target_ext = suffix + fragment
reftype = "ref"
else:
target_ext = ""
reftype = "doc"
break
else:
continue # Not a link to a potential Sphinx source file
target_docname = nbconvert.filters.posix_path(
os.path.normpath(os.path.join(os.path.dirname(env.docname), target))
)
if target_docname in env.found_docs:
reftarget = "/" + target_docname + target_ext
if reftype == "ref":
reftarget = reftarget.lower()
linktext = node.astext()
xref = sphinx.addnodes.pending_xref(
reftype=reftype,
reftarget=reftarget,
refdomain="std",
refwarn=True,
refexplicit=True,
refdoc=env.docname,
)
xref += docutils.nodes.Text(linktext, linktext)
node.replace_self(xref)
[docs]class CreateSectionLabels(docutils.transforms.Transform):
"""Make labels for each document and each section thereof.
These labels are referenced in RewriteLocalLinks but can also be
used manually with ``:ref:``.
"""
default_priority = 250 # Before references.PropagateTargets (260)
[docs] def apply(self):
env = self.document.settings.env
file_ext = os.path.splitext(env.doc2path(env.docname))[1]
i_still_have_to_create_the_document_label = True
for section in self.document.traverse(docutils.nodes.section):
assert section.children
assert isinstance(section.children[0], docutils.nodes.title)
title = section.children[0].astext()
link_id = section["ids"][0]
label = "/" + env.docname + file_ext + "#" + link_id
label = label.lower()
env.domaindata["std"]["labels"][label] = (env.docname, link_id, title)
env.domaindata["std"]["anonlabels"][label] = (env.docname, link_id)
# Create a label for the whole document using the first section:
if i_still_have_to_create_the_document_label:
label = "/" + env.docname.lower() + file_ext
env.domaindata["std"]["labels"][label] = (env.docname, "", title)
env.domaindata["std"]["anonlabels"][label] = (env.docname, "")
i_still_have_to_create_the_document_label = False
[docs]class CreateDomainObjectLabels(docutils.transforms.Transform):
"""Create labels for domain-specific object signatures."""
default_priority = 250 # About the same as CreateSectionLabels
[docs] def apply(self):
sphinx = import_sphinx()
env = self.document.settings.env
file_ext = os.path.splitext(env.doc2path(env.docname))[1]
for sig in self.document.traverse(sphinx.addnodes.desc_signature):
try:
title = sig["ids"][0]
except IndexError:
# Object has same name as another, so skip it
continue
link_id = title.replace(" ", "-")
sig["ids"] = [link_id]
label = "/" + env.docname + file_ext + "#" + link_id
label = label.lower()
env.domaindata["std"]["labels"][label] = (env.docname, link_id, title)
env.domaindata["std"]["anonlabels"][label] = (env.docname, link_id)