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
@@ -139,7 +138,7 @@ def setup(app, get_doc_object_=get_doc_object):
139
138
# Extra mangling domains
140
139
app .add_domain (NumpyPythonDomain )
141
140
app .add_domain (NumpyCDomain )
142
-
141
+
143
142
metadata = {'parallel_read_safe' : True }
144
143
return metadata
145
144
@@ -190,6 +189,62 @@ class NumpyCDomain(ManglingDomainBase, CDomain):
190
189
}
191
190
192
191
192
+ def match_items (lines , content_old ):
193
+ """Create items for mangled lines.
194
+
195
+ This function tries to match the lines in ``lines`` with the items (source
196
+ file references and line numbers) in ``content_old``. The
197
+ ``mangle_docstrings`` function changes the actual docstrings, but doesn't
198
+ keep track of where each line came from. The manging does many operations
199
+ on the original lines, which are hard to track afterwards.
200
+
201
+ Many of the line changes come from deleting or inserting blank lines. This
202
+ function tries to match lines by ignoring blank lines. All other changes
203
+ (such as inserting figures or changes in the references) are completely
204
+ ignored, so the generated line numbers will be off if ``mangle_docstrings``
205
+ does anything non-trivial.
206
+
207
+ This is a best-effort function and the real fix would be to make
208
+ ``mangle_docstrings`` actually keep track of the ``items`` together with
209
+ the ``lines``.
210
+
211
+ Examples
212
+ --------
213
+ >>> lines = ['', 'A', '', 'B', ' ', '', 'C', 'D']
214
+ >>> lines_old = ['a', '', '', 'b', '', 'c']
215
+ >>> items_old = [('file1.py', 0), ('file1.py', 1), ('file1.py', 2),
216
+ ... ('file2.py', 0), ('file2.py', 1), ('file2.py', 2)]
217
+ >>> content_old = ViewList(lines_old, items=items_old)
218
+ >>> match_items(lines, content_old) # doctest: +NORMALIZE_WHITESPACE
219
+ [('file1.py', 0), ('file1.py', 0), ('file2.py', 0), ('file2.py', 0),
220
+ ('file2.py', 2), ('file2.py', 2), ('file2.py', 2), ('file2.py', 2)]
221
+ >>> # first 2 ``lines`` are matched to 'a', second 2 to 'b', rest to 'c'
222
+ >>> # actual content is completely ignored.
223
+
224
+ Notes
225
+ -----
226
+ The algorithm tries to match any line in ``lines`` with one in
227
+ ``lines_old``. It skips over all empty lines in ``lines_old`` and assigns
228
+ this line number to all lines in ``lines``, unless a non-empty line is
229
+ found in ``lines`` in which case it goes to the next line in ``lines_old``.
230
+
231
+ """
232
+ items_new = []
233
+ lines_old = content_old .data
234
+ items_old = content_old .items
235
+ j = 0
236
+ for i , line in enumerate (lines ):
237
+ # go to next non-empty line in old:
238
+ # line.strip() checks whether the string is all whitespace
239
+ while j < len (lines_old ) - 1 and not lines_old [j ].strip ():
240
+ j += 1
241
+ items_new .append (items_old [j ])
242
+ if line .strip () and j < len (lines_old ) - 1 :
243
+ j += 1
244
+ assert (len (items_new ) == len (lines ))
245
+ return items_new
246
+
247
+
193
248
def wrap_mangling_directive (base_directive , objtype ):
194
249
class directive (base_directive ):
195
250
def run (self ):
@@ -205,7 +260,10 @@ def run(self):
205
260
206
261
lines = list (self .content )
207
262
mangle_docstrings (env .app , objtype , name , None , None , lines )
208
- self .content = ViewList (lines , self .content .parent )
263
+ if self .content :
264
+ items = match_items (lines , self .content )
265
+ self .content = ViewList (lines , items = items ,
266
+ parent = self .content .parent )
209
267
210
268
return base_directive .run (self )
211
269
0 commit comments