Commits

Georg Brandl committed b24c937 Merge

merge with pv/sphinx-work

Comments (0)

Files changed (7)

doc/ext/autosummary.rst

      that stub pages should be generated for the entries listed in this
      directive.  The option accepts a directory name as an argument;
      :program:`sphinx-autogen` will by default place its output in this
-     directory.
+     directory. If no argument is given, output is placed in the same directory
+     as the file that contains the directive.
 
    * If you don't want the :dir:`autosummary` to show function signatures in the
      listing, include the ``nosignatures`` option::
             sphinx.environment.BuildEnvironment
             sphinx.util.relative_uri
 
+   * You can specify a custom template with the ``template`` option.
+     For example, ::
+
+         .. autosummary::
+            :template: mytemplate.rst
+
+            sphinx.environment.BuildEnvironment
+
+     would use the template :file:`mytemplate.rst` in your
+     :confval:`templates_path` to generate the pages for all entries
+     listed. See `Customizing templates`_ below.
+
 
 :program:`sphinx-autogen` -- generate autodoc stub pages
 --------------------------------------------------------
 
 .. confval:: autosummary_generate
 
-   A list of documents for which stub pages should be generated.  They will be
-   placed in the directories specified in the ``:toctree:`` options.
+   Boolean indicating whether to scan all found documents for
+   autosummary directives, and to generate stub pages for each.
+
+   Can also be a list of documents for which stub pages should be
+   generated.
+
+   The new files will be placed in the directories specified in the
+   ``:toctree:`` options of the directives.
+
+
+Customizing templates
+---------------------
+
+You can customize the stub page templates, in a similar way as the
+HTML Jinja templates, see
+:ref:`templating`. (:class:`~sphinx.application.TemplateBridge` is not
+supported.)
+
+.. note::
+
+   If you find yourself spending much time tailoring the stub
+   templates, this may indicate that it's a better idea to write
+   custom narrative documentation instead.
+
+Autosummary uses the following template files:
+
+  - :file:`autosummary/base.rst` -- fallback template
+  - :file:`autosummary/module.rst` -- template for modules
+  - :file:`autosummary/class.rst` -- template for classes
+  - :file:`autosummary/function.rst` -- template for functions
+  - :file:`autosummary/attribute.rst` -- template for class attributes
+  - :file:`autosummary/method.rst` -- template for class methods
+
+The following variables available in the templates:
+
+.. data:: name
+
+   Name of the documented object, excluding the module and class parts.
+
+.. data:: objname
+
+   Name of the documented object, excluding the module parts.
+
+.. data:: fullname
+
+   Full name of the documented object, including module and class parts.
+
+.. data:: module
+
+   Name of the module the documented object belongs to.
+
+.. data:: class
+
+   Name of the class the documented object belongs to.
+   Only available for methods and attributes.
+
+.. data:: underline
+
+   A string containing ``len(full_name) * '='``.
+
+.. data:: members
+
+   List containing names of all members of the module or class.
+   Only available for modules and classes.
+
+.. data:: functions
+
+   List containing names of "public" functions in the module.
+   Here, "public" here means that the name does not start with an
+   underscore. Only available for modules.
+
+.. data:: classes
+
+   List containing names of "public" classes in the module.
+   Only available for modules.
+
+.. data:: exceptions
+
+   List containing names of "public" exceptions in the module.
+   Only available for modules.
+
+.. data:: methods
+
+   List containing names of "public" methods in the class.
+   Only available for classes.
+
+.. data:: methods
+
+   List containing names of "public" attributes in the class.
+   Only available for classes.
+
+.. note::
+   
+   You can use the :dir:`autosummary` directive in the stub pages.
+   However, stub pages are not generated automatically recursively.

sphinx/ext/autosummary/__init__.py

     """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."""
+def autosummary_noop(self, node):
     pass
 
-def autosummary_noop(self, node):
+
+# -- autosummary_table node ----------------------------------------------------
+
+class autosummary_table(nodes.comment):
     pass
 
