2121import sys
2222import re
2323import pydoc
24- import sphinx
2524import inspect
2625import collections
26+ import hashlib
27+
28+ from docutils .nodes import citation , Text
29+ import sphinx
30+ from sphinx .addnodes import pending_xref , desc_content
2731
2832if sphinx .__version__ < '1.0.1' :
2933 raise RuntimeError ("Sphinx 1.0.1 or newer is required" )
3741 sixu = lambda s : unicode (s , 'unicode_escape' )
3842
3943
40- def rename_references (app , what , name , obj , options , lines ,
41- reference_offset = [0 ]):
42- # replace reference numbers so that there are no duplicates
44+ HASH_LEN = 12
45+
46+
47+ def rename_references (app , what , name , obj , options , lines ):
48+ # decorate reference numbers so that there are no duplicates
49+ # these are later undecorated in the doctree, in relabel_references
4350 references = set ()
4451 for line in lines :
4552 line = line .strip ()
@@ -49,19 +56,51 @@ def rename_references(app, what, name, obj, options, lines,
4956 references .add (m .group (1 ))
5057
5158 if references :
52- for r in references :
53- if r .isdigit ():
54- new_r = sixu ("R%d" ) % (reference_offset [0 ] + int (r ))
55- else :
56- new_r = sixu ("%s%d" ) % (r , reference_offset [0 ])
59+ # we use a hash to mangle the reference name to avoid invalid names
60+ sha = hashlib .sha256 ()
61+ sha .update (name .encode ('utf8' ))
62+ prefix = 'R' + sha .hexdigest ()[:HASH_LEN ]
5763
64+ for r in references :
65+ new_r = prefix + '-' + r
5866 for i , line in enumerate (lines ):
5967 lines [i ] = lines [i ].replace (sixu ('[%s]_' ) % r ,
6068 sixu ('[%s]_' ) % new_r )
6169 lines [i ] = lines [i ].replace (sixu ('.. [%s]' ) % r ,
6270 sixu ('.. [%s]' ) % new_r )
6371
64- reference_offset [0 ] += len (references )
72+
73+ def _ascend (node , cls ):
74+ while node and not isinstance (node , cls ):
75+ node = node .parent
76+ return node
77+
78+
79+ def relabel_references (app , doc ):
80+ # Change 'hash-ref' to 'ref' in label text
81+ for citation_node in doc .traverse (citation ):
82+ if _ascend (citation_node , desc_content ) is None :
83+ # no desc node in ancestry -> not in a docstring
84+ # XXX: should we also somehow check it's in a References section?
85+ continue
86+ label_node = citation_node [0 ]
87+ prefix , _ , new_label = label_node [0 ].astext ().partition ('-' )
88+ assert len (prefix ) == HASH_LEN + 1
89+ new_text = Text (new_label )
90+ label_node .replace (label_node [0 ], new_text )
91+
92+ for id in citation_node ['backrefs' ]:
93+ ref = doc .ids [id ]
94+ ref_text = ref [0 ]
95+
96+ # Sphinx has created pending_xref nodes with [reftext] text.
97+ def matching_pending_xref (node ):
98+ return (isinstance (node , pending_xref ) and
99+ node [0 ].astext () == '[%s]' % ref_text )
100+
101+ for xref_node in ref .parent .traverse (matching_pending_xref ):
102+ xref_node .replace (xref_node [0 ], Text ('[%s]' % new_text ))
103+ ref .replace (ref_text , new_text .copy ())
65104
66105
67106DEDUPLICATION_TAG = ' !! processed by numpydoc !!'
@@ -139,6 +178,7 @@ def setup(app, get_doc_object_=get_doc_object):
139178
140179 app .connect ('autodoc-process-docstring' , mangle_docstrings )
141180 app .connect ('autodoc-process-signature' , mangle_signature )
181+ app .connect ('doctree-read' , relabel_references )
142182 app .add_config_value ('numpydoc_edit_link' , None , False )
143183 app .add_config_value ('numpydoc_use_plots' , None , False )
144184 app .add_config_value ('numpydoc_use_blockquotes' , None , False )
0 commit comments