~ubuntu-branches/ubuntu/utopic/python-chaco/utopic

« back to all changes in this revision

Viewing changes to docs/source/sphinxext/docscrape.py

  • Committer: Package Import Robot
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2014-06-01 17:04:08 UTC
  • mfrom: (7.2.5 sid)
  • Revision ID: package-import@ubuntu.com-20140601170408-m86xvdjd83a4qon0
Tags: 4.4.1-1ubuntu1
* Merge from Debian unstable. Remaining Ubuntu changes:
 - Let the binary-predeb target work on the usr/lib/python* directory
   as we don't have usr/share/pyshared anymore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"""Extract reference documentation from the NumPy source tree.
2
 
 
3
 
"""
4
 
 
5
 
import inspect
6
 
import textwrap
7
 
import re
8
 
import pydoc
9
 
from StringIO import StringIO
10
 
from warnings import warn
11
 
 
12
 
class Reader(object):
13
 
    """A line-based string reader.
14
 
 
15
 
    """
16
 
    def __init__(self, data):
17
 
        """
18
 
        Parameters
19
 
        ----------
20
 
        data : str
21
 
           String with lines separated by '\n'.
22
 
 
23
 
        """
24
 
        if isinstance(data,list):
25
 
            self._str = data
26
 
        else:
27
 
            self._str = data.split('\n') # store string as list of lines
28
 
 
29
 
        self.reset()
30
 
 
31
 
    def __getitem__(self, n):
32
 
        return self._str[n]
33
 
 
34
 
    def reset(self):
35
 
        self._l = 0 # current line nr
36
 
 
37
 
    def read(self):
38
 
        if not self.eof():
39
 
            out = self[self._l]
40
 
            self._l += 1
41
 
            return out
42
 
        else:
43
 
            return ''
44
 
 
45
 
    def seek_next_non_empty_line(self):
46
 
        for l in self[self._l:]:
47
 
            if l.strip():
48
 
                break
49
 
            else:
50
 
                self._l += 1
51
 
 
52
 
    def eof(self):
53
 
        return self._l >= len(self._str)
54
 
 
55
 
    def read_to_condition(self, condition_func):
56
 
        start = self._l
57
 
        for line in self[start:]:
58
 
            if condition_func(line):
59
 
                return self[start:self._l]
60
 
            self._l += 1
61
 
            if self.eof():
62
 
                return self[start:self._l+1]
63
 
        return []
64
 
 
65
 
    def read_to_next_empty_line(self):
66
 
        self.seek_next_non_empty_line()
67
 
        def is_empty(line):
68
 
            return not line.strip()
69
 
        return self.read_to_condition(is_empty)
70
 
 
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)
75
 
 
76
 
    def peek(self,n=0):
77
 
        if self._l + n < len(self._str):
78
 
            return self[self._l + n]
79
 
        else:
80
 
            return ''
81
 
 
82
 
    def is_empty(self):
83
 
        return not ''.join(self._str).strip()
84
 
 
85
 
 
86
 
class NumpyDocString(object):
87
 
    def __init__(self,docstring):
88
 
        docstring = docstring.split('\n')
89
 
 
90
 
        # De-indent paragraph
91
 
        try:
92
 
            indent = min(len(s) - len(s.lstrip()) for s in docstring
93
 
                         if s.strip())
94
 
        except ValueError:
95
 
            indent = 0
96
 
 
97
 
        for n,line in enumerate(docstring):
98
 
            docstring[n] = docstring[n][indent:]
99
 
 
100
 
        self._doc = Reader(docstring)
101
 
        self._parsed_data = {
102
 
            'Signature': '',
103
 
            'Summary': '',
104
 
            'Extended Summary': [],
105
 
            'Parameters': [],
106
 
            'Returns': [],
107
 
            'Raises': [],
108
 
            'Warns': [],
109
 
            'Other Parameters': [],
110
 
            'Attributes': [],
111
 
            'Methods': [],
112
 
            'See Also': [],
113
 
            'Notes': [],
114
 
            'References': '',
115
 
            'Examples': '',
116
 
            'index': {}
117
 
            }
118
 
 
119
 
        self._parse()
120
 
 
121
 
    def __getitem__(self,key):
122
 
        return self._parsed_data[key]
123
 
 
124
 
    def __setitem__(self,key,val):
125
 
        if not self._parsed_data.has_key(key):
126
 
            warn("Unknown section %s" % key)
127
 
        else:
128
 
            self._parsed_data[key] = val
129
 
 
130
 
    def _is_at_section(self):
131
 
        self._doc.seek_next_non_empty_line()
132
 
 
133
 
        if self._doc.eof():
134
 
            return False
135
 
 
136
 
        l1 = self._doc.peek().strip()  # e.g. Parameters
137
 
 
138
 
        if l1.startswith('.. index::'):
139
 
            return True
140
 
 
141
 
        l2 = self._doc.peek(1).strip() #    ----------
142
 
        return l2.startswith('-'*len(l1))
143
 
 
144
 
    def _strip(self,doc):
145
 
        i = 0
146
 
        j = 0
147
 
        for i,line in enumerate(doc):
148
 
            if line.strip(): break
149
 
 
150
 
        for j,line in enumerate(doc[::-1]):
151
 
            if line.strip(): break
152
 
 
153
 
        return doc[i:len(doc)-j]
154
 
 
155
 
    def _read_to_next_section(self):
156
 
        section = self._doc.read_to_next_empty_line()
157
 
 
158
 
        while not self._is_at_section() and not self._doc.eof():
159
 
            if not self._doc.peek(-1).strip(): # previous line was empty
160
 
                section += ['']
161
 
 
162
 
            section += self._doc.read_to_next_empty_line()
163
 
 
164
 
        return section
165
 
 
166
 
    def _read_sections(self):
167
 
        while not self._doc.eof():
168
 
            data = self._read_to_next_section()
169
 
            name = data[0].strip()
170
 
 
171
 
            if name.startswith('..'): # index section
172
 
                yield name, data[1:]
173
 
            elif len(data) < 2:
174
 
                yield StopIteration
175
 
            else:
176
 
                yield name, self._strip(data[2:])
177
 
 
178
 
    def _parse_param_list(self,content):
179
 
        r = Reader(content)
180
 
        params = []
181
 
        while not r.eof():
182
 
            header = r.read().strip()
183
 
            if ' : ' in header:
184
 
                arg_name, arg_type = header.split(' : ')[:2]
185
 
            else:
186
 
                arg_name, arg_type = header, ''
187
 
 
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)
192
 
 
193
 
            params.append((arg_name,arg_type,desc))
194
 
 
195
 
        return params
196
 
 
197
 
    def _parse_see_also(self, content):
198
 
        """