+def autosummary_table_visit_html(self, node):
+    """Make the first column of the table non-breaking."""
+    try:
+        tbody = node[0][0][-1]
+        for row in tbody:
+            col1_entry = row[0]
+            par = col1_entry[0]
+            for j, subnode in enumerate(list(par)):
+                if isinstance(subnode, nodes.Text):
+                    new_text = unicode(subnode.astext())
+                    new_text = new_text.replace(u" ", u"\u00a0")
+                    par[j] = nodes.Text(new_text)
+    except IndexError:
+        pass
+
 
 # -- autodoc integration -------------------------------------------------------
 
     option_spec = {
         'toctree': directives.unchanged,
         'nosignatures': directives.flag,
+        'template': directives.unchanged,
     }
 
     def warn(self, msg):
         self.warnings = []
 
         names = [x.strip().split()[0] for x in self.content
-                 if x.strip() and re.search(r'^[a-zA-Z_]', x.strip()[0])]
+                 if x.strip() and re.search(r'^[~a-zA-Z_]', x.strip()[0])]
         items = self.get_items(names)
-        nodes = [self.get_table(items)]
+        nodes = self.get_table(items)
 
         if 'toctree' in self.options:
             suffix = env.config.source_suffix
         Try to import the given names, and return a list of
         ``[(name, signature, summary_string, real_name), ...]``.
         """
+        env = self.state.document.settings.env
+        
         prefixes = ['']
-        prefixes.insert(0, self.state.document.settings.env.currmodule)
+        if env.currmodule:
+            prefixes.insert(0, env.currmodule)
 
         items = []
 
+        max_item_chars = 50
+
         for name in names:
+            display_name = name
+            if name.startswith('~'):
+                name = name[1:]
+                display_name = name.split('.')[-1]
+
             try:
                 obj, real_name = import_by_name(name, prefixes=prefixes)
             except ImportError:
                 continue
 
             # NB. using real_name here is important, since Documenters
-            #     don't handle module prefixes slightly differently
+            #     handle module prefixes slightly differently
             documenter = get_documenter(obj)(self, real_name)
             if not documenter.parse_name():
                 self.warn('failed to parse name %s' % real_name)
-                items.append((name, '', '', real_name))
+                items.append((display_name, '', '', real_name))
                 continue
             if not documenter.import_object():
                 self.warn('failed to import object %s' % real_name)
-                items.append((name, '', '', real_name))
+                items.append((display_name, '', '', real_name))
                 continue
 
             # -- Grab the signature
             if not sig:
                 sig = ''
             else:
-                sig = mangle_signature(sig).replace('*', r'\*')
+                max_chars = max(10, max_item_chars - len(display_name))
+                sig = mangle_signature(sig, max_chars=max_chars)
+                sig = sig.replace('*', r'\*')
 
             # -- Grab the summary
 
             else:
                 summary = ''
 
-            items.append((name, sig, summary, real_name))
+            items.append((display_name, sig, summary, real_name))
 
         return items
 
     def get_table(self, items):
         """
