25
26
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
27
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
"""Walk module sources and autogenerate RST documentation files.
30
Usage: python autogen.py MODULE_NAME...
32
Requires RST template files 'class.rst.in', 'index.rst.in', 'module.rst.in' and
33
'source.rst.in' somewhere in templates_path as defined in conf.py. Suggested to
34
be used with the 'autoclean' extension. Together, this script, the templates
35
and the 'autoclean' extension should provide decent and complete API
36
documentation automatically generated from source code with verbose docstrings
37
written in reStructuredText markup. Additional hand-written documentation can
38
be added by editing the 'index.rst.in' template to include introductory text or
39
links to the separate, handwritten files.
30
Walk module sources and autogenerate RST documentation files.
32
Usage: ``python autogen.py MODULE_NAME...``
34
Requires RST template files ``class.rst.in``, ``index.rst.in``,
35
``module.rst.in`` and ``source.rst.in`` somewhere in ``templates_path`` as
36
defined in ``conf.py``. Suggested to be used with the ``autoclean`` extension.
37
Together, this script, the templates and the ``autoclean`` extension should
38
provide decent and complete API documentation automatically generated from
39
source code with verbose docstrings written in reStructuredText markup.
40
Additional hand-written documentation can be added by editing the
41
``index.rst.in`` template to include introductory text or links to the
42
separate, handwritten files.
97
99
source_include=get_source_include(module))
99
101
def filter_members(members):
100
"""Return subset of members to appear in documentation."""
102
"""Return subset of `members` to appear in documentation."""
101
103
if not conf.private_members:
102
104
members = [x for x in members
103
105
if (not x.startswith("_") or
104
106
x in conf.include_members)]
105
108
members = set(members) - set(conf.exclude_members)
106
members = [x for x in members
107
if not x.endswith(tuple(conf.exclude_members_endswith))]
109
exclude = tuple(conf.exclude_members_endswith)
110
members = [x for x in members if not x.endswith(exclude)]
108
111
return sorted(list(members))
110
113
def get_anchestors(cls):
112
115
if not cls.__bases__:
114
117
base = cls.__bases__[0]
115
name = "%s.%s" % (base.__module__, base.__name__)
118
name = "{}.{}".format(base.__module__, base.__name__)
116
119
name = name.replace("__builtin__.", "")
117
120
return [name] + get_anchestors(base)
119
122
def get_classes(module):
120
"""Return list of names of classes defined in module."""
123
"""Return list of names of classes defined in `module`."""
121
124
is_class = lambda x: inspect.isclass(getattr(module, x))
122
classes = filter(is_class, module.__dict__.keys())
125
classes = list(filter(is_class, list(module.__dict__.keys())))
123
126
top = module.__name__.split(".")[0]
124
for i in reversed(range(len(classes))):
127
for i in list(reversed(range(len(classes)))):
125
128
candidate = getattr(module, classes[i])
126
129
parent = inspect.getmodule(candidate)
127
130
if (parent is None or
130
133
return filter_members(classes)
132
135
def get_functions(module):
133
"""Return list of names of functions defined in module."""
136
"""Return list of names of functions defined in `module`."""
134
137
is_function = lambda x: inspect.isfunction(getattr(module, x))
135
functions = filter(is_function, module.__dict__.keys())
138
functions = list(filter(is_function, list(module.__dict__.keys())))
136
139
top = module.__name__.split(".")[0]
137
for i in reversed(range(len(functions))):
140
for i in list(reversed(range(len(functions)))):
138
141
candidate = getattr(module, functions[i])
139
142
parent = inspect.getmodule(candidate)
140
143
if (parent is None or
143
146
return filter_members(functions)
145
148
def get_methods(cls):
146
"""Return list of names of methods defined in cls."""
147
is_method = lambda x: inspect.ismethod(getattr(cls, x))
148
methods = filter(is_method, cls.__dict__.keys())
149
"""Return list of names of methods defined in `cls`."""
150
is_method = lambda x: inspect.isfunction(getattr(cls, x))
151
methods = list(filter(is_method, list(cls.__dict__.keys())))
149
152
return filter_members(methods)
151
154
def get_modules(module):
152
"""Return list of names of modules defined in module."""
155
"""Return list of names of modules defined in `module`."""
153
156
is_module = lambda x: inspect.ismodule(getattr(module, x))
154
modules = filter(is_module, module.__dict__.keys())
157
modules = list(filter(is_module, list(module.__dict__.keys())))
155
158
top = module.__name__.split(".")[0]
156
for i in reversed(range(len(modules))):
159
for i in list(reversed(range(len(modules)))):
157
160
candidate = getattr(module, modules[i])
158
161
parent = inspect.getmodule(candidate)
159
162
if (parent is None or
173
176
dotted_name = obj.__module__
174
177
if dotted_name is None:
176
return "/%s/%s_source" % (conf.autogen_output_path, dotted_name)
179
return "/{}/{}_source".format(conf.autogen_output_path, dotted_name)
178
181
def get_source_fname(obj):
179
"""Return obj filename relative to project root or None."""
182
"""Return `obj` filename relative to project root or ``None``."""
180
183
path = get_source_path(obj)
191
194
def get_source_include(obj):
192
"""Return obj filename relative to documentation root or None."""
195
"""Return `obj` filename relative to documentation root or ``None``."""
193
196
fname = get_source_fname(obj)
194
197
if fname is None:
196
return "/%s" % os.path.join(conf.project_root, fname)
199
return "/{}".format(os.path.join(conf.project_root, fname))
198
201
def get_source_path(obj):
199
"""Return absolute path to file obj is defined in or None."""
202
"""Return absolute path to file `obj` is defined in or ``None``."""
201
204
path = inspect.getfile(obj)
202
205
except TypeError:
219
222
__import__(module))
221
224
def write_template(name_in, names_out, **kwargs):
222
"""Write RST-template to file based in values in kwargs."""
225
"""Write RST-template to file based in values in `kwargs`."""
223
226
directory = os.path.dirname(__file__)
224
basename = "%s.rst.in" % name_in
227
basename = "{}.rst.in".format(name_in)
225
228
for template_dir in conf.templates_path:
226
229
template_dir = os.path.join(directory, template_dir)
227
230
template_file = os.path.join(template_dir, basename)
228
231
if os.path.isfile(template_file): break
229
text = codecs.open(template_file, "r", conf.source_encoding).read()
232
encoding = conf.source_encoding
233
text = open(template_file, "r", encoding=encoding).read()
230
234
template = jinja2.Template(text)
231
for key, value in kwargs.items():
232
underline = "%s_double_underline" % key
235
for key, value in list(kwargs.items()):
236
underline = "{}_double_underline".format(key)
233
237
kwargs[underline] = "=" * len(str(value))
234
underline = "%s_single_underline" % key
238
underline = "{}_single_underline".format(key)
235
239
kwargs[underline] = "-" * len(str(value))
236
240
text = template.render(**kwargs)
237
241
if not text.endswith(("\n", "\r")):
238
text = "%s%s" % (text, os.linesep)
242
text = "".join((text, os.linesep))
239
243
if name_in == "index":
240
return codecs.open("index.rst", "w", conf.source_encoding).write(text)
244
encoding = conf.source_encoding
245
return open("index.rst", "w", encoding=encoding).write(text)
241
246
names_out = list(names_out)
242
names_out[-1] = "%s.rst" % names_out[-1]
247
names_out[-1] = "{}.rst".format(names_out[-1])
243
248
path = os.path.join(directory,
244
249
conf.autogen_output_path,
245
250
".".join(names_out))
247
252
if not os.path.isdir(os.path.dirname(path)):
248
253
os.makedirs(os.path.dirname(path))
249
codecs.open(path, "w", conf.source_encoding).write(text)
254
encoding = conf.source_encoding
255
open(path, "w", encoding=encoding).write(text)
252
257
if __name__ == "__main__":
253
258
main(sys.argv[1:])