diff --git a/sphinx-autogen.py b/sphinx-autogen.py new file mode 100755 index 000000000..494f4d85a --- /dev/null +++ b/sphinx-autogen.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Sphinx - Python documentation toolchain + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2007-2009 by Georg Brandl. + :license: BSD. +""" + +import sys + +if __name__ == '__main__': + from sphinx.ext.autosummary.generate import main + sys.exit(main(sys.argv)) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index f26c46769..8775fe868 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -1,80 +1,73 @@ +# -*- coding: utf-8 -*- """ -=========== -autosummary -=========== + sphinx.ext.autosummary + ~~~~~~~~~~~~~~~~~~~~~~ -Sphinx extension that adds an autosummary:: directive, which can be -used to generate function/method/attribute/etc. summary lists, similar -to those output eg. by Epydoc and other API doc generation tools. + Sphinx extension that adds an autosummary:: directive, which can be + used to generate function/method/attribute/etc. summary lists, similar + to those output eg. by Epydoc and other API doc generation tools. -An :autolink: role is also provided. + An :autolink: role is also provided. -autosummary directive ---------------------- + autosummary directive + --------------------- -The autosummary directive has the form:: + The autosummary directive has the form:: - .. autosummary:: - :nosignatures: - :toctree: generated/ - - module.function_1 - module.function_2 - ... + .. autosummary:: + :nosignatures: + :toctree: generated/ -and it generates an output table (containing signatures, optionally) + module.function_1 + module.function_2 + ... - ======================== ============================================= - module.function_1(args) Summary line from the docstring of function_1 - module.function_2(args) Summary line from the docstring - ... - ======================== ============================================= + and it generates an output table (containing signatures, optionally) -If the :toctree: option is specified, files matching the function names -are inserted to the toctree with the given prefix: + ======================== ============================================= + module.function_1(args) Summary line from the docstring of function_1 + module.function_2(args) Summary line from the docstring + ... + ======================== ============================================= - generated/module.function_1 - generated/module.function_2 - ... + If the :toctree: option is specified, files matching the function names + are inserted to the toctree with the given prefix: -Note: The file names contain the module:: or currentmodule:: prefixes. + generated/module.function_1 + generated/module.function_2 + ... -.. seealso:: autosummary_generate.py + Note: The file names contain the module:: or currentmodule:: prefixes. + + .. seealso:: autosummary_generate.py -autolink role -------------- + autolink role + ------------- -The autolink role functions as ``:obj:`` when the name referred can be -resolved to a Python object, and otherwise it becomes simple emphasis. -This can be used as the default role to make links 'smart'. + The autolink role functions as ``:obj:`` when the name referred can be + resolved to a Python object, and otherwise it becomes simple emphasis. + This can be used as the default role to make links 'smart'. + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. """ -import sys, os, posixpath, re + +import os +import re +import sys +import inspect +import posixpath from docutils.parsers.rst import directives from docutils.statemachine import ViewList from docutils import nodes -import sphinx.addnodes, sphinx.roles, sphinx.builder +from sphinx import addnodes, roles from sphinx.util import patfilter -import inspect -def setup(app): - app.add_directive('autosummary', autosummary_directive, True, (0, 0, False), - toctree=directives.unchanged, - nosignatures=directives.flag) - app.add_role('autolink', autolink_role) - - app.add_node(autosummary_toc, - html=(autosummary_toc_visit_html, autosummary_toc_depart_noop), - latex=(autosummary_toc_visit_latex, autosummary_toc_depart_noop)) - app.connect('doctree-read', process_autosummary_toc) - -#------------------------------------------------------------------------------ -# autosummary_toc node -#------------------------------------------------------------------------------ +# -- autosummary_toc node ------------------------------------------------------ class autosummary_toc(nodes.comment): pass @@ -83,7 +76,6 @@ def process_autosummary_toc(app, doctree): """ Insert items described in autosummary:: to the TOC tree, but do not generate the toctree:: list. - """ env = app.builder.env crawled = {} @@ -92,7 +84,7 @@ def process_autosummary_toc(app, doctree): for j, subnode in enumerate(node): try: if (isinstance(subnode, autosummary_toc) - and isinstance(subnode[0], sphinx.addnodes.toctree)): + and isinstance(subnode[0], addnodes.toctree)): env.note_toctree(env.docname, subnode[0]) continue except IndexError: @@ -104,19 +96,18 @@ def process_autosummary_toc(app, doctree): crawl_toc(doctree) def autosummary_toc_visit_html(self, node): - """Hide autosummary toctree list in HTML output""" + """Hide autosummary toctree list in HTML output.""" raise nodes.SkipNode def autosummary_toc_visit_latex(self, node): - """Show autosummary toctree (= put the referenced pages here) in Latex""" + """Show autosummary toctree (= put the referenced pages here) in Latex.""" pass def autosummary_toc_depart_noop(self, node): pass -#------------------------------------------------------------------------------ -# .. autosummary:: -#------------------------------------------------------------------------------ + +# -- .. autosummary:: ---------------------------------------------------------- def autosummary_directive(dirname, arguments, options, content, lineno, content_offset, block_text, state, state_machine): @@ -155,7 +146,7 @@ def autosummary_directive(dirname, arguments, options, content, lineno, line=lineno)) docnames.append(docname) - tocnode = sphinx.addnodes.toctree() + tocnode = addnodes.toctree() tocnode['includefiles'] = docnames tocnode['maxdepth'] = -1 tocnode['glob'] = None @@ -165,6 +156,7 @@ def autosummary_directive(dirname, arguments, options, content, lineno, else: return warnings + [node] + def get_autosummary(names, state, no_signatures=False): """ Generate a proper table node for autosummary:: directive. @@ -175,10 +167,10 @@ def get_autosummary(names, state, no_signatures=False): Names of Python objects to be imported and added to the table. document : document Docutils document object - + """ document = state.document - + real_names = {} warnings = [] @@ -209,14 +201,14 @@ def get_autosummary(names, state, no_signatures=False): except ImportError: warnings.append(document.reporter.warning( 'failed to import %s' % name)) - append_row(":obj:`%s`" % name, "") + append_row(':obj:`%s`' % name, '') continue real_names[name] = real_name - title = "" + title = '' qualifier = 'obj' - col1 = ":"+qualifier+":`%s <%s>`" % (name, real_name) + col1 = ':'+qualifier+':`%s <%s>`' % (name, real_name) col2 = title append_row(col1, col2) @@ -240,7 +232,7 @@ def import_by_name(name, prefixes=[None]): The imported object name Name of the imported object (useful if `prefixes` was used) - + """ for prefix in prefixes: try: @@ -254,9 +246,18 @@ def import_by_name(name, prefixes=[None]): raise ImportError def _import_by_name(name): - """Import a Python object given its full name""" + """Import a Python object given its full name.""" try: + # try first interpret `name` as MODNAME.OBJ name_parts = name.split('.') + try: + modname = '.'.join(name_parts[:-1]) + __import__(modname) + return getattr(sys.modules[modname], name_parts[-1]) + except (ImportError, IndexError, AttributeError): + pass + + # ... then as MODNAME, MODNAME.OBJ1, MODNAME.OBJ1.OBJ2, ... last_j = 0 modname = None for j in reversed(range(1, len(name_parts)+1)): @@ -279,20 +280,19 @@ def _import_by_name(name): except (ValueError, ImportError, AttributeError, KeyError), e: raise ImportError(e) -#------------------------------------------------------------------------------ -# :autolink: (smart default role) -#------------------------------------------------------------------------------ + +# -- :autolink: (smart default role) ------------------------------------------- def autolink_role(typ, rawtext, etext, lineno, inliner, options={}, content=[]): """ Smart linking role. - Expands to ":obj:`text`" if `text` is an object that can be imported; - otherwise expands to "*text*". + Expands to ':obj:`text`' if `text` is an object that can be imported; + otherwise expands to '*text*'. """ - r = sphinx.roles.xfileref_role('obj', rawtext, etext, lineno, inliner, - options, content) + r = roles.xfileref_role('obj', rawtext, etext, lineno, inliner, + options, content) pnode = r[0][0] prefixes = [None] @@ -304,3 +304,15 @@ def autolink_role(typ, rawtext, etext, lineno, inliner, r[0][0] = nodes.emphasis(rawtext, content[0].astext(), classes=content['classes']) return r + + +def setup(app): + app.add_directive('autosummary', autosummary_directive, True, (0, 0, False), + toctree=directives.unchanged, + nosignatures=directives.flag) + app.add_role('autolink', autolink_role) + + app.add_node(autosummary_toc, + html=(autosummary_toc_visit_html, autosummary_toc_depart_noop), + latex=(autosummary_toc_visit_latex, autosummary_toc_depart_noop)) + app.connect('doctree-read', process_autosummary_toc) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index ce9e4c2d8..178a2870a 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -1,85 +1,91 @@ +# -*- coding: utf-8 -*- """ -autosummary_generate.py OPTIONS FILES + sphinx.ext.autosummary.generate + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Generate automatic RST source files for items referred to in -autosummary:: directives. + 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. + Each generated RST file contains a single auto*:: directive which + extracts the docstring of the referred item. -Example Makefile rule:: + Example Makefile rule:: - generate: - sphinx-autogen source/*.rst source/generated + generate: + sphinx-autogen source/*.rst source/generated + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. """ -import glob, re, inspect, os, optparse +import os +import re +import sys +import glob +import inspect + +from jinja2 import Environment, PackageLoader + from sphinx.ext.autosummary import import_by_name +from sphinx.util import ensuredir -from jinja import Environment, PackageLoader -env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates')) +# create our own templating environment, for module template only +env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates')) -def main(): - p = optparse.OptionParser(__doc__.strip()) - options, args = p.parse_args() - - if len(args) <2: - p.error("wrong number of arguments") - print 'generating docs from:', args[:-1] - generate_autosummary_docs(args[:-1], args[-1]) - -def generate_autosummary_docs(source_dir, output_dir): +def generate_autosummary_docs(sources, output_dir=None): # read names = {} - for name, loc in get_documented(source_dir).items(): + 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 - - if not os.path.isdir(path): - os.makedirs(path) + if output_dir is not None: + path = output_dir + + ensuredir(path) try: obj, name = import_by_name(name) except ImportError, e: - print "Failed to import '%s': %s" % (name, e) + print >>sys.stderr, 'Failed to import %r: %s' % (name, e) continue fn = os.path.join(path, '%s.rst' % name) - - if os.path.exists(fn): - # skip + # skip it if it exists + if os.path.isfile(fn): continue f = open(fn, 'w') - try: - if inspect.ismodule(obj): - tmpl = env.get_template('module.html') - 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, - functions=functions, - classes=classes, - exceptions=exceptions, + 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, + functions=functions, + classes=classes, + exceptions=exceptions, len_functions=len(functions), len_classes=len(classes), - len_exceptions=len(exceptions) - - ) + 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')) @@ -96,40 +102,40 @@ def generate_autosummary_docs(source_dir, output_dir): 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) + 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) + 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 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. + 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. """ - - 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 @@ -150,32 +156,34 @@ def get_documented(filenames): 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) + 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": + 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)) + (filename, current_title, 'auto' + m.group(1), None)) continue m = title_underline_re.match(line) @@ -191,5 +199,17 @@ def get_documented(filenames): last_line = line return documented -if __name__ == "__main__": + +def main(args=None): + if args is None: + args = sys.argv[1:] + + if len(args) < 2: + print >>sys.stderr, 'usage: %s sourcefile ... outputdir' % sys.argv[0] + + print 'generating docs from:', ', '.join(args[:-1]) + generate_autosummary_docs(args[:-1], args[-1]) + + +if __name__ == '__main__': main() diff --git a/sphinx/ext/autosummary/templates/module.html b/sphinx/ext/autosummary/templates/module similarity index 100% rename from sphinx/ext/autosummary/templates/module.html rename to sphinx/ext/autosummary/templates/module