summaryrefslogtreecommitdiff
path: root/sphinx/ext/autosummary/generate.py
blob: 0a1e0406d044b352be109fdc7c2fed7141d8ebd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# -*- coding: utf-8 -*-
"""
    sphinx.ext.autosummary.generate
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Usable as a library or script to generate automatic RST source files for
    items referred to in autosummary:: directives.

    Each generated RST file contains a single auto*:: directive which
    extracts the docstring of the referred item.

    Example Makefile rule::

       generate:
               sphinx-autogen source/*.rst source/generated

    :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""
import os
import re
import sys
import getopt
import inspect

from jinja2 import Environment, PackageLoader

from sphinx.ext.autosummary import import_by_name
from sphinx.util import ensuredir

# create our own templating environment, for module template only
env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates'))


def _simple_info(msg):
    print msg

def _simple_warn(msg):
    print >>sys.stderr, 'WARNING: ' + msg

def generate_autosummary_docs(sources, output_dir=None, suffix=None,
                              warn=_simple_warn, info=_simple_info):
    info('generating autosummary for: %s' % ', '.join(sources))
    if output_dir:
        info('writing to %s' % output_dir)
    # read
    names = {}
    for name, loc in get_documented(sources).items():
        for (filename, sec_title, keyword, toctree) in loc:
            if toctree is not None:
                path = os.path.join(os.path.dirname(filename), toctree)
                names[name] = os.path.abspath(path)

    # write
    for name, path in sorted(names.items()):
        path = output_dir or path
        ensuredir(path)

        try:
            obj, name = import_by_name(name)
        except ImportError, e:
            warn('failed to import %r: %s' % (name, e))
            continue

        fn = os.path.join(path, name + (suffix or '.rst'))
        # skip it if it exists
        if os.path.isfile(fn):
            continue

        f = open(fn, 'w')

        try:
            if inspect.ismodule(obj):
                # XXX replace this with autodoc's API?
                tmpl = env.get_template('module')
                functions = [getattr(obj, item).__name__
                             for item in dir(obj)
                             if inspect.isfunction(getattr(obj, item))]
                classes = [getattr(obj, item).__name__
                           for item in dir(obj)
                           if inspect.isclass(getattr(obj, item))
                           and not issubclass(getattr(obj, item), Exception)]
                exceptions = [getattr(obj, item).__name__
                              for item in dir(obj)
                              if inspect.isclass(getattr(obj, item))
                              and issubclass(getattr(obj, item), Exception)]
                rendered = tmpl.render(name=name,
                                       underline='='*len(name),
                                       functions=functions,
                                       classes=classes,
                                       exceptions=exceptions,
                                       len_functions=len(functions),
                                       len_classes=len(classes),
                                       len_exceptions=len(exceptions))
                f.write(rendered)
            else:
                f.write('%s\n%s\n\n' % (name, '='*len(name)))

                if inspect.isclass(obj):
                    if issubclass(obj, Exception):
                        f.write(format_modulemember(name, 'autoexception'))
                    else:
                        f.write(format_modulemember(name, 'autoclass'))
                elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj):
                    f.write(format_classmember(name, 'automethod'))
                elif callable(obj):
                    f.write(format_modulemember(name, 'autofunction'))
                elif hasattr(obj, '__get__'):
                    f.write(format_classmember(name, 'autoattribute'))
                else:
                    f.write(format_modulemember(name, 'autofunction'))
        finally:
            f.close()


def format_modulemember(name, directive):
    parts = name.split('.')
    mod, name = '.'.join(parts[:-1]), parts[-1]
    return '.. currentmodule:: %s\n\n.. %s:: %s\n' % (mod, directive, name)


def format_classmember(name, directive):
    parts = name.split('.')
    mod, name = '.'.join(parts[:-2]), '.'.join(parts[-2:])
    return '.. currentmodule:: %s\n\n.. %s:: %s\n' % (mod, directive, name)


title_underline_re = re.compile('^[-=*_^#]{3,}\s*$')
autodoc_re = re.compile(r'.. auto(function|method|attribute|class|exception'
                        '|module)::\s*([A-Za-z0-9_.]+)\s*$')
autosummary_re = re.compile(r'^\.\.\s+autosummary::\s*')
module_re = re.compile(r'^\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$')
autosummary_item_re = re.compile(r'^\s+([_a-zA-Z][a-zA-Z0-9_.]*)\s*')
toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$')

def get_documented(filenames):
    """
    Find out what items are documented in the given filenames.

    Returns a dict of list of (filename, title, keyword, toctree) Keys are
    documented names of objects.  The value is a list of locations where the
    object was documented.  Each location is a tuple of filename, the current
    section title, the name of the directive, and the value of the :toctree:
    argument (if present) of the directive.
    """

    documented = {}

    for filename in filenames:
        current_title = []
        last_line = None
        toctree = None
        current_module = None
        in_autosummary = False

        f = open(filename, 'r')
        for line in f:
            try:
                if in_autosummary:
                    m = toctree_arg_re.match(line)
                    if m:
                        toctree = m.group(1)
                        continue

                    if line.strip().startswith(':'):
                        continue # skip options

                    m = autosummary_item_re.match(line)

                    if m:
                        name = m.group(1).strip()
                        if current_module and \
                               not name.startswith(current_module + '.'):
                            name = '%s.%s' % (current_module, name)
                        documented.setdefault(name, []).append(
                            (filename, current_title, 'autosummary', toctree))
                        continue
                    if line.strip() == '':
                        continue
                    in_autosummary = False

                m = autosummary_re.match(line)
                if m:
                    in_autosummary = True
                    continue

                m = autodoc_re.search(line)
                if m:
                    name = m.group(2).strip()
                    # XXX look in newer generate.py
                    if current_module and \
                           not name.startswith(current_module + '.'):
                        name = '%s.%s' % (current_module, name)
                    if m.group(1) == 'module':
                        current_module = name
                    documented.setdefault(name, []).append(
                        (filename, current_title, 'auto' + m.group(1), None))
                    continue

                m = title_underline_re.match(line)
                if m and last_line:
                    current_title = last_line.strip()
                    continue

                m = module_re.match(line)
                if m:
                    current_module = m.group(2)
                    continue
            finally:
                last_line = line
    return documented


def main(argv=sys.argv):
    usage = 'usage: %s [-o output_dir] [-s suffix] sourcefile ...' % sys.argv[0]
    try:
        opts, args = getopt.getopt(argv[1:], 'o:s:')
    except getopt.error:
        print >>sys.stderr, usage
        return 1

    output_dir = None
    suffix = None
    for opt, val in opts:
        if opt == '-o':
            output_dir = val
        elif opt == '-s':
            suffix = val

    if len(args) < 1:
        print >>sys.stderr, usage
        return 1

    generate_autosummary_docs(args, output_dir, suffix)


if __name__ == '__main__':
    main(sys.argv)