199
 
        func_name : Descriptive text
200
 
            continued text
201
 
        another_func_name : Descriptive text
202
 
        func_name1, func_name2, func_name3
203
 
 
204
 
        """
205
 
        functions = []
206
 
        current_func = None
207
 
        rest = []
208
 
        for line in content:
209
 
            if not line.strip(): continue
210
 
            if ':' in line:
211
 
                if current_func:
212
 
                    functions.append((current_func, rest))
213
 
                r = line.split(':', 1)
214
 
                current_func = r[0].strip()
215
 
                r[1] = r[1].strip()
216
 
                if r[1]:
217
 
                    rest = [r[1]]
218
 
                else:
219
 
                    rest = []
220
 
            elif not line.startswith(' '):
221
 
                if current_func:
222
 
                    functions.append((current_func, rest))
223
 
                    current_func = None
224
 
                    rest = []
225
 
                if ',' in line:
226
 
                    for func in line.split(','):
227
 
                        func = func.strip()
228
 
                        if func:
229
 
                            functions.append((func, []))
230
 
                elif line.strip():
231
 
                    current_func = line.strip()
232
 
            elif current_func is not None:
233
 
                rest.append(line.strip())
234
 
        if current_func:
235
 
            functions.append((current_func, rest))
236
 
        return functions
237
 
 
238
 
    def _parse_index(self, section, content):
239
 
        """
240
 
        .. index: default
241
 
           :refguide: something, else, and more