-        Generate a proper table node for autosummary:: directive.
+        Generate a proper list of table nodes for autosummary:: directive.
 
         *items* is a list produced by :meth:`get_items`.
         """
-        table = nodes.table('')
+        table_spec = addnodes.tabular_col_spec()
+        table_spec['spec'] = 'LL'
+
+        table = autosummary_table('')
+        real_table = nodes.table('')
+        table.append(real_table)
         group = nodes.tgroup('', cols=2)
-        table.append(group)
+        real_table.append(group)
         group.append(nodes.colspec('', colwidth=10))
         group.append(nodes.colspec('', colwidth=90))
         body = nodes.tbody('')
             col2 = summary
             append_row(col1, col2)
 
-        return table
+        return [table_spec, table]
 
 def mangle_signature(sig, max_chars=30):
     """Reformat a function signature to a more compact form."""
     r = re.compile(r"(?P<name>[a-zA-Z0-9_*]+)(?P<default>=.*?)?, ")
     items = r.findall(sig)
 
-    args = []
-    opts = []
+    args = [name for name, default in items if not default]
+    opts = [name for name, default in items if default]
 
-    total_len = 4
-    for name, default in items:
-        if default:
-            opts.append(name)
+    sig = limited_join(", ", args, max_chars=max_chars-2)
+    if opts:
+        if not sig:
+            sig = "[%s]" % limited_join(", ", opts, max_chars=max_chars-4)
+        elif len(sig) < max_chars - 4 - 2 - 3:
+            sig += "[, %s]" % limited_join(", ", opts,
+                                           max_chars=max_chars-len(sig)-4-2)
+
+    return u"(%s)" % sig
+
+def limited_join(sep, items, max_chars=30, overflow_marker="..."):
+    """
+    Join a number of strings to one, limiting the length to *max_chars*.
+
+    If the string overflows this limit, replace the last fitting item by
+    *overflow_marker*.
+
+    Returns: joined_string
+    """
+    full_str = sep.join(items)
+    if len(full_str) < max_chars:
+        return full_str
+
+    n_chars = 0
+    n_items = 0
+    for j, item in enumerate(items):
+        n_chars += len(item) + len(sep)
+        if n_chars < max_chars - len(overflow_marker):
+            n_items += 1
         else:
-            args.append(name)
-        total_len += len(name) + 2
-
-        if total_len > max_chars:
-            if opts:
-                opts.append('...')
-            else:
-                args.append('...')
             break
 
-    if opts and args:
-        sig = ", ".join(args) + "[, " + ", ".join(opts) + "]"
-    elif opts and not args:
-        sig = "[" + ", ".join(opts) + "]"
-    else:
-        sig = ", ".join(args)
-
-    sig = unicode(sig).replace(u" ", u"\u00a0")
-    return u"(%s)" % sig
-
+    return sep.join(list(items[:n_items]) + [overflow_marker])
 
 # -- Importing items -----------------------------------------------------------
 
 
 def process_generate_options(app):
     genfiles = app.config.autosummary_generate
+
+    ext = app.config.source_suffix
+
+    if genfiles and not hasattr(genfiles, '__len__'):
+        env = app.builder.env
+        genfiles = [x + ext for x in env.found_docs
+                    if os.path.isfile(env.doc2path(x))]
+
     if not genfiles:
         return
+
     from sphinx.ext.autosummary.generate import generate_autosummary_docs
 
-    ext = app.config.source_suffix
     genfiles = [genfile + (not genfile.endswith(ext) and ext or '')
                 for genfile in genfiles]
-    generate_autosummary_docs(genfiles, warn=app.warn, info=app.info,
-                              suffix=ext, base_path=app.srcdir)
+
+    generate_autosummary_docs(genfiles, builder=app.builder,
+                              warn=app.warn, info=app.info, suffix=ext,
+                              base_path=app.srcdir)
 
 
 def setup(app):
     app.setup_extension('sphinx.ext.autodoc')
     app.add_node(autosummary_toc,
                  html=(autosummary_toc_visit_html, autosummary_noop),
-                 latex=(autosummary_toc_visit_latex, autosummary_noop),
+                 latex=(autosummary_noop, autosummary_noop),
+                 text=(autosummary_noop, autosummary_noop))
+    app.add_node(autosummary_table,
+                 html=(autosummary_table_visit_html, autosummary_noop),
+                 latex=(autosummary_noop, autosummary_noop),
                  text=(autosummary_noop, autosummary_noop))
     app.add_directive('autosummary', Autosummary)
     app.add_role('autolink', autolink_role)

sphinx/ext/autosummary/generate.py

 import inspect
 import pydoc
 
-from jinja2 import Environment, PackageLoader
+from jinja2 import FileSystemLoader, TemplateNotFound
+from jinja2.sandbox import SandboxedEnvironment
 
 from sphinx.ext.autosummary import import_by_name, get_documenter
 from sphinx.util import ensuredir
-
+from sphinx.jinja2glue import BuiltinTemplateLoader
 
 def main(argv=sys.argv):
     usage = """%prog [OPTIONS] SOURCEFILE ..."""
     p.add_option("-s", "--suffix", action="store", type="string",
                  dest="suffix", default="rst",
                  help="Default suffix for files (default: %default)")
+    p.add_option("-t", "--templates", action="store", type="string",
+                 dest="templates", default=None,
+                 help="Custom template directory (default: %default)")
     options, args = p.parse_args(argv[1:])
 
     if len(args) < 1:
         p.error('no input files given')
 
     generate_autosummary_docs(args, options.output_dir,
-                              "." + options.suffix)
-
+                              "." + options.suffix,
+                              template_dir=options.templates)
 
 def _simple_info(msg):
     print msg
 
 # -- Generating output ---------------------------------------------------------
 
-# create our own templating environment, for module template only
-env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates'))
-
 def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
                               warn=_simple_warn, info=_simple_info,
-                              base_path=None):
+                              base_path=None, builder=None, template_dir=None):
 
+    showed_sources = list(sorted(sources))
+    if len(showed_sources) > 20:
+        showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:]
     info('[autosummary] generating autosummary for: %s' %
-         ', '.join(sorted(sources)))
+         ', '.join(showed_sources))
 
     if output_dir:
         info('[autosummary] writing to %s' % output_dir)
     if base_path is not None:
         sources = [os.path.join(base_path, filename) for filename in sources]
 
+    # create our own templating environment
+    template_dirs = [os.path.join(os.path.dirname(__file__), 'templates')]
+    if builder is not None:
+        # allow the user to override the templates
+        template_loader = BuiltinTemplateLoader()
+        template_loader.init(builder, dirs=template_dirs)
+    else:
+        if template_dir:
+            template_dirs.insert(0, template_dir)
+        template_loader = FileSystemLoader(template_dirs)
+    template_env = SandboxedEnvironment(loader=template_loader)
+
     # read
     items = find_autosummary_in_files(sources)
 
     items = dict([(item, True) for item in items]).keys()
 
     # write
-    for name, path in sorted(items):
+    for name, path, template_name in sorted(items):
         if path is None:
             # The corresponding autosummary:: directive did not have
             # a :toctree: option
         f = open(fn, 'w')
 
         try:
-            if inspect.ismodule(obj):
-                tmpl = env.get_template('module')
+            doc = get_documenter(obj)
 
-                def get_items(mod, typ):
-                    return [
-                        getattr(mod, name).__name__ for name in dir(mod)
-                        if get_documenter(getattr(mod, name)).objtype == typ
-                    ]
+            if template_name is not None:
+                template = template_env.get_template(template_name)
+            else:
+                try:
+                    template = template_env.get_template('autosummary/%s.rst'
+                                                         % doc.objtype)
+                except TemplateNotFound:
+                    template = template_env.get_template('autosummary/base.rst')
 
-                functions = get_items(obj, 'function')
-                classes = get_items(obj, 'class')
-                exceptions = get_items(obj, 'exception')
+            def get_members(obj, typ, include_public=[]):
+                items = [
+                    name for name in dir(obj)
+                    if get_documenter(getattr(obj, name)).objtype == typ
+                ]
+                public = [x for x in items
+                          if x in include_public or not x.startswith('_')]
+                return public, items
 
-                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)
+            info = {}
+
+            if doc.objtype == 'module':
+                info['members'] = dir(obj)
+                info['functions'], info['all_functions'] = \
+                                   get_members(obj, 'function')
+                info['classes'], info['all_classes'] = \
+                                 get_members(obj, 'class')
+                info['exceptions'], info['all_exceptions'] = \
+                                   get_members(obj, 'exception')
+            elif doc.objtype == 'class':
+                info['members'] = dir(obj)
+                info['methods'], info['all_methods'] = \
+                                 get_members(obj, 'method', ['__init__'])
+                info['attributes'], info['all_attributes'] = \
+                                 get_members(obj, 'attribute')
+
+            parts = name.split('.')
+            if doc.objtype in ('method', 'attribute'):
+                mod_name = '.'.join(parts[:-2])
+                cls_name = parts[-2]
+                obj_name = '.'.join(parts[-2:])
+                info['class'] = cls_name
             else:
-                f.write('%s\n%s\n\n' % (name, '='*len(name)))
+                mod_name, obj_name = '.'.join(parts[:-1]), parts[-1]
 
-                doc = get_documenter(obj)
-                if doc.objtype in ('method', 'attribute'):
-                    f.write(format_classmember(name, 'auto%s' % doc.objtype))
-                else:
-                    f.write(format_modulemember(name, 'auto%s' % doc.objtype))
+            info['fullname'] = name
+            info['module'] = mod_name
+            info['objname'] = obj_name
+            info['name'] = parts[-1]
+
+            info['objtype'] = doc.objtype
+            info['underline'] = len(name) * '='
+
+            rendered = template.render(**info)
+            f.write(rendered)
         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)
-
-
 # -- Finding documented entries in files ---------------------------------------
 
 def find_autosummary_in_files(filenames):
     """
     Find out what items appear in autosummary:: directives in the given lines.
 
-    Returns a list of (name, toctree) where *name* is a name of an object
-    and *toctree* the :toctree: path of the corresponding autosummary directive
-    (relative to the root of the file name). *toctree* is ``None`` if
-    the directive does not have the :toctree: option set.
+    Returns a list of (name, toctree, template) where *name* is a name
+    of an object and *toctree* the :toctree: path of the corresponding
+    autosummary directive (relative to the root of the file name), and
+    *template* the value of the :template: option. *toctree* and
+    *template* ``None`` if the directive does not have the
+    corresponding options set.
     """
     autosummary_re = re.compile(r'^\.\.\s+autosummary::\s*')
     automodule_re = re.compile(r'.. automodule::\s*([A-Za-z0-9_.]+)\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*$')
+    template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$')
 
     documented = []
 
     toctree = None
+    template = None
     current_module = module
     in_autosummary = False
 
                                            toctree)
                 continue
 
+            m = template_arg_re.match(line)
+            if m:
+                template = m.group(1).strip()
+                continue
+
             if line.strip().startswith(':'):
                 continue # skip options
 
                 if current_module and \
                        not name.startswith(current_module + '.'):
                     name = "%s.%s" % (current_module, name)
-                documented.append((name, toctree))
+                documented.append((name, toctree, template))
                 continue
 
             if not line.strip():
         if m:
             in_autosummary = True
             toctree = None
+            template = None
             continue
 
         m = automodule_re.search(line)

sphinx/ext/autosummary/templates/autosummary/base.rst

+{{ fullname }}
+{{ underline }}
+
+.. currentmodule:: {{ module }}
+
+.. auto{{ objtype }}:: {{ objname }}

sphinx/ext/autosummary/templates/autosummary/class.rst

+{{ fullname }}
+{{ underline }}
+
+.. currentmodule:: {{ module }}
+
+.. autoclass:: {{ objname }}
+
+   {% block methods %}
+   .. automethod:: __init__
+
+   {% if methods %}
+   .. rubric:: Methods
+
+   .. autosummary::
+   {% for item in methods %}
+      ~{{ name }}.{{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block attributes %}
+   {% if attributes %}
+   .. rubric:: Attributes
+
+   .. autosummary::
+   {% for item in attributes %}
+      ~{{ name }}.{{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}

sphinx/ext/autosummary/templates/autosummary/module.rst

+{{ fullname }}
+{{ underline }}
+
+.. automodule:: {{ fullname }}
+
+   {% block functions %}
+   {% if functions %}
+   .. rubric:: Functions
+
+   .. autosummary::
+   {% for item in functions %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block classes %}
+   {% if classes %}
+   .. rubric:: Classes
+
+   .. autosummary::
+   {% for item in classes %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block exceptions %}
+   {% if exceptions %}
+   .. rubric:: Exceptions
+
+   .. autosummary::
+   {% for item in classes %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}

sphinx/ext/autosummary/templates/module

-:mod:`{{name}}`
-======{{ underline }}=
-
-
-.. automodule:: {{name}}
-
-{% if len_functions > 0 %}
-Functions
-----------
-{% for item in functions %}
-.. autofunction:: {{item}}
-{% endfor %}
-{% endif %}
-
-{% if len_classes > 0 %}
-Classes
---------
-{% for item in classes %}
-.. autoclass:: {{item}}
-   :show-inheritance:
-   :members:
-   :inherited-members:
-   :undoc-members:
-   
-{% endfor %}
-{% endif %}
-
-{% if len_exceptions > 0 %}
-Exceptions
-------------
-{% for item in exceptions %}
-.. autoclass:: {{item}}
-   :show-inheritance:
-   :members:
-   :inherited-members:
-   :undoc-members:
-   
-{% endfor %}
-{% endif %}