21
21
import sys
22
22
import re
23
23
import pydoc
24
- import sphinx
25
24
import inspect
26
25
import collections
26
+ import hashlib
27
+
28
+ from docutils .nodes import citation , Text
29
+ import sphinx
30
+ from sphinx .addnodes import pending_xref , desc_content
27
31
28
32
if sphinx .__version__ < '1.0.1' :
29
33
raise RuntimeError ("Sphinx 1.0.1 or newer is required" )
37
41
sixu = lambda s : unicode (s , 'unicode_escape' )
38
42
39
43
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
43
50
references = set ()
44
51
for line in lines :
45
52
line = line .strip ()
@@ -49,19 +56,51 @@ def rename_references(app, what, name, obj, options, lines,
49
56
references .add (m .group (1 ))
50
57
51
58
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 ]
57
63
64
+ for r in references :
65
+ new_r = prefix + '-' + r
58
66
for i , line in enumerate (lines ):
59
67
lines [i ] = lines [i ].replace (sixu ('[%s]_' ) % r ,
60
68
sixu ('[%s]_' ) % new_r )
61
69
lines [i ] = lines [i ].replace (sixu ('.. [%s]' ) % r ,
62
70
sixu ('.. [%s]' ) % new_r )
63
71
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 ())
65
104
66
105
67
106
DEDUPLICATION_TAG = ' !! processed by numpydoc !!'
@@ -139,6 +178,7 @@ def setup(app, get_doc_object_=get_doc_object):
139
178
140
179
app .connect ('autodoc-process-docstring' , mangle_docstrings )
141
180
app .connect ('autodoc-process-signature' , mangle_signature )
181
+ app .connect ('doctree-read' , relabel_references )
142
182
app .add_config_value ('numpydoc_edit_link' , None , False )
143
183
app .add_config_value ('numpydoc_use_plots' , None , False )
144
184
app .add_config_value ('numpydoc_use_blockquotes' , None , False )
0 commit comments