242
 
 
243
 
        """
244
 
        def strip_each_in(lst):
245
 
            return [s.strip() for s in lst]
246
 
 
247
 
        out = {}
248
 
        section = section.split('::')
249
 
        if len(section) > 1:
250
 
            out['default'] = strip_each_in(section[1].split(','))[0]
251
 
        for line in content:
252
 
            line = line.split(':')
253
 
            if len(line) > 2:
254
 
                out[line[1]] = strip_each_in(line[2].split(','))
255
 
        return out
256
 
 
257
 
    def _parse_summary(self):
258
 
        """Grab signature (if given) and summary"""
259
 
        if self._is_at_section():
260
 
            return
261
 
 
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()
268
 
        else:
269
 
            self['Summary'] = summary
270
 
 
271
 
        if not self._is_at_section():
272
 
            self['Extended Summary'] = self._read_to_next_section()
273
 
 
274
 
    def _parse(self):
275
 
        self._doc.reset()
276
 
        self._parse_summary()
277
 
 
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)
288
 
            else:
289
 
                self[section] = content
290
 
 
291
 
    # string conversion routines
292
 
 
293
 
    def _str_header(self, name, symbol='-'):
294
 
        return [name, len(name)*symbol]
295
 
 
296
 
    def _str_indent(self, doc, indent=4):
297
 
        out = []
298
 
        for line in doc:
299
 
            out += [' '*indent + line]
300
 
        return out
301
 
 
302
 
    def _str_signature(self):
303
 
        if self['Signature']:
304
 
            return [self['Signature'].replace('*','\*')] + ['']
305
 
        else:
306
 
            return ['']
307
 
 
308
 
    def _str_summary(self):
309
 
        if self['Summary']:
310
 
            return self['Summary'] + ['']
311
 
        else:
312
 
            return []
313
 
 
314
 
    def _str_extended_summary(self):
315
 
        if self['Extended Summary']:
316
 
            return self['Extended Summary'] + ['']
317
 
        else:
318
 
            return []
319
 
 
320
 
    def _str_param_list(self, name):
321
 
        out = []
322
 
        if 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)
327
 
            out += ['']
328
 
        return out
329
 
 
330
 
    def _str_section(self, name):
331
 
        out = []
332
 
        if self[name]:
333
 
            out += self._str_header(name)
334
 
            out += self[name]
335
 
            out += ['']
336
 
        return out
337
 
 
338
 
    def _str_see_also(self, func_role):
339
 
        if not self['See Also']: return []
340
 
        out = []
341
 
        out += self._str_header("See Also")
342
 
        last_had_desc = True
343
 
        for func, desc in self['See Also']:
344
 
            if func_role:
345
 
                link = ':%s:`%s`' % (func_role, func)
346
 
            else:
347
 
                link = "`%s`_" % func
348
 
            if desc or last_had_desc:
349
 
                out += ['']
350
 
                out += [link]
351
 
            else:
352
 
                out[-1] += ", %s" % link
353
 
            if desc:
354
 
                out += self._str_indent(desc)
355
 
                last_had_desc = True
356
 
            else:
357
 
                last_had_desc = False
358
 
        out += ['']
359
 
        return out
360
 
 
361
 
    def _str_index(self):
362
 
        idx = self['index']
363
 
        out = []
364
 
        out += ['.. index:: %s' % idx.get('default','')]
365
 
        for section, references in idx.iteritems():
366
 
            if section == 'default':
367
 
                continue
368
 
            out += ['   :%s: %s' % (section, ', '.join(references))]
369
 
        return out
370
 
 
371
 
    def __str__(self, func_role=''):
372
 
        out = []
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)
383
 
 
384
 
 
385
 
def indent(str,indent=4):
386
 
    indent_str = ' '*indent
387
 
    if str is None:
388
 
        return indent_str
389
 
    lines = str.split('\n')
390
 
    return '\n'.join(indent_str + l for l in lines)
391
 
 
392
 
def header(text, style='-'):
393
 
    return text + '\n' + style*len(text) + '\n'
394
 
 
395
 
 
396
 
class FunctionDoc(NumpyDocString):
397
 
    def __init__(self, func, role='func'):
398
 
        self._f = func
399
 
        self._role = role # e.g. "func" or "meth"
400
 
        try:
401
 
            NumpyDocString.__init__(self,inspect.getdoc(func) or '')
402
 
        except ValueError, e:
403
 
            print '*'*78
404
 
            print "ERROR: '%s' while parsing `%s`" % (e, self._f)
405
 
            print '*'*78
406
 
            #print "Docstring follows:"
407
 
            #print doclines
408
 
            #print '='*78
409
 
 
410
 
        if not self['Signature']:
411
 
            func, func_name = self.get_func()
412
 
            try:
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)
418
 
            except TypeError, e:
419
 
                signature = '%s()' % func_name
420
 
            self['Signature'] = signature
421
 
 
422
 
    def get_func(self):
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__)
426
 
        else:
427
 
            func = self._f
428
 
        return func, func_name
429
 
 
430
 
    def __str__(self):
431
 
        out = ''
432
 
 
433
 
        func, func_name = self.get_func()
434
 
        signature = self['Signature'].replace('*', '\*')
435
 
 
436
 
        roles = {'func': 'function',
437
 
                 'meth': 'method'}
438
 
 
439
 
        if self._role:
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,''),
443
 
                                             func_name)
444
 
 
445
 
        out += super(FunctionDoc, self).__str__(func_role=self._role)
446
 
        return out
447
 
 
448
 
 
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)
453
 
        self._cls = cls
454
 
 
455
 
        if modulename and not modulename.endswith('.'):
456
 
            modulename += '.'
457
 
        self._mod = modulename
458
 
        self._name = cls.__name__
459
 
        self._func_doc = func_doc
460
 
 
461
 
        NumpyDocString.__init__(self, pydoc.getdoc(cls))
462
 
 
463
 
    @property
464
 
    def methods(self):
465
 
        return [name for name,func in inspect.getmembers(self._cls)
466
 
                if not name.startswith('_') and callable(func)]
467
 
 
468
 
    def __str__(self):
469
 
        out = ''
470
 
        out += super(ClassDoc, self).__str__()
471
 
        out += "\n\n"
472
 
 
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)
477
 
 
478
 
        return out
479
 
 
480