29
29
raise RuntimeError ("Sphinx 1.0.1 or newer is required" )
30
30
31
31
from .docscrape_sphinx import get_doc_object , SphinxDocString
32
- from sphinx .util .compat import Directive
33
32
34
33
if sys .version_info [0 ] >= 3 :
35
34
sixu = lambda s : s
@@ -133,7 +132,7 @@ def setup(app, get_doc_object_=get_doc_object):
133
132
# Extra mangling domains
134
133
app .add_domain (NumpyPythonDomain )
135
134
app .add_domain (NumpyCDomain )
136
-
135
+
137
136
metadata = {'parallel_read_safe' : True }
138
137
return metadata
139
138
@@ -184,6 +183,62 @@ class NumpyCDomain(ManglingDomainBase, CDomain):
184
183
}
185
184
186
185
186
+ def match_items (lines , content_old ):
187
+ """Create items for mangled lines.
188
+
189
+ This function tries to match the lines in ``lines`` with the items (source
190
+ file references and line numbers) in ``content_old``. The
191
+ ``mangle_docstrings`` function changes the actual docstrings, but doesn't
192
+ keep track of where each line came from. The manging does many operations
193
+ on the original lines, which are hard to track afterwards.
194
+
195
+ Many of the line changes come from deleting or inserting blank lines. This
196
+ function tries to match lines by ignoring blank lines. All other changes
197
+ (such as inserting figures or changes in the references) are completely
198
+ ignored, so the generated line numbers will be off if ``mangle_docstrings``
199
+ does anything non-trivial.
200
+
201
+ This is a best-effort function and the real fix would be to make
202
+ ``mangle_docstrings`` actually keep track of the ``items`` together with
203
+ the ``lines``.
204
+
205
+ Examples
206
+ --------
207
+ >>> lines = ['', 'A', '', 'B', ' ', '', 'C', 'D']
208
+ >>> lines_old = ['a', '', '', 'b', '', 'c']
209
+ >>> items_old = [('file1.py', 0), ('file1.py', 1), ('file1.py', 2),
210
+ ... ('file2.py', 0), ('file2.py', 1), ('file2.py', 2)]
211
+ >>> content_old = ViewList(lines_old, items=items_old)
212
+ >>> match_items(lines, content_old) # doctest: +NORMALIZE_WHITESPACE
213
+ [('file1.py', 0), ('file1.py', 0), ('file2.py', 0), ('file2.py', 0),
214
+ ('file2.py', 2), ('file2.py', 2), ('file2.py', 2), ('file2.py', 2)]
215
+ >>> # first 2 ``lines`` are matched to 'a', second 2 to 'b', rest to 'c'
216
+ >>> # actual content is completely ignored.
217
+
218
+ Notes
219
+ -----
220
+ The algorithm tries to match any line in ``lines`` with one in
221
+ ``lines_old``. It skips over all empty lines in ``lines_old`` and assigns
222
+ this line number to all lines in ``lines``, unless a non-empty line is
223
+ found in ``lines`` in which case it goes to the next line in ``lines_old``.
224
+
225
+ """
226
+ items_new = []
227
+ lines_old = content_old .data
228
+ items_old = content_old .items
229
+ j = 0
230
+ for i , line in enumerate (lines ):
231
+ # go to next non-empty line in old:
232
+ # line.strip() checks whether the string is all whitespace
233
+ while j < len (lines_old ) - 1 and not lines_old [j ].strip ():
234
+ j += 1
235
+ items_new .append (items_old [j ])
236
+ if line .strip () and j < len (lines_old ) - 1 :
237
+ j += 1
238
+ assert (len (items_new ) == len (lines ))
239
+ return items_new
240
+
241
+
187
242
def wrap_mangling_directive (base_directive , objtype ):
188
243
class directive (base_directive ):
189
244
def run (self ):
@@ -199,7 +254,10 @@ def run(self):
199
254
200
255
lines = list (self .content )
201
256
mangle_docstrings (env .app , objtype , name , None , None , lines )
202
- self .content = ViewList (lines , self .content .parent )
257
+ if self .content :
258
+ items = match_items (lines , self .content )
259
+ self .content = ViewList (lines , items = items ,
260
+ parent = self .content .parent )
203
261
204
262
return base_directive .run (self )
205
263
0 commit comments