Skip to content

Ensure reference renaming is parallel-safe #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 28, 2018
6 changes: 6 additions & 0 deletions doc/format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@ The sections of a function's docstring are:
should not be required to understand it. References are numbered, starting
from one, in the order in which they are cited.

.. warning:: **References will break tables**

Where references like [1] appear in a tables within a numpydoc
docstring, the table markup will be broken by numpydoc processing. See
`numpydoc issue #130 <https://github.com/numpy/numpydoc/issues/130>`_

14. **Examples**

An optional section for examples, using the `doctest
Expand Down
60 changes: 50 additions & 10 deletions numpydoc/numpydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
import sys
import re
import pydoc
import sphinx
import inspect
import collections
import hashlib

from docutils.nodes import citation, Text
import sphinx
from sphinx.addnodes import pending_xref, desc_content

if sphinx.__version__ < '1.0.1':
raise RuntimeError("Sphinx 1.0.1 or newer is required")
Expand All @@ -36,9 +40,12 @@
sixu = lambda s: unicode(s, 'unicode_escape')


def rename_references(app, what, name, obj, options, lines,
reference_offset=[0]):
# replace reference numbers so that there are no duplicates
HASH_LEN = 12


def rename_references(app, what, name, obj, options, lines):
# decorate reference numbers so that there are no duplicates
# these are later undecorated in the doctree, in relabel_references
references = set()
for line in lines:
line = line.strip()
Expand All @@ -48,19 +55,51 @@ def rename_references(app, what, name, obj, options, lines,
references.add(m.group(1))

if references:
for r in references:
if r.isdigit():
new_r = sixu("R%d") % (reference_offset[0] + int(r))
else:
new_r = sixu("%s%d") % (r, reference_offset[0])
# we use a hash to mangle the reference name to avoid invalid names
sha = hashlib.sha256()
sha.update(name.encode('utf8'))
prefix = 'R' + sha.hexdigest()[:HASH_LEN]

for r in references:
new_r = prefix + '-' + r
for i, line in enumerate(lines):
lines[i] = lines[i].replace(sixu('[%s]_') % r,
sixu('[%s]_') % new_r)
lines[i] = lines[i].replace(sixu('.. [%s]') % r,
sixu('.. [%s]') % new_r)

reference_offset[0] += len(references)

def _ascend(node, cls):
while node and not isinstance(node, cls):
node = node.parent
return node


def relabel_references(app, doc):
# Change 'hash-ref' to 'ref' in label text
for citation_node in doc.traverse(citation):
if _ascend(citation_node, desc_content) is None:
# no desc node in ancestry -> not in a docstring
# XXX: should we also somehow check it's in a References section?
continue
label_node = citation_node[0]
prefix, _, new_label = label_node[0].astext().partition('-')
assert len(prefix) == HASH_LEN + 1
new_text = Text(new_label)
label_node.replace(label_node[0], new_text)

for id in citation_node['backrefs']:
ref = doc.ids[id]
ref_text = ref[0]

# Sphinx has created pending_xref nodes with [reftext] text.
def matching_pending_xref(node):
return (isinstance(node, pending_xref) and
node[0].astext() == '[%s]' % ref_text)

for xref_node in ref.parent.traverse(matching_pending_xref):
xref_node.replace(xref_node[0], Text('[%s]' % new_text))
ref.replace(ref_text, new_text.copy())


DEDUPLICATION_TAG = ' !! processed by numpydoc !!'
Expand Down Expand Up @@ -137,6 +176,7 @@ def setup(app, get_doc_object_=get_doc_object):

app.connect('autodoc-process-docstring', mangle_docstrings)
app.connect('autodoc-process-signature', mangle_signature)
app.connect('doctree-read', relabel_references)
app.add_config_value('numpydoc_edit_link', None, False)
app.add_config_value('numpydoc_use_plots', None, False)
app.add_config_value('numpydoc_show_class_members', True, True)
Expand Down