Ticket #12878: class_summary.patch

File class_summary.patch, 33.7 KB (added by hivert, 10 years ago)

Experimental patch not ready for review

  • new file doc/common/autosummary/__init__.py

    # HG changeset patch
    # User Florent Hivert <Florent.Hivert@univ-rouen.fr>
    # Date 1335348720 -7200
    # Node ID a1e968978f23cd2ca60bfcc65eb2575d302bfb5c
    # Parent  fd1a61a6490cd3e0bf11c5348f59da75a876ddb4
    [mq]: class_summary.patch
    
    diff --git a/doc/common/autosummary/__init__.py b/doc/common/autosummary/__init__.py
    new file mode 100644
    - +  
     1# -*- coding: utf-8 -*-
     2"""
     3    sphinx.ext.autosummary
     4    ~~~~~~~~~~~~~~~~~~~~~~
     5
     6    Sphinx extension that adds an autosummary:: directive, which can be
     7    used to generate function/method/attribute/etc. summary lists, similar
     8    to those output eg. by Epydoc and other API doc generation tools.
     9
     10    An :autolink: role is also provided.
     11
     12    autosummary directive
     13    ---------------------
     14
     15    The autosummary directive has the form::
     16
     17        .. autosummary::
     18           :nosignatures:
     19           :toctree: generated/
     20
     21           module.function_1
     22           module.function_2
     23           ...
     24
     25    and it generates an output table (containing signatures, optionally)
     26
     27        ========================  =============================================
     28        module.function_1(args)   Summary line from the docstring of function_1
     29        module.function_2(args)   Summary line from the docstring
     30        ...
     31        ========================  =============================================
     32
     33    If the :toctree: option is specified, files matching the function names
     34    are inserted to the toctree with the given prefix:
     35
     36        generated/module.function_1
     37        generated/module.function_2
     38        ...
     39
     40    Note: The file names contain the module:: or currentmodule:: prefixes.
     41
     42    .. seealso:: autosummary_generate.py
     43
     44
     45    autolink role
     46    -------------
     47
     48    The autolink role functions as ``:obj:`` when the name referred can be
     49    resolved to a Python object, and otherwise it becomes simple emphasis.
     50    This can be used as the default role to make links 'smart'.
     51
     52    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
     53    :license: BSD, see LICENSE for details.
     54"""
     55
     56import os
     57import re
     58import sys
     59import inspect
     60import posixpath
     61
     62from docutils.parsers.rst import directives
     63from docutils.statemachine import ViewList
     64from docutils import nodes
     65
     66from sphinx import addnodes
     67from sphinx.util.compat import Directive
     68
     69
     70# -- autosummary_toc node ------------------------------------------------------
     71
     72class autosummary_toc(nodes.comment):
     73    pass
     74
     75def process_autosummary_toc(app, doctree):
     76    """Insert items described in autosummary:: to the TOC tree, but do
     77    not generate the toctree:: list.
     78    """
     79    env = app.builder.env
     80    crawled = {}
     81    def crawl_toc(node, depth=1):
     82        crawled[node] = True
     83        for j, subnode in enumerate(node):
     84            try:
     85                if (isinstance(subnode, autosummary_toc)
     86                    and isinstance(subnode[0], addnodes.toctree)):
     87                    env.note_toctree(env.docname, subnode[0])
     88                    continue
     89            except IndexError:
     90                continue
     91            if not isinstance(subnode, nodes.section):
     92                continue
     93            if subnode not in crawled:
     94                crawl_toc(subnode, depth+1)
     95    crawl_toc(doctree)
     96
     97def autosummary_toc_visit_html(self, node):
     98    """Hide autosummary toctree list in HTML output."""
     99    raise nodes.SkipNode
     100
     101def autosummary_noop(self, node):
     102    pass
     103
     104
     105# -- autosummary_table node ----------------------------------------------------
     106
     107class autosummary_table(nodes.comment):
     108    pass
     109
     110def autosummary_table_visit_html(self, node):
     111    """Make the first column of the table non-breaking."""
     112    try:
     113        tbody = node[0][0][-1]
     114        for row in tbody:
     115            col1_entry = row[0]
     116            par = col1_entry[0]
     117            for j, subnode in enumerate(list(par)):
     118                if isinstance(subnode, nodes.Text):
     119                    new_text = unicode(subnode.astext())
     120                    new_text = new_text.replace(u" ", u"\u00a0")
     121                    par[j] = nodes.Text(new_text)
     122    except IndexError:
     123        pass
     124
     125
     126# -- autodoc integration -------------------------------------------------------
     127
     128class FakeDirective:
     129    env = {}
     130    genopt = {}
     131
     132def get_documenter(obj, parent):
     133    """Get an autodoc.Documenter class suitable for documenting the given
     134    object.
     135
     136    *obj* is the Python object to be documented, and *parent* is an
     137    another Python object (e.g. a module or a class) to which *obj*
     138    belongs to.
     139    """
     140    from sage_autodoc import AutoDirective, DataDocumenter, \
     141         ModuleDocumenter
     142
     143    if inspect.ismodule(obj):
     144        # ModuleDocumenter.can_document_member always returns False
     145        return ModuleDocumenter
     146
     147    # Construct a fake documenter for *parent*
     148    if parent is not None:
     149        parent_doc_cls = get_documenter(parent, None)
     150    else:
     151        parent_doc_cls = ModuleDocumenter
     152
     153    if hasattr(parent, '__name__'):
     154        parent_doc = parent_doc_cls(FakeDirective(), parent.__name__)
     155    else:
     156        parent_doc = parent_doc_cls(FakeDirective(), "")
     157
     158    # Get the corrent documenter class for *obj*
     159    classes = [cls for cls in AutoDirective._registry.values()
     160               if cls.can_document_member(obj, '', False, parent_doc)]
     161    if classes:
     162        classes.sort(key=lambda cls: cls.priority)
     163        return classes[-1]
     164    else:
     165        return DataDocumenter
     166
     167
     168# -- .. autosummary:: ----------------------------------------------------------
     169
     170class Autosummary(Directive):
     171    """
     172    Pretty table containing short signatures and summaries of functions etc.
     173
     174    autosummary can also optionally generate a hidden toctree:: node.
     175    """
     176
     177    required_arguments = 0
     178    optional_arguments = 0
     179    final_argument_whitespace = False
     180    has_content = True
     181    option_spec = {
     182        'toctree': directives.unchanged,
     183        'nosignatures': directives.flag,
     184        'template': directives.unchanged,
     185    }
     186
     187    def warn(self, msg):
     188        self.warnings.append(self.state.document.reporter.warning(
     189            msg, line=self.lineno))
     190
     191    def run(self):
     192        self.env = env = self.state.document.settings.env
     193        self.genopt = {}
     194        self.warnings = []
     195
     196        names = [x.strip().split()[0] for x in self.content
     197                 if x.strip() and re.search(r'^[~a-zA-Z_]', x.strip()[0])]
     198        items = self.get_items(names)
     199        nodes = self.get_table(items)
     200
     201        if 'toctree' in self.options:
     202            suffix = env.config.source_suffix
     203            dirname = posixpath.dirname(env.docname)
     204
     205            tree_prefix = self.options['toctree'].strip()
     206            docnames = []
     207            for name, sig, summary, real_name in items:
     208                docname = posixpath.join(tree_prefix, real_name)
     209                if docname.endswith(suffix):
     210                    docname = docname[:-len(suffix)]
     211                docname = posixpath.normpath(posixpath.join(dirname, docname))
     212                if docname not in env.found_docs:
     213                    self.warn('toctree references unknown document %r'
     214                              % docname)
     215                docnames.append(docname)
     216
     217            tocnode = addnodes.toctree()
     218            tocnode['includefiles'] = docnames
     219            tocnode['entries'] = [(None, docname) for docname in docnames]
     220            tocnode['maxdepth'] = -1
     221            tocnode['glob'] = None
     222
     223            tocnode = autosummary_toc('', '', tocnode)
     224            nodes.append(tocnode)
     225
     226        return self.warnings + nodes
     227
     228    def get_items(self, names):
     229        """Try to import the given names, and return a list of
     230        ``[(name, signature, summary_string, real_name), ...]``.
     231        """
     232        env = self.state.document.settings.env
     233
     234        prefixes = get_import_prefixes_from_env(env)
     235
     236        items = []
     237
     238        max_item_chars = 50
     239
     240        for name in names:
     241            display_name = name
     242            if name.startswith('~'):
     243                name = name[1:]
     244                display_name = name.split('.')[-1]
     245
     246            try:
     247                real_name, obj, parent = import_by_name(name, prefixes=prefixes)
     248            except ImportError:
     249                self.warn('failed to import %s' % name)
     250                items.append((name, '', '', name))
     251                continue
     252
     253            # NB. using real_name here is important, since Documenters
     254            #     handle module prefixes slightly differently
     255            documenter = get_documenter(obj, parent)(self, real_name)
     256            if not documenter.parse_name():
     257                self.warn('failed to parse name %s' % real_name)
     258                items.append((display_name, '', '', real_name))
     259                continue
     260            if not documenter.import_object():
     261                self.warn('failed to import object %s' % real_name)
     262                items.append((display_name, '', '', real_name))
     263                continue
     264
     265            # -- Grab the signature
     266
     267            sig = documenter.format_signature()
     268            if not sig:
     269                sig = ''
     270            else:
     271                max_chars = max(10, max_item_chars - len(display_name))
     272                sig = mangle_signature(sig, max_chars=max_chars)
     273                sig = sig.replace('*', r'\*')
     274
     275            # -- Grab the summary
     276
     277            doc = list(documenter.process_doc(documenter.get_doc()))
     278
     279            while doc and not doc[0].strip():
     280                doc.pop(0)
     281            m = re.search(r"^([A-Z][^A-Z]*?\.\s)", " ".join(doc).strip())
     282            if m:
     283                summary = m.group(1).strip()
     284            elif doc:
     285                summary = doc[0].strip()
     286            else:
     287                summary = ''
     288
     289            items.append((display_name, sig, summary, real_name))
     290
     291        return items
     292
     293    def get_table(self, items):
     294        """Generate a proper list of table nodes for autosummary:: directive.
     295
     296        *items* is a list produced by :meth:`get_items`.
     297        """
     298        table_spec = addnodes.tabular_col_spec()
     299        table_spec['spec'] = 'll'
     300
     301        table = autosummary_table('')
     302        real_table = nodes.table('', classes=['longtable'])
     303        table.append(real_table)
     304        group = nodes.tgroup('', cols=2)
     305        real_table.append(group)
     306        group.append(nodes.colspec('', colwidth=10))
     307        group.append(nodes.colspec('', colwidth=90))
     308        body = nodes.tbody('')
     309        group.append(body)
     310
     311        def append_row(*column_texts):
     312            row = nodes.row('')
     313            for text in column_texts:
     314                node = nodes.paragraph('')
     315                vl = ViewList()
     316                vl.append(text, '<autosummary>')
     317                self.state.nested_parse(vl, 0, node)
     318                try:
     319                    if isinstance(node[0], nodes.paragraph):
     320                        node = node[0]
     321                except IndexError:
     322                    pass
     323                row.append(nodes.entry('', node))
     324            body.append(row)
     325
     326        for name, sig, summary, real_name in items:
     327            qualifier = 'obj'
     328            if 'nosignatures' not in self.options:
     329                col1 = ':%s:`%s <%s>`\ %s' % (qualifier, name, real_name, sig)
     330            else:
     331                col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name)
     332            col2 = summary
     333            append_row(col1, col2)
     334
     335        return [table_spec, table]
     336
     337def mangle_signature(sig, max_chars=30):
     338    """Reformat a function signature to a more compact form."""
     339    s = re.sub(r"^\((.*)\)$", r"\1", sig).strip()
     340
     341    # Strip strings (which can contain things that confuse the code below)
     342    s = re.sub(r"\\\\", "", s)
     343    s = re.sub(r"\\'", "", s)
     344    s = re.sub(r"'[^']*'", "", s)
     345
     346    # Parse the signature to arguments + options
     347    args = []
     348    opts = []
     349
     350    opt_re = re.compile(r"^(.*, |)([a-zA-Z0-9_*]+)=")
     351    while s:
     352        m = opt_re.search(s)
     353        if not m:
     354            # The rest are arguments
     355            args = s.split(', ')
     356            break
     357
     358        opts.insert(0, m.group(2))
     359        s = m.group(1)[:-2]
     360
     361    # Produce a more compact signature
     362    sig = limited_join(", ", args, max_chars=max_chars-2)
     363    if opts:
     364        if not sig:
     365            sig = "[%s]" % limited_join(", ", opts, max_chars=max_chars-4)
     366        elif len(sig) < max_chars - 4 - 2 - 3:
     367            sig += "[, %s]" % limited_join(", ", opts,
     368                                           max_chars=max_chars-len(sig)-4-2)
     369
     370    return u"(%s)" % sig
     371
     372def limited_join(sep, items, max_chars=30, overflow_marker="..."):
     373    """Join a number of strings to one, limiting the length to *max_chars*.
     374
     375    If the string overflows this limit, replace the last fitting item by
     376    *overflow_marker*.
     377
     378    Returns: joined_string
     379    """
     380    full_str = sep.join(items)
     381    if len(full_str) < max_chars:
     382        return full_str
     383
     384    n_chars = 0
     385    n_items = 0
     386    for j, item in enumerate(items):
     387        n_chars += len(item) + len(sep)
     388        if n_chars < max_chars - len(overflow_marker):
     389            n_items += 1
     390        else:
     391            break
     392
     393    return sep.join(list(items[:n_items]) + [overflow_marker])
     394
     395# -- Importing items -----------------------------------------------------------
     396
     397def get_import_prefixes_from_env(env):
     398    """
     399    Obtain current Python import prefixes (for `import_by_name`)
     400    from ``document.env``
     401    """
     402    prefixes = [None]
     403
     404    currmodule = env.temp_data.get('py:module')
     405    if currmodule:
     406        prefixes.insert(0, currmodule)
     407
     408    currclass = env.temp_data.get('py:class')
     409    if currclass:
     410        if currmodule:
     411            prefixes.insert(0, currmodule + "." + currclass)
     412        else:
     413            prefixes.insert(0, currclass)
     414
     415    return prefixes
     416
     417def import_by_name(name, prefixes=[None]):
     418    """Import a Python object that has the given *name*, under one of the
     419    *prefixes*.  The first name that succeeds is used.
     420    """
     421    tried = []
     422    for prefix in prefixes:
     423        try:
     424            if prefix:
     425                prefixed_name = '.'.join([prefix, name])
     426            else:
     427                prefixed_name = name
     428            obj, parent = _import_by_name(prefixed_name)
     429            return prefixed_name, obj, parent
     430        except ImportError:
     431            tried.append(prefixed_name)
     432    raise ImportError('no module named %s' % ' or '.join(tried))
     433
     434def _import_by_name(name):
     435    """Import a Python object given its full name."""
     436    try:
     437        name_parts = name.split('.')
     438
     439        # try first interpret `name` as MODNAME.OBJ
     440        modname = '.'.join(name_parts[:-1])
     441        if modname:
     442            try:
     443                __import__(modname)
     444                mod = sys.modules[modname]
     445                return getattr(mod, name_parts[-1]), mod
     446            except (ImportError, IndexError, AttributeError):
     447                pass
     448
     449        # ... then as MODNAME, MODNAME.OBJ1, MODNAME.OBJ1.OBJ2, ...
     450        last_j = 0
     451        modname = None
     452        for j in reversed(range(1, len(name_parts)+1)):
     453            last_j = j
     454            modname = '.'.join(name_parts[:j])
     455            try:
     456                __import__(modname)
     457            except ImportError:
     458                continue
     459            if modname in sys.modules:
     460                break
     461
     462        if last_j < len(name_parts):
     463            parent = None
     464            obj = sys.modules[modname]
     465            for obj_name in name_parts[last_j:]:
     466                parent = obj
     467                obj = getattr(obj, obj_name)
     468            return obj, parent
     469        else:
     470            return sys.modules[modname], None
     471    except (ValueError, ImportError, AttributeError, KeyError), e:
     472        raise ImportError(*e.args)
     473
     474
     475# -- :autolink: (smart default role) -------------------------------------------
     476
     477def autolink_role(typ, rawtext, etext, lineno, inliner,
     478                  options={}, content=[]):
     479    """Smart linking role.
     480
     481    Expands to ':obj:`text`' if `text` is an object that can be imported;
     482    otherwise expands to '*text*'.
     483    """
     484    env = inliner.document.settings.env
     485    r = env.get_domain('py').role('obj')(
     486        'obj', rawtext, etext, lineno, inliner, options, content)
     487    pnode = r[0][0]
     488
     489    prefixes = get_import_prefixes_from_env(env)
     490    try:
     491        name, obj, parent = import_by_name(pnode['reftarget'], prefixes)
     492    except ImportError:
     493        content = pnode[0]
     494        r[0][0] = nodes.emphasis(rawtext, content[0].astext(),
     495                                 classes=content['classes'])
     496    return r
     497
     498
     499def process_generate_options(app):
     500    genfiles = app.config.autosummary_generate
     501
     502    ext = app.config.source_suffix
     503
     504    if genfiles and not hasattr(genfiles, '__len__'):
     505        env = app.builder.env
     506        genfiles = [x + ext for x in env.found_docs
     507                    if os.path.isfile(env.doc2path(x))]
     508
     509    if not genfiles:
     510        return
     511
     512    from sphinx.ext.autosummary.generate import generate_autosummary_docs
     513
     514    genfiles = [genfile + (not genfile.endswith(ext) and ext or '')
     515                for genfile in genfiles]
     516
     517    generate_autosummary_docs(genfiles, builder=app.builder,
     518                              warn=app.warn, info=app.info, suffix=ext,
     519                              base_path=app.srcdir)
     520
     521
     522def setup(app):
     523    # I need autodoc
     524    app.setup_extension('sage_autodoc')
     525    app.add_node(autosummary_toc,
     526                 html=(autosummary_toc_visit_html, autosummary_noop),
     527                 latex=(autosummary_noop, autosummary_noop),
     528                 text=(autosummary_noop, autosummary_noop),
     529                 man=(autosummary_noop, autosummary_noop),
     530                 texinfo=(autosummary_noop, autosummary_noop))
     531    app.add_node(autosummary_table,
     532                 html=(autosummary_table_visit_html, autosummary_noop),
     533                 latex=(autosummary_noop, autosummary_noop),
     534                 text=(autosummary_noop, autosummary_noop),
     535                 man=(autosummary_noop, autosummary_noop),
     536                 texinfo=(autosummary_noop, autosummary_noop))
     537    app.add_directive('autosummary', Autosummary)
     538    app.add_role('autolink', autolink_role)
     539    app.connect('doctree-read', process_autosummary_toc)
     540    app.connect('builder-inited', process_generate_options)
     541    app.add_config_value('autosummary_generate', [], True)
  • new file doc/common/autosummary/generate.py

    diff --git a/doc/common/autosummary/generate.py b/doc/common/autosummary/generate.py
    new file mode 100644
    - +  
     1# -*- coding: utf-8 -*-
     2"""
     3    sphinx.ext.autosummary.generate
     4    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     5
     6    Usable as a library or script to generate automatic RST source files for
     7    items referred to in autosummary:: directives.
     8
     9    Each generated RST file contains a single auto*:: directive which
     10    extracts the docstring of the referred item.
     11
     12    Example Makefile rule::
     13
     14       generate:
     15               sphinx-autogen -o source/generated source/*.rst
     16
     17    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
     18    :license: BSD, see LICENSE for details.
     19"""
     20
     21import os
     22import re
     23import sys
     24import pydoc
     25import optparse
     26
     27from jinja2 import FileSystemLoader, TemplateNotFound
     28from jinja2.sandbox import SandboxedEnvironment
     29
     30from sphinx import package_dir
     31from sphinx.ext.autosummary import import_by_name, get_documenter
     32from sphinx.jinja2glue import BuiltinTemplateLoader
     33from sphinx.util.osutil import ensuredir
     34from sphinx.util.inspect import safe_getattr
     35
     36def main(argv=sys.argv):
     37    usage = """%prog [OPTIONS] SOURCEFILE ..."""
     38    p = optparse.OptionParser(usage.strip())
     39    p.add_option("-o", "--output-dir", action="store", type="string",
     40                 dest="output_dir", default=None,
     41                 help="Directory to place all output in")
     42    p.add_option("-s", "--suffix", action="store", type="string",
     43                 dest="suffix", default="rst",
     44                 help="Default suffix for files (default: %default)")
     45    p.add_option("-t", "--templates", action="store", type="string",
     46                 dest="templates", default=None,
     47                 help="Custom template directory (default: %default)")
     48    options, args = p.parse_args(argv[1:])
     49
     50    if len(args) < 1:
     51        p.error('no input files given')
     52
     53    generate_autosummary_docs(args, options.output_dir,
     54                              "." + options.suffix,
     55                              template_dir=options.templates)
     56
     57def _simple_info(msg):
     58    print msg
     59
     60def _simple_warn(msg):
     61    print >> sys.stderr, 'WARNING: ' + msg
     62
     63# -- Generating output ---------------------------------------------------------
     64
     65def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
     66                              warn=_simple_warn, info=_simple_info,
     67                              base_path=None, builder=None, template_dir=None):
     68
     69    showed_sources = list(sorted(sources))
     70    if len(showed_sources) > 20:
     71        showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:]
     72    info('[autosummary] generating autosummary for: %s' %
     73         ', '.join(showed_sources))
     74
     75    if output_dir:
     76        info('[autosummary] writing to %s' % output_dir)
     77
     78    if base_path is not None:
     79        sources = [os.path.join(base_path, filename) for filename in sources]
     80
     81    # create our own templating environment
     82    template_dirs = [os.path.join(package_dir, 'ext',
     83                                  'autosummary', 'templates')]
     84    if builder is not None:
     85        # allow the user to override the templates
     86        template_loader = BuiltinTemplateLoader()
     87        template_loader.init(builder, dirs=template_dirs)
     88    else:
     89        if template_dir:
     90            template_dirs.insert(0, template_dir)
     91        template_loader = FileSystemLoader(template_dirs)
     92    template_env = SandboxedEnvironment(loader=template_loader)
     93
     94    # read
     95    items = find_autosummary_in_files(sources)
     96
     97    # remove possible duplicates
     98    items = dict([(item, True) for item in items]).keys()
     99
     100    # keep track of new files
     101    new_files = []
     102
     103    # write
     104    for name, path, template_name in sorted(items):
     105        if path is None:
     106            # The corresponding autosummary:: directive did not have
     107            # a :toctree: option
     108            continue
     109
     110        path = output_dir or os.path.abspath(path)
     111        ensuredir(path)
     112
     113        try:
     114            name, obj, parent = import_by_name(name)
     115        except ImportError, e:
     116            warn('[autosummary] failed to import %r: %s' % (name, e))
     117            continue
     118
     119        fn = os.path.join(path, name + suffix)
     120
     121        # skip it if it exists
     122        if os.path.isfile(fn):
     123            continue
     124
     125        new_files.append(fn)
     126
     127        f = open(fn, 'w')
     128
     129        try:
     130            doc = get_documenter(obj, parent)
     131
     132            if template_name is not None:
     133                template = template_env.get_template(template_name)
     134            else:
     135                try:
     136                    template = template_env.get_template('autosummary/%s.rst'
     137                                                         % doc.objtype)
     138                except TemplateNotFound:
     139                    template = template_env.get_template('autosummary/base.rst')
     140
     141            def get_members(obj, typ, include_public=[]):
     142                items = []
     143                for name in dir(obj):
     144                    try:
     145                        documenter = get_documenter(safe_getattr(obj, name),
     146                                                    obj)
     147                    except AttributeError:
     148                        continue
     149                    if documenter.objtype == typ:
     150                        items.append(name)
     151                public = [x for x in items
     152                          if x in include_public or not x.startswith('_')]
     153                return public, items
     154
     155            ns = {}
     156
     157            if doc.objtype == 'module':
     158                ns['members'] = dir(obj)
     159                ns['functions'], ns['all_functions'] = \
     160                                   get_members(obj, 'function')
     161                ns['classes'], ns['all_classes'] = \
     162                                 get_members(obj, 'class')
     163                ns['exceptions'], ns['all_exceptions'] = \
     164                                   get_members(obj, 'exception')
     165            elif doc.objtype == 'class':
     166                ns['members'] = dir(obj)
     167                ns['methods'], ns['all_methods'] = \
     168                                 get_members(obj, 'method', ['__init__'])
     169                ns['attributes'], ns['all_attributes'] = \
     170                                 get_members(obj, 'attribute')
     171
     172            parts = name.split('.')
     173            if doc.objtype in ('method', 'attribute'):
     174                mod_name = '.'.join(parts[:-2])
     175                cls_name = parts[-2]
     176                obj_name = '.'.join(parts[-2:])
     177                ns['class'] = cls_name
     178            else:
     179                mod_name, obj_name = '.'.join(parts[:-1]), parts[-1]
     180
     181            ns['fullname'] = name
     182            ns['module'] = mod_name
     183            ns['objname'] = obj_name
     184            ns['name'] = parts[-1]
     185
     186            ns['objtype'] = doc.objtype
     187            ns['underline'] = len(name) * '='
     188
     189            rendered = template.render(**ns)
     190            f.write(rendered)
     191        finally:
     192            f.close()
     193
     194    # descend recursively to new files
     195    if new_files:
     196        generate_autosummary_docs(new_files, output_dir=output_dir,
     197                                  suffix=suffix, warn=warn, info=info,
     198                                  base_path=base_path, builder=builder,
     199                                  template_dir=template_dir)
     200
     201
     202# -- Finding documented entries in files ---------------------------------------
     203
     204def find_autosummary_in_files(filenames):
     205    """Find out what items are documented in source/*.rst.
     206
     207    See `find_autosummary_in_lines`.
     208    """
     209    documented = []
     210    for filename in filenames:
     211        f = open(filename, 'r')
     212        lines = f.read().splitlines()
     213        documented.extend(find_autosummary_in_lines(lines, filename=filename))
     214        f.close()
     215    return documented
     216
     217def find_autosummary_in_docstring(name, module=None, filename=None):
     218    """Find out what items are documented in the given object's docstring.
     219
     220    See `find_autosummary_in_lines`.
     221    """
     222    try:
     223        real_name, obj, parent = import_by_name(name)
     224        lines = pydoc.getdoc(obj).splitlines()
     225        return find_autosummary_in_lines(lines, module=name, filename=filename)
     226    except AttributeError:
     227        pass
     228    except ImportError, e:
     229        print "Failed to import '%s': %s" % (name, e)
     230    return []
     231
     232def find_autosummary_in_lines(lines, module=None, filename=None):
     233    """Find out what items appear in autosummary:: directives in the
     234    given lines.
     235
     236    Returns a list of (name, toctree, template) where *name* is a name
     237    of an object and *toctree* the :toctree: path of the corresponding
     238    autosummary directive (relative to the root of the file name), and
     239    *template* the value of the :template: option. *toctree* and
     240    *template* ``None`` if the directive does not have the
     241    corresponding options set.
     242    """
     243    autosummary_re = re.compile(r'^(\s*)\.\.\s+autosummary::\s*')
     244    automodule_re = re.compile(
     245        r'^\s*\.\.\s+automodule::\s*([A-Za-z0-9_.]+)\s*$')
     246    module_re = re.compile(
     247        r'^\s*\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$')
     248    autosummary_item_re = re.compile(r'^\s+(~?[_a-zA-Z][a-zA-Z0-9_.]*)\s*.*?')
     249    toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$')
     250    template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$')
     251
     252    documented = []
     253
     254    toctree = None
     255    template = None
     256    current_module = module
     257    in_autosummary = False
     258    base_indent = ""
     259
     260    for line in lines:
     261        if in_autosummary:
     262            m = toctree_arg_re.match(line)
     263            if m:
     264                toctree = m.group(1)
     265                if filename:
     266                    toctree = os.path.join(os.path.dirname(filename),
     267                                           toctree)
     268                continue
     269
     270            m = template_arg_re.match(line)
     271            if m:
     272                template = m.group(1).strip()
     273                continue
     274
     275            if line.strip().startswith(':'):
     276                continue # skip options
     277
     278            m = autosummary_item_re.match(line)
     279            if m:
     280                name = m.group(1).strip()
     281                if name.startswith('~'):
     282                    name = name[1:]
     283                if current_module and \
     284                       not name.startswith(current_module + '.'):
     285                    name = "%s.%s" % (current_module, name)
     286                documented.append((name, toctree, template))
     287                continue
     288
     289            if not line.strip() or line.startswith(base_indent + " "):
     290                continue
     291
     292            in_autosummary = False
     293
     294        m = autosummary_re.match(line)
     295        if m:
     296            in_autosummary = True
     297            base_indent = m.group(1)
     298            toctree = None
     299            template = None
     300            continue
     301
     302        m = automodule_re.search(line)
     303        if m:
     304            current_module = m.group(1).strip()
     305            # recurse into the automodule docstring
     306            documented.extend(find_autosummary_in_docstring(
     307                current_module, filename=filename))
     308            continue
     309
     310        m = module_re.match(line)
     311        if m:
     312            current_module = m.group(2)
     313            continue
     314
     315    return documented
     316
     317
     318if __name__ == '__main__':
     319    main()
  • new file doc/common/autosummary/templates/autosummary/base.rst

    diff --git a/doc/common/autosummary/templates/autosummary/base.rst b/doc/common/autosummary/templates/autosummary/base.rst
    new file mode 100644
    - +  
     1{{ fullname }}
     2{{ underline }}
     3
     4.. currentmodule:: {{ module }}
     5
     6.. auto{{ objtype }}:: {{ objname }}
  • new file doc/common/autosummary/templates/autosummary/class.rst

    diff --git a/doc/common/autosummary/templates/autosummary/class.rst b/doc/common/autosummary/templates/autosummary/class.rst
    new file mode 100644
    - +  
     1{{ fullname }}
     2{{ underline }}
     3
     4.. currentmodule:: {{ module }}
     5
     6.. autoclass:: {{ objname }}
     7
     8   {% block methods %}
     9   .. automethod:: __init__
     10
     11   {% if methods %}
     12   .. rubric:: Methods
     13
     14   .. autosummary::
     15   {% for item in methods %}
     16      ~{{ name }}.{{ item }}
     17   {%- endfor %}
     18   {% endif %}
     19   {% endblock %}
     20
     21   {% block attributes %}
     22   {% if attributes %}
     23   .. rubric:: Attributes
     24
     25   .. autosummary::
     26   {% for item in attributes %}
     27      ~{{ name }}.{{ item }}
     28   {%- endfor %}
     29   {% endif %}
     30   {% endblock %}
  • new file doc/common/autosummary/templates/autosummary/module.rst

    diff --git a/doc/common/autosummary/templates/autosummary/module.rst b/doc/common/autosummary/templates/autosummary/module.rst
    new file mode 100644
    - +  
     1{{ fullname }}
     2{{ underline }}
     3
     4.. automodule:: {{ fullname }}
     5
     6   {% block functions %}
     7   {% if functions %}
     8   .. rubric:: Functions
     9
     10   .. autosummary::
     11   {% for item in functions %}
     12      {{ item }}
     13   {%- endfor %}
     14   {% endif %}
     15   {% endblock %}
     16
     17   {% block classes %}
     18   {% if classes %}
     19   .. rubric:: Classes
     20
     21   .. autosummary::
     22   {% for item in classes %}
     23      {{ item }}
     24   {%- endfor %}
     25   {% endif %}
     26   {% endblock %}
     27
     28   {% block exceptions %}
     29   {% if exceptions %}
     30   .. rubric:: Exceptions
     31
     32   .. autosummary::
     33   {% for item in exceptions %}
     34      {{ item }}
     35   {%- endfor %}
     36   {% endif %}
     37   {% endblock %}
  • doc/common/conf.py

    diff --git a/doc/common/conf.py b/doc/common/conf.py
    a b sys.path.append(get_doc_abspath('common' 
    2121# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
    2222extensions = ['sage_autodoc',  'sphinx.ext.graphviz',
    2323              'sphinx.ext.inheritance_diagram', 'sphinx.ext.todo',
    24               'sphinx.ext.extlinks']
     24              'sphinx.ext.extlinks', 'autosummary']
    2525# We do *not* fully initialize intersphinx since we call it by hand
    2626# in find_sage_dangling_links.
    2727#, 'sphinx.ext.intersphinx']
  • doc/common/sage_autodoc.py

    diff --git a/doc/common/sage_autodoc.py b/doc/common/sage_autodoc.py
    a b class ClassDocumenter(ModuleLevelDocumen 
    987987        else:
    988988            ModuleLevelDocumenter.add_content(self, more_content)
    989989
     990    def add_toc_array(self, all_members=False):
     991        # set current namespace for finding members
     992        self.env.autodoc_current_module = self.modname
     993        if self.objpath:
     994            self.env.autodoc_current_class = self.objpath[0]
     995
     996        want_all = all_members or self.options.inherited_members or \
     997                   self.options.members is ALL
     998        # find out which members are documentable
     999        members_check_module, members = self.get_object_members(want_all)
     1000
     1001        # remove members given by exclude-members
     1002        if self.options.exclude_members:
     1003            members = [(membername, member) for (membername, member) in members
     1004                       if membername not in self.options.exclude_members]
     1005        members = self.filter_members(members, want_all)
     1006        self.add_line(u".. rubric:: Documented Members:", '<autosummary>')
     1007        self.add_line(u"", '<autosummary>')
     1008        self.add_line(u".. autosummary::", '<autosummary>')
     1009        self.add_line(u"", '<autosummary>')
     1010        for member in members:
     1011            self.add_line("    "+str(member[0]), '<autosummary>')
     1012        self.add_line(u"", '<autosummary>')
     1013
    9901014    def document_members(self, all_members=False):
    9911015        if self.doc_as_attr:
    9921016            return
     1017        self.add_toc_array(all_members)
    9931018        ModuleLevelDocumenter.document_members(self, all_members)
    9941019
    9951020