summaryrefslogtreecommitdiff
path: root/doc/sphinxext/autosummary_generate.py
blob: 9e2c6c7712c87d29ed4a39c60d039ee5ec1adfaa (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
#!/usr/bin/env python
r"""
autosummary_generate.py OPTIONS FILES

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:
            ./ext/autosummary_generate.py -o source/generated source/*.rst

"""
import glob, re, inspect, os, optparse
from autosummary import import_by_name

try:
    from phantom_import import import_phantom_module
except ImportError:
    import_phantom_module = lambda x: x

def main():
    p = optparse.OptionParser(__doc__.strip())
    p.add_option("-p", "--phantom", action="store", type="string",
                 dest="phantom", default=None,
                 help="Phantom import modules from a file")
    p.add_option("-o", "--output-dir", action="store", type="string",
                 dest="output_dir", default=None,
                 help=("Write all output files to the given directory (instead "
                       "of writing them as specified in the autosummary:: "
                       "directives)"))
    options, args = p.parse_args()

    if len(args) == 0:
        p.error("wrong number of arguments")

    if options.phantom and os.path.isfile(options.phantom):
        import_phantom_module(options.phantom)

    # read
    names = {}
    for name, loc in get_documented(args).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()):
        if options.output_dir is not None:
            path = options.output_dir
        
        if not os.path.isdir(path):
            os.makedirs(path)

        try:
            obj, name = import_by_name(name)
        except ImportError, e:
            print "Failed to import '%s': %s" % (name, e)
            continue

        fn = os.path.join(path, '%s.rst' % name)

        if os.path.exists(fn):
            # skip
            continue

        f = open(fn, 'w')

        try:
            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.ismodule(obj):
                f.write(format_modulemember(name, 'automodule'))
            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)

def get_documented(filenames):
    """
    Find out what items are documented in source/*.rst
    
    Returns
    -------
    documented : dict of list of (filename, title, keyword, toctree)
        Dictionary whose 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.

    """
    title_underline_re = re.compile("^[-=*_^#]{3,}\s*$")
    autodoc_re = re.compile(".. 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*$')
    
    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()
                    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

if __name__ == "__main__":
    main()