1
"""Extract reference documentation from the NumPy source tree.
9
from StringIO import StringIO
10
from warnings import warn
13
"""A line-based string reader.
16
def __init__(self, data):
21
String with lines separated by '\n'.
24
if isinstance(data,list):
27
self._str = data.split('\n') # store string as list of lines
31
def __getitem__(self, n):
35
self._l = 0 # current line nr
45
def seek_next_non_empty_line(self):
46
for l in self[self._l:]:
53
return self._l >= len(self._str)
55
def read_to_condition(self, condition_func):
57
for line in self[start:]:
58
if condition_func(line):
59
return self[start:self._l]
62
return self[start:self._l+1]
65
def read_to_next_empty_line(self):
66
self.seek_next_non_empty_line()
68
return not line.strip()
69
return self.read_to_condition(is_empty)
71
def read_to_next_unindented_line(self):
72
def is_unindented(line):
73
return (line.strip() and (len(line.lstrip()) == len(line)))
74
return self.read_to_condition(is_unindented)
77
if self._l + n < len(self._str):
78
return self[self._l + n]
83
return not ''.join(self._str).strip()
86
class NumpyDocString(object):
87
def __init__(self,docstring):
88
docstring = docstring.split('\n')
92
indent = min(len(s) - len(s.lstrip()) for s in docstring
97
for n,line in enumerate(docstring):
98
docstring[n] = docstring[n][indent:]
100
self._doc = Reader(docstring)
101
self._parsed_data = {
104
'Extended Summary': [],
109
'Other Parameters': [],
121
def __getitem__(self,key):
122
return self._parsed_data[key]
124
def __setitem__(self,key,val):
125
if not self._parsed_data.has_key(key):
126
warn("Unknown section %s" % key)
128
self._parsed_data[key] = val
130
def _is_at_section(self):
131
self._doc.seek_next_non_empty_line()
136
l1 = self._doc.peek().strip() # e.g. Parameters
138
if l1.startswith('.. index::'):
141
l2 = self._doc.peek(1).strip() # ----------
142
return l2.startswith('-'*len(l1))
144
def _strip(self,doc):
147
for i,line in enumerate(doc):
148
if line.strip(): break
150
for j,line in enumerate(doc[::-1]):
151
if line.strip(): break
153
return doc[i:len(doc)-j]
155
def _read_to_next_section(self):
156
section = self._doc.read_to_next_empty_line()
158
while not self._is_at_section() and not self._doc.eof():
159
if not self._doc.peek(-1).strip(): # previous line was empty
162
section += self._doc.read_to_next_empty_line()
166
def _read_sections(self):
167
while not self._doc.eof():
168
data = self._read_to_next_section()
169
name = data[0].strip()
171
if name.startswith('..'): # index section
176
yield name, self._strip(data[2:])
178
def _parse_param_list(self,content):
182
header = r.read().strip()
184
arg_name, arg_type = header.split(' : ')[:2]
186
arg_name, arg_type = header, ''
188
desc = r.read_to_next_unindented_line()
189
for n,line in enumerate(desc):
190
desc[n] = line.strip()
191
desc = desc #'\n'.join(desc)
193
params.append((arg_name,arg_type,desc))
197
def _parse_see_also(self, content):
199
func_name : Descriptive text
201
another_func_name : Descriptive text
202
func_name1, func_name2, func_name3
209
if not line.strip(): continue
212
functions.append((current_func, rest))
213
r = line.split(':', 1)
214
current_func = r[0].strip()
220
elif not line.startswith(' '):
222
functions.append((current_func, rest))
226
for func in line.split(','):
229
functions.append((func, []))
231
current_func = line.strip()
232
elif current_func is not None:
233
rest.append(line.strip())
235
functions.append((current_func, rest))
238
def _parse_index(self, section, content):
241
:refguide: something, else, and more
244
def strip_each_in(lst):
245
return [s.strip() for s in lst]
248
section = section.split('::')
250
out['default'] = strip_each_in(section[1].split(','))[0]
252
line = line.split(':')
254
out[line[1]] = strip_each_in(line[2].split(','))
257
def _parse_summary(self):
258
"""Grab signature (if given) and summary"""
259
if self._is_at_section():
262
summary = self._doc.read_to_next_empty_line()
263
summary_str = " ".join([s.strip() for s in summary]).strip()
264
if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
265
self['Signature'] = summary_str
266
if not self._is_at_section():
267
self['Summary'] = self._doc.read_to_next_empty_line()
269
self['Summary'] = summary
271
if not self._is_at_section():
272
self['Extended Summary'] = self._read_to_next_section()
276
self._parse_summary()
278
for (section,content) in self._read_sections():
279
if not section.startswith('..'):
280
section = ' '.join([s.capitalize() for s in section.split(' ')])
281
if section in ('Parameters', 'Attributes', 'Methods',
282
'Returns', 'Raises', 'Warns'):
283
self[section] = self._parse_param_list(content)
284
elif section.startswith('.. index::'):
285
self['index'] = self._parse_index(section, content)
286
elif section == 'See Also':
287
self['See Also'] = self._parse_see_also(content)
289
self[section] = content
291
# string conversion routines
293
def _str_header(self, name, symbol='-'):
294
return [name, len(name)*symbol]
296
def _str_indent(self, doc, indent=4):
299
out += [' '*indent + line]
302
def _str_signature(self):
303
if self['Signature']:
304
return [self['Signature'].replace('*','\*')] + ['']
308
def _str_summary(self):
310
return self['Summary'] + ['']
314
def _str_extended_summary(self):
315
if self['Extended Summary']:
316
return self['Extended Summary'] + ['']
320
def _str_param_list(self, name):
323
out += self._str_header(name)
324
for param,param_type,desc in self[name]:
325
out += ['%s : %s' % (param, param_type)]
326
out += self._str_indent(desc)
330
def _str_section(self, name):
333
out += self._str_header(name)
338
def _str_see_also(self, func_role):
339
if not self['See Also']: return []
341
out += self._str_header("See Also")
343
for func, desc in self['See Also']:
345
link = ':%s:`%s`' % (func_role, func)
347
link = "`%s`_" % func
348
if desc or last_had_desc:
352
out[-1] += ", %s" % link
354
out += self._str_indent(desc)
357
last_had_desc = False
361
def _str_index(self):
364
out += ['.. index:: %s' % idx.get('default','')]
365
for section, references in idx.iteritems():
366
if section == 'default':
368
out += [' :%s: %s' % (section, ', '.join(references))]
371
def __str__(self, func_role=''):
373
out += self._str_signature()
374
out += self._str_summary()
375
out += self._str_extended_summary()
376
for param_list in ('Parameters','Returns','Raises'):
377
out += self._str_param_list(param_list)
378
out += self._str_see_also(func_role)
379
for s in ('Notes','References','Examples'):
380
out += self._str_section(s)
381
out += self._str_index()
382
return '\n'.join(out)
385
def indent(str,indent=4):
386
indent_str = ' '*indent
389
lines = str.split('\n')
390
return '\n'.join(indent_str + l for l in lines)
392
def header(text, style='-'):
393
return text + '\n' + style*len(text) + '\n'
396
class FunctionDoc(NumpyDocString):
397
def __init__(self, func, role='func'):
399
self._role = role # e.g. "func" or "meth"
401
NumpyDocString.__init__(self,inspect.getdoc(func) or '')
402
except ValueError, e:
404
print "ERROR: '%s' while parsing `%s`" % (e, self._f)
406
#print "Docstring follows:"
410
if not self['Signature']:
411
func, func_name = self.get_func()
413
# try to read signature
414
argspec = inspect.getargspec(func)
415
argspec = inspect.formatargspec(*argspec)
416
argspec = argspec.replace('*','\*')
417
signature = '%s%s' % (func_name, argspec)
419
signature = '%s()' % func_name
420
self['Signature'] = signature
423
func_name = getattr(self._f, '__name__', self.__class__.__name__)
424
if hasattr(self._f, '__class__') or inspect.isclass(self._f):
425
func = getattr(self._f, '__call__', self._f.__init__)
428
return func, func_name
433
func, func_name = self.get_func()
434
signature = self['Signature'].replace('*', '\*')
436
roles = {'func': 'function',
440
if not roles.has_key(self._role):
441
print "Warning: invalid role %s" % self._role
442
out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''),
445
out += super(FunctionDoc, self).__str__(func_role=self._role)
449
class ClassDoc(NumpyDocString):
450
def __init__(self,cls,modulename='',func_doc=FunctionDoc):
451
if not inspect.isclass(cls):
452
raise ValueError("Initialise using a class. Got %r" % cls)
455
if modulename and not modulename.endswith('.'):
457
self._mod = modulename
458
self._name = cls.__name__
459
self._func_doc = func_doc
461
NumpyDocString.__init__(self, pydoc.getdoc(cls))
465
return [name for name,func in inspect.getmembers(self._cls)
466
if not name.startswith('_') and callable(func)]
470
out += super(ClassDoc, self).__str__()
473
#for m in self.methods:
474
# print "Parsing `%s`" % m
475
# out += str(self._func_doc(getattr(self._cls,m), 'meth')) + '\n\n'
476
# out += '.. index::\n single: %s; %s\n\n' % (self._name, m)