numpydoc.py 5.57 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
"""
========
numpydoc
========

Sphinx extension that handles docstrings in the Numpy standard format. [1]

It will:

- Convert Parameters etc. sections to field lists.
- Convert See Also section to a See also entry.
- Renumber references.
- Extract the signature from the docstring, if it can't be determined otherwise.

Tiago Peixoto's avatar
Tiago Peixoto committed
15
.. [1] https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
16
17
18

"""

19
import sphinx
Tiago Peixoto's avatar
Tiago Peixoto committed
20
import collections
21
22
23
24

if sphinx.__version__ < '1.0.1':
    raise RuntimeError("Sphinx 1.0.1 or newer is required")

25
import os, re, pydoc
Tiago Peixoto's avatar
Tiago Peixoto committed
26
from .docscrape_sphinx import get_doc_object, SphinxDocString
27
from sphinx.util.compat import Directive
28
29
30
31
import inspect

def mangle_docstrings(app, what, name, obj, options, lines,
                      reference_offset=[0]):
32
33
34
35

    cfg = dict(use_plots=app.config.numpydoc_use_plots,
               show_class_members=app.config.numpydoc_show_class_members)

36
37
    if what == 'module':
        # Strip top title
Tiago Peixoto's avatar
Tiago Peixoto committed
38
        title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*',
39
                              re.I|re.S)
Tiago Peixoto's avatar
Tiago Peixoto committed
40
        lines[:] = title_re.sub('', "\n".join(lines)).split("\n")
41
    else:
Tiago Peixoto's avatar
Tiago Peixoto committed
42
43
        doc = get_doc_object(obj, what, "\n".join(lines), config=cfg)
        lines[:] = str(doc).split("\n")
44
45
46
47

    if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \
           obj.__name__:
        if hasattr(obj, '__module__'):
Tiago Peixoto's avatar
Tiago Peixoto committed
48
            v = dict(full_name="%s.%s" % (obj.__module__, obj.__name__))
49
50
        else:
            v = dict(full_name=obj.__name__)
Tiago Peixoto's avatar
Tiago Peixoto committed
51
52
        lines += ['', '.. htmlonly::', '']
        lines += ['    %s' % x for x in
53
54
55
56
                  (app.config.numpydoc_edit_link % v).split("\n")]

    # replace reference numbers so that there are no duplicates
    references = []
57
58
    for line in lines:
        line = line.strip()
Tiago Peixoto's avatar
Tiago Peixoto committed
59
        m = re.match(r'^.. \[([a-z0-9_.-])\]', line, re.I)
60
61
62
63
64
        if m:
            references.append(m.group(1))

    # start renaming from the longest string, to avoid overwriting parts
    references.sort(key=lambda x: -len(x))
65
66
67
    if references:
        for i, line in enumerate(lines):
            for r in references:
Tiago Peixoto's avatar
Tiago Peixoto committed
68
69
                if re.match(r'^\d+$', r):
                    new_r = "R%d" % (reference_offset[0] + int(r))
70
                else:
Tiago Peixoto's avatar
Tiago Peixoto committed
71
72
73
74
75
                    new_r = "%s%d" % (r, reference_offset[0])
                lines[i] = lines[i].replace('[%s]_' % r,
                                            '[%s]_' % new_r)
                lines[i] = lines[i].replace('.. [%s]' % r,
                                            '.. [%s]' % new_r)
76
77
78
79
80
81

    reference_offset[0] += len(references)

def mangle_signature(app, what, name, obj, options, sig, retann):
    # Do not try to inspect classes that don't define `__init__`
    if (inspect.isclass(obj) and
82
83
        (not hasattr(obj, '__init__') or
        'initializes x; see ' in pydoc.getdoc(obj.__init__))):
84
85
        return '', ''

Tiago Peixoto's avatar
Tiago Peixoto committed
86
    if not (isinstance(obj, collections.Callable) or hasattr(obj, '__argspec_is_invalid_')): return
87
88
89
90
    if not hasattr(obj, '__doc__'): return

    doc = SphinxDocString(pydoc.getdoc(obj))
    if doc['Signature']:
Tiago Peixoto's avatar
Tiago Peixoto committed
91
92
        sig = re.sub("^[^(]*", "", doc['Signature'])
        return sig, ''
93
94
95
96

def setup(app, get_doc_object_=get_doc_object):
    global get_doc_object
    get_doc_object = get_doc_object_
97

98
    app.connect('autodoc-process-docstring', mangle_docstrings)
99
100
101
102
103
104
105
106
    app.connect('autodoc-process-signature', mangle_signature)
    app.add_config_value('numpydoc_edit_link', None, False)
    app.add_config_value('numpydoc_use_plots', None, False)
    app.add_config_value('numpydoc_show_class_members', True, True)

    # Extra mangling domains
    app.add_domain(NumpyPythonDomain)
    app.add_domain(NumpyCDomain)
107
108

#------------------------------------------------------------------------------
109
# Docstring-mangling domains
110
111
#------------------------------------------------------------------------------

112
113
114
115
116
117
118
119
120
121
122
123
from docutils.statemachine import ViewList
from sphinx.domains.c import CDomain
from sphinx.domains.python import PythonDomain

class ManglingDomainBase(object):
    directive_mangling_map = {}

    def __init__(self, *a, **kw):
        super(ManglingDomainBase, self).__init__(*a, **kw)
        self.wrap_mangling_directives()

    def wrap_mangling_directives(self):
Tiago Peixoto's avatar
Tiago Peixoto committed
124
        for name, objtype in list(self.directive_mangling_map.items()):
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
            self.directives[name] = wrap_mangling_directive(
                self.directives[name], objtype)

class NumpyPythonDomain(ManglingDomainBase, PythonDomain):
    name = 'np'
    directive_mangling_map = {
        'function': 'function',
        'class': 'class',
        'exception': 'class',
        'method': 'function',
        'classmethod': 'function',
        'staticmethod': 'function',
        'attribute': 'attribute',
    }

class NumpyCDomain(ManglingDomainBase, CDomain):
    name = 'np-c'
    directive_mangling_map = {
        'function': 'function',
        'member': 'attribute',
        'macro': 'function',
        'type': 'class',
        'var': 'object',
    }

def wrap_mangling_directive(base_directive, objtype):
    class directive(base_directive):
        def run(self):
            env = self.state.document.settings.env

            name = None
            if self.arguments:
                m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0])
                name = m.group(2).strip()

            if not name:
                name = self.arguments[0]

            lines = list(self.content)
            mangle_docstrings(env.app, objtype, name, None, None, lines)
            self.content = ViewList(lines, self.content.parent)

            return base_directive.run(self)

    return directive
170