Commits

Anonymous committed 69f7b5d

initial code brought over from numpy. Now we need to do some simplification of the numpy code.

Comments (0)

Files changed (7)

     entry_points={
         'console_scripts': [
             'sphinx-build = sphinx:main',
-            'sphinx-quickstart = sphinx.quickstart:main'
+            'sphinx-quickstart = sphinx.quickstart:main',
+            'sphinx-autogen = sphinx.scripts.autosummary_generate:main',
         ],
         'distutils.commands': [
             'build_sphinx = sphinx.setup_command:BuildDoc',

sphinx/ext/autosummary/__init__.py

+"""
+===========
+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.
+
+An :autolink: role is also provided.
+
+autosummary directive
+---------------------
+
+The autosummary directive has the form::
+
+    .. autosummary::
+       :nosignatures:
+       :toctree: generated/
+       
+       module.function_1
+       module.function_2
+       ...
+
+and it generates an output table (containing signatures, optionally)
+
+    ========================  =============================================
+    module.function_1(args)   Summary line from the docstring of function_1
+    module.function_2(args)   Summary line from the docstring
+    ...
+    ========================  =============================================
+
+If the :toctree: option is specified, files matching the function names
+are inserted to the toctree with the given prefix:
+
+    generated/module.function_1
+    generated/module.function_2
+    ...
+
+Note: The file names contain the module:: or currentmodule:: prefixes.
+
+.. seealso:: autosummary_generate.py
+
+
+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'.
+
+"""
+import sys, os, posixpath, re
+
+from docutils.parsers.rst import directives
+from docutils.statemachine import ViewList
+from docutils import nodes
+
+import sphinx.addnodes, sphinx.roles, sphinx.builder
+from sphinx.util import patfilter
+
+from docscrape_sphinx import get_doc_object
+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
+#------------------------------------------------------------------------------
+
+class autosummary_toc(nodes.comment):
+    pass
+
+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 = {}
+    def crawl_toc(node, depth=1):
+        crawled[node] = True
+        for j, subnode in enumerate(node):
+            try:
+                if (isinstance(subnode, autosummary_toc)
+                    and isinstance(subnode[0], sphinx.addnodes.toctree)):
+                    env.note_toctree(env.docname, subnode[0])
+                    continue
+            except IndexError:
+                continue
+            if not isinstance(subnode, nodes.section):
+                continue
+            if subnode not in crawled:
+                crawl_toc(subnode, depth+1)
+    crawl_toc(doctree)
+
+def autosummary_toc_visit_html(self, node):
+    """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"""
+    pass
+
+def autosummary_toc_depart_noop(self, node):
+    pass
+
+#------------------------------------------------------------------------------
+# .. autosummary::
+#------------------------------------------------------------------------------
+
+def autosummary_directive(dirname, arguments, options, content, lineno,
+                          content_offset, block_text, state, state_machine):
+    """
+    Pretty table containing short signatures and summaries of functions etc.
+
+    autosummary also generates a (hidden) toctree:: node.
+
+    """
+
+    names = []
+    names += [x.strip() for x in content if x.strip()]
+
+    table, warnings, real_names = get_autosummary(names, state,
+                                                  'nosignatures' in options)
+    node = table
+
+    env = state.document.settings.env
+    suffix = env.config.source_suffix
+    all_docnames = env.found_docs.copy()
+    dirname = posixpath.dirname(env.docname)
+
+    if 'toctree' in options:
+        tree_prefix = options['toctree'].strip()
+        docnames = []
+        for name in names:
+            name = real_names.get(name, name)
+
+            docname = tree_prefix + name
+            if docname.endswith(suffix):
+                docname = docname[:-len(suffix)]
+            docname = posixpath.normpath(posixpath.join(dirname, docname))
+            if docname not in env.found_docs:
+                warnings.append(state.document.reporter.warning(
+                    'toctree references unknown document %r' % docname,
+                    line=lineno))
+            docnames.append(docname)
+
+        tocnode = sphinx.addnodes.toctree()
+        tocnode['includefiles'] = docnames
+        tocnode['maxdepth'] = -1
+        tocnode['glob'] = None
+
+        tocnode = autosummary_toc('', '', tocnode)
+        return warnings + [node] + [tocnode]
+    else:
+        return warnings + [node]
+
+def get_autosummary(names, state, no_signatures=False):
+    """
+    Generate a proper table node for autosummary:: directive.
+
+    Parameters
+    ----------
+    names : list of str
+        Names of Python objects to be imported and added to the table.
+    document : document
+        Docutils document object
+    
+    """
+    document = state.document
+    
+    real_names = {}
+    warnings = []
+
+    prefixes = ['']
+    prefixes.insert(0, document.settings.env.currmodule)
+
+    table = nodes.table('')
+    group = nodes.tgroup('', cols=2)
+    table.append(group)
+    group.append(nodes.colspec('', colwidth=30))
+    group.append(nodes.colspec('', colwidth=70))
+    body = nodes.tbody('')
+    group.append(body)
+
+    def append_row(*column_texts):
+        row = nodes.row('')
+        for text in column_texts:
+            node = nodes.paragraph('')
+            vl = ViewList()
+            vl.append(text, '<autosummary>')
+            state.nested_parse(vl, 0, node)
+            row.append(nodes.entry('', node))
+        body.append(row)
+
+    for name in names:
+        try:
+            obj, real_name = import_by_name(name, prefixes=prefixes)
+        except ImportError:
+            warnings.append(document.reporter.warning(
+                'failed to import %s' % name))
+            append_row(":obj:`%s`" % name, "")
+            continue
+
+        real_names[name] = real_name
+
+        doc = get_doc_object(obj)
+
+        if doc['Summary']:
+            title = " ".join(doc['Summary'])
+        else:
+            title = ""
+        qualifier = 'obj'
+        if inspect.ismodule(obj):
+            qualifier = 'mod'
+        col1 = ":"+qualifier+":`%s <%s>`" % (name, real_name)
+        if doc['Signature']:
+            sig = re.sub('^[a-zA-Z_0-9.-]*', '',
+                         doc['Signature'].replace('*', r'\*'))
+            if '=' in sig:
+                # abbreviate optional arguments
+                sig = re.sub(r', ([a-zA-Z0-9_]+)=', r'[, \1=', sig, count=1)
+                sig = re.sub(r'\(([a-zA-Z0-9_]+)=', r'([\1=', sig, count=1)
+                sig = re.sub(r'=[^,)]+,', ',', sig)
+                sig = re.sub(r'=[^,)]+\)$', '])', sig)
+                # shorten long strings
+                sig = re.sub(r'(\[.{16,16}[^,)]*?),.*?\]\)', r'\1, ...])', sig)
+            else:
+                sig = re.sub(r'(\(.{16,16}[^,)]*?),.*?\)', r'\1, ...)', sig)
+            col1 += " " + sig
+        col2 = title
+        append_row(col1, col2)
+
+    return table, warnings, real_names
+
+def import_by_name(name, prefixes=[None]):
+    """
+    Import a Python object that has the given name, under one of the prefixes.
+
+    Parameters
+    ----------
+    name : str
+        Name of a Python object, eg. 'numpy.ndarray.view'
+    prefixes : list of (str or None), optional
+        Prefixes to prepend to the name (None implies no prefix).
+        The first prefixed name that results to successful import is used.
+
+    Returns
+    -------
+    obj
+        The imported object
+    name
+        Name of the imported object (useful if `prefixes` was used)
+    
+    """
+    for prefix in prefixes:
+        try:
+            if prefix:
+                prefixed_name = '.'.join([prefix, name])
+            else:
+                prefixed_name = name
+            return _import_by_name(prefixed_name), prefixed_name
+        except ImportError:
+            pass
+    raise ImportError
+
+def _import_by_name(name):
+    """Import a Python object given its full name"""
+    try:
+        name_parts = name.split('.')
+        last_j = 0
+        modname = None
+        for j in reversed(range(1, len(name_parts)+1)):
+            last_j = j
+            modname = '.'.join(name_parts[:j])
+            try:
+                __import__(modname)
+            except ImportError:
+                continue
+            if modname in sys.modules:
+                break
+
+        if last_j < len(name_parts):
+            obj = sys.modules[modname]
+            for obj_name in name_parts[last_j:]:
+                obj = getattr(obj, obj_name)
+            return obj
+        else:
+            return sys.modules[modname]
+    except (ValueError, ImportError, AttributeError, KeyError), e:
+        raise ImportError(e)
+
+#------------------------------------------------------------------------------
+# :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*".
+    """
+    r = sphinx.roles.xfileref_role('obj', rawtext, etext, lineno, inliner,
+                                   options, content)
+    pnode = r[0][0]
+
+    prefixes = [None]
+    #prefixes.insert(0, inliner.document.settings.env.currmodule)
+    try:
+        obj, name = import_by_name(pnode['reftarget'], prefixes)
+    except ImportError:
+        content = pnode[0]
+        r[0][0] = nodes.emphasis(rawtext, content[0].astext(),
+                                 classes=content['classes'])
+    return r

sphinx/ext/autosummary/docscrape.py

+"""Extract reference documentation from the NumPy source tree.
+
+"""
+
+import inspect
+import textwrap
+import re
+import pydoc
+from StringIO import StringIO
+from warnings import warn
+
+class Reader(object):
+    """A line-based string reader.
+
+    """
+    def __init__(self, data):
+        """
+        Parameters
+        ----------
+        data : str
+           String with lines separated by '\n'.
+
+        """
+        if isinstance(data,list):
+            self._str = data
+        else:
+            self._str = data.split('\n') # store string as list of lines
+
+        self.reset()
+
+    def __getitem__(self, n):
+        return self._str[n]
+
+    def reset(self):
+        self._l = 0 # current line nr
+
+    def read(self):
+        if not self.eof():
+            out = self[self._l]
+            self._l += 1
+            return out
+        else:
+            return ''
+
+    def seek_next_non_empty_line(self):
+        for l in self[self._l:]:
+            if l.strip():
+                break
+            else:
+                self._l += 1
+
+    def eof(self):
+        return self._l >= len(self._str)
+
+    def read_to_condition(self, condition_func):
+        start = self._l
+        for line in self[start:]:
+            if condition_func(line):
+                return self[start:self._l]
+            self._l += 1
+            if self.eof():
+                return self[start:self._l+1]
+        return []
+
+    def read_to_next_empty_line(self):
+        self.seek_next_non_empty_line()
+        def is_empty(line):
+            return not line.strip()
+        return self.read_to_condition(is_empty)
+
+    def read_to_next_unindented_line(self):
+        def is_unindented(line):
+            return (line.strip() and (len(line.lstrip()) == len(line)))
+        return self.read_to_condition(is_unindented)
+
+    def peek(self,n=0):
+        if self._l + n < len(self._str):
+            return self[self._l + n]
+        else:
+            return ''
+
+    def is_empty(self):
+        return not ''.join(self._str).strip()
+
+
+class NumpyDocString(object):
+    def __init__(self,docstring):
+        docstring = docstring.split('\n')
+
+        # De-indent paragraph
+        try:
+            indent = min(len(s) - len(s.lstrip()) for s in docstring
+                         if s.strip())
+        except ValueError:
+            indent = 0
+
+        for n,line in enumerate(docstring):
+            docstring[n] = docstring[n][indent:]
+
+        self._doc = Reader(docstring)
+        self._parsed_data = {
+            'Signature': '',
+            'Summary': '',
+            'Extended Summary': [],
+            'Parameters': [],
+            'Returns': [],
+            'Raises': [],
+            'Warns': [],
+            'Other Parameters': [],
+            'Attributes': [],
+            'Methods': [],
+            'See Also': [],
+            'Notes': [],
+            'Warnings': [],
+            'References': '',
+            'Examples': '',
+            'index': {}
+            }
+
+        self._parse()
+
+    def __getitem__(self,key):
+        return self._parsed_data[key]
+
+    def __setitem__(self,key,val):
+        if not self._parsed_data.has_key(key):
+            warn("Unknown section %s" % key)
+        else:
+            self._parsed_data[key] = val
+
+    def _is_at_section(self):
+        self._doc.seek_next_non_empty_line()
+
+        if self._doc.eof():
+            return False
+
+        l1 = self._doc.peek().strip()  # e.g. Parameters
+
+        if l1.startswith('.. index::'):
+            return True
+
+        l2 = self._doc.peek(1).strip() #    ---------- or ==========
+        return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1))
+
+    def _strip(self,doc):
+        i = 0
+        j = 0
+        for i,line in enumerate(doc):
+            if line.strip(): break
+
+        for j,line in enumerate(doc[::-1]):
+            if line.strip(): break
+
+        return doc[i:len(doc)-j]
+
+    def _read_to_next_section(self):
+        section = self._doc.read_to_next_empty_line()
+
+        while not self._is_at_section() and not self._doc.eof():
+            if not self._doc.peek(-1).strip(): # previous line was empty
+                section += ['']
+
+            section += self._doc.read_to_next_empty_line()
+
+        return section
+
+    def _read_sections(self):
+        while not self._doc.eof():
+            data = self._read_to_next_section()
+            name = data[0].strip()
+
+            if name.startswith('..'): # index section
+                yield name, data[1:]
+            elif len(data) < 2:
+                yield StopIteration
+            else:
+                yield name, self._strip(data[2:])
+
+    def _parse_param_list(self,content):
+        r = Reader(content)
+        params = []
+        while not r.eof():
+            header = r.read().strip()
+            if ' : ' in header:
+                arg_name, arg_type = header.split(' : ')[:2]
+            else:
+                arg_name, arg_type = header, ''
+
+            desc = r.read_to_next_unindented_line()
+            for n,line in enumerate(desc):
+                desc[n] = line.strip()
+            desc = desc #'\n'.join(desc)
+
+            params.append((arg_name,arg_type,desc))
+
+        return params
+
+    
+    _name_rgx = re.compile(r"^\s*(:(?P<role>\w+):`(?P<name>[a-zA-Z0-9_.-]+)`|"
+                           r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X)
+    def _parse_see_also(self, content):
+        """
+        func_name : Descriptive text
+            continued text
+        another_func_name : Descriptive text
+        func_name1, func_name2, :meth:`func_name`, func_name3
+
+        """
+        items = []
+
+        def parse_item_name(text):
+            """Match ':role:`name`' or 'name'"""
+            m = self._name_rgx.match(text)
+            if m:
+                g = m.groups()
+                if g[1] is None:
+                    return g[3], None
+                else:
+                    return g[2], g[1]
+            raise ValueError("%s is not a item name" % text)
+
+        def push_item(name, rest):
+            if not name:
+                return
+            name, role = parse_item_name(name)
+            items.append((name, list(rest), role))
+            del rest[:]
+
+        current_func = None
+        rest = []
+        
+        for line in content:
+            if not line.strip(): continue
+
+            m = self._name_rgx.match(line)
+            if m and line[m.end():].strip().startswith(':'):
+                push_item(current_func, rest)
+                current_func, line = line[:m.end()], line[m.end():]
+                rest = [line.split(':', 1)[1].strip()]
+                if not rest[0]:
+                    rest = []
+            elif not line.startswith(' '):
+                push_item(current_func, rest)
+                current_func = None
+                if ',' in line:
+                    for func in line.split(','):
+                        push_item(func, [])
+                elif line.strip():
+                    current_func = line
+            elif current_func is not None:
+                rest.append(line.strip())
+        push_item(current_func, rest)
+        return items
+
+    def _parse_index(self, section, content):
+        """
+        .. index: default
+           :refguide: something, else, and more
+
+        """
+        def strip_each_in(lst):
+            return [s.strip() for s in lst]
+
+        out = {}
+        section = section.split('::')
+        if len(section) > 1:
+            out['default'] = strip_each_in(section[1].split(','))[0]
+        for line in content:
+            line = line.split(':')
+            if len(line) > 2:
+                out[line[1]] = strip_each_in(line[2].split(','))
+        return out
+    
+    def _parse_summary(self):
+        """Grab signature (if given) and summary"""
+        if self._is_at_section():
+            return
+
+        summary = self._doc.read_to_next_empty_line()
+        summary_str = " ".join([s.strip() for s in summary]).strip()
+        if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
+            self['Signature'] = summary_str
+            if not self._is_at_section():
+                self['Summary'] = self._doc.read_to_next_empty_line()
+        else:
+            self['Summary'] = summary
+
+        if not self._is_at_section():
+            self['Extended Summary'] = self._read_to_next_section()
+    
+    def _parse(self):
+        self._doc.reset()
+        self._parse_summary()
+
+        for (section,content) in self._read_sections():
+            if not section.startswith('..'):
+                section = ' '.join([s.capitalize() for s in section.split(' ')])
+            if section in ('Parameters', 'Attributes', 'Methods',
+                           'Returns', 'Raises', 'Warns'):
+                self[section] = self._parse_param_list(content)
+            elif section.startswith('.. index::'):
+                self['index'] = self._parse_index(section, content)
+            elif section == 'See Also':
+                self['See Also'] = self._parse_see_also(content)
+            else:
+                self[section] = content
+
+    # string conversion routines
+
+    def _str_header(self, name, symbol='-'):
+        return [name, len(name)*symbol]
+
+    def _str_indent(self, doc, indent=4):
+        out = []
+        for line in doc:
+            out += [' '*indent + line]
+        return out
+
+    def _str_signature(self):
+        if self['Signature']:
+            return [self['Signature'].replace('*','\*')] + ['']
+        else:
+            return ['']
+
+    def _str_summary(self):
+        if self['Summary']:
+            return self['Summary'] + ['']
+        else:
+            return []
+
+    def _str_extended_summary(self):
+        if self['Extended Summary']:
+            return self['Extended Summary'] + ['']
+        else:
+            return []
+
+    def _str_param_list(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            for param,param_type,desc in self[name]:
+                out += ['%s : %s' % (param, param_type)]
+                out += self._str_indent(desc)
+            out += ['']
+        return out
+
+    def _str_section(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            out += self[name]
+            out += ['']
+        return out
+
+    def _str_see_also(self, func_role):
+        if not self['See Also']: return []
+        out = []
+        out += self._str_header("See Also")
+        last_had_desc = True
+        for func, desc, role in self['See Also']:
+            if role:
+                link = ':%s:`%s`' % (role, func)
+            elif func_role:
+                link = ':%s:`%s`' % (func_role, func)
+            else:
+                link = "`%s`_" % func
+            if desc or last_had_desc:
+                out += ['']
+                out += [link]
+            else:
+                out[-1] += ", %s" % link
+            if desc:
+                out += self._str_indent([' '.join(desc)])
+                last_had_desc = True
+            else:
+                last_had_desc = False
+        out += ['']
+        return out
+
+    def _str_index(self):
+        idx = self['index']
+        out = []
+        out += ['.. index:: %s' % idx.get('default','')]
+        for section, references in idx.iteritems():
+            if section == 'default':
+                continue
+            out += ['   :%s: %s' % (section, ', '.join(references))]
+        return out
+
+    def __str__(self, func_role=''):
+        out = []
+        out += self._str_signature()
+        out += self._str_summary()
+        out += self._str_extended_summary()
+        for param_list in ('Parameters','Returns','Raises'):
+            out += self._str_param_list(param_list)
+        out += self._str_section('Warnings')
+        out += self._str_see_also(func_role)
+        for s in ('Notes','References','Examples'):
+            out += self._str_section(s)
+        out += self._str_index()
+        return '\n'.join(out)
+
+
+def indent(str,indent=4):
+    indent_str = ' '*indent
+    if str is None:
+        return indent_str
+    lines = str.split('\n')
+    return '\n'.join(indent_str + l for l in lines)
+
+def header(text, style='-'):
+    return text + '\n' + style*len(text) + '\n'
+
+
+class FunctionDoc(NumpyDocString):
+    def __init__(self, func, role='func'):
+        self._f = func
+        self._role = role # e.g. "func" or "meth"
+        try:
+            NumpyDocString.__init__(self,inspect.getdoc(func) or '')
+        except ValueError, e:
+            print '*'*78
+            print "ERROR: '%s' while parsing `%s`" % (e, self._f)
+            print '*'*78
+            #print "Docstring follows:"
+            #print doclines
+            #print '='*78
+
+        if not self['Signature']:
+            func, func_name = self.get_func()
+            try:
+                # try to read signature
+                argspec = inspect.getargspec(func)
+                argspec = inspect.formatargspec(*argspec)
+                argspec = argspec.replace('*','\*')
+                signature = '%s%s' % (func_name, argspec)
+            except TypeError, e:
+                signature = '%s()' % func_name
+            self['Signature'] = signature
+
+    def get_func(self):
+        func_name = getattr(self._f, '__name__', self.__class__.__name__)
+        if inspect.isclass(self._f):
+            func = getattr(self._f, '__call__', self._f.__init__)
+        else:
+            func = self._f
+        return func, func_name
+            
+    def __str__(self):
+        out = ''
+
+        func, func_name = self.get_func()
+        signature = self['Signature'].replace('*', '\*')
+
+        roles = {'func': 'function',
+                 'meth': 'method'}
+
+        if self._role:
+            if not roles.has_key(self._role):
+                print "Warning: invalid role %s" % self._role
+            out += '.. %s:: %s\n    \n\n' % (roles.get(self._role,''),
+                                             func_name)
+
+        out += super(FunctionDoc, self).__str__(func_role=self._role)
+        return out
+
+
+class ClassDoc(NumpyDocString):
+    def __init__(self,cls,modulename='',func_doc=FunctionDoc):
+        if not inspect.isclass(cls):
+            raise ValueError("Initialise using a class. Got %r" % cls)
+        self._cls = cls
+
+        if modulename and not modulename.endswith('.'):
+            modulename += '.'
+        self._mod = modulename
+        self._name = cls.__name__
+        self._func_doc = func_doc
+
+        NumpyDocString.__init__(self, pydoc.getdoc(cls))
+
+    @property
+    def methods(self):
+        return [name for name,func in inspect.getmembers(self._cls)
+                if not name.startswith('_') and callable(func)]
+
+    def __str__(self):
+        out = ''
+        out += super(ClassDoc, self).__str__()
+        out += "\n\n"
+
+        #for m in self.methods:
+        #    print "Parsing `%s`" % m
+        #    out += str(self._func_doc(getattr(self._cls,m), 'meth')) + '\n\n'
+        #    out += '.. index::\n   single: %s; %s\n\n' % (self._name, m)
+
+        return out
+
+

sphinx/ext/autosummary/docscrape_sphinx.py

+import re, inspect, textwrap, pydoc
+from docscrape import NumpyDocString, FunctionDoc, ClassDoc
+
+class SphinxDocString(NumpyDocString):
+    # string conversion routines
+    def _str_header(self, name, symbol='`'):
+        return ['.. rubric:: ' + name, '']
+
+    def _str_field_list(self, name):
+        return [':' + name + ':']
+
+    def _str_indent(self, doc, indent=4):
+        out = []
+        for line in doc:
+            out += [' '*indent + line]
+        return out
+
+    def _str_signature(self):
+        return ['']
+        if self['Signature']:
+            return ['``%s``' % self['Signature']] + ['']
+        else:
+            return ['']
+
+    def _str_summary(self):
+        return self['Summary'] + ['']
+
+    def _str_extended_summary(self):
+        return self['Extended Summary'] + ['']
+
+    def _str_param_list(self, name):
+        out = []
+        if self[name]:
+            out += self._str_field_list(name)
+            out += ['']
+            for param,param_type,desc in self[name]:
+                out += self._str_indent(['**%s** : %s' % (param.strip(),
+                                                          param_type)])
+                out += ['']
+                out += self._str_indent(desc,8)
+                out += ['']
+        return out
+
+    def _str_section(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            out += ['']
+            content = textwrap.dedent("\n".join(self[name])).split("\n")
+            out += content
+            out += ['']
+        return out
+
+    def _str_see_also(self, func_role):
+        out = []
+        if self['See Also']:
+            see_also = super(SphinxDocString, self)._str_see_also(func_role)
+            out = ['.. seealso::', '']
+            out += self._str_indent(see_also[2:])
+        return out
+
+    def _str_warnings(self):
+        out = []
+        if self['Warnings']:
+            out = ['.. warning::', '']
+            out += self._str_indent(self['Warnings'])
+        return out
+
+    def _str_index(self):
+        idx = self['index']
+        out = []
+        if len(idx) == 0:
+            return out
+
+        out += ['.. index:: %s' % idx.get('default','')]
+        for section, references in idx.iteritems():
+            if section == 'default':
+                continue
+            elif section == 'refguide':
+                out += ['   single: %s' % (', '.join(references))]
+            else:
+                out += ['   %s: %s' % (section, ','.join(references))]
+        return out
+
+    def _str_references(self):
+        out = []
+        if self['References']:
+            out += self._str_header('References')
+            if isinstance(self['References'], str):
+                self['References'] = [self['References']]
+            out.extend(self['References'])
+            out += ['']
+        return out
+
+    def __str__(self, indent=0, func_role="obj"):
+        out = []
+        out += self._str_signature()
+        out += self._str_index() + ['']
+        out += self._str_summary()
+        out += self._str_extended_summary()
+        for param_list in ('Parameters', 'Attributes', 'Methods',
+                           'Returns','Raises'):
+            out += self._str_param_list(param_list)
+        out += self._str_warnings()
+        out += self._str_see_also(func_role)
+        out += self._str_section('Notes')
+        out += self._str_references()
+        out += self._str_section('Examples')
+        out = self._str_indent(out,indent)
+        return '\n'.join(out)
+
+class SphinxFunctionDoc(SphinxDocString, FunctionDoc):
+    pass
+
+class SphinxClassDoc(SphinxDocString, ClassDoc):
+    pass
+
+def get_doc_object(obj, what=None):
+    if what is None:
+        if inspect.isclass(obj):
+            what = 'class'
+        elif inspect.ismodule(obj):
+            what = 'module'
+        elif callable(obj):
+            what = 'function'
+        else:
+            what = 'object'
+    if what == 'class':
+        return SphinxClassDoc(obj, '', func_doc=SphinxFunctionDoc)
+    elif what in ('function', 'method'):
+        return SphinxFunctionDoc(obj, '')
+    else:
+        return SphinxDocString(pydoc.getdoc(obj))

sphinx/scripts/__init__.py

Empty file added.

sphinx/scripts/autosummary_generate.py

+#!/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 sphinx.ext.autosummary import import_by_name
+
+from jinja import Environment, PackageLoader
+env =  Environment(loader=PackageLoader('numpyext', 'templates'))
+
+def main():
+    p = optparse.OptionParser(__doc__.strip())
+    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")
+
+    # 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:
+            
+            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, 
+                                       len_functions=len(functions),
+                                       len_classes=len(classes),
+                                       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'))
+                    else:
+                        f.write(format_modulemember(name, 'autoclass'))
+                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()

sphinx/templates/autosummary-module.html

+:mod:`{{name}}`
+===============================================================================================================================================
+
+
+.. 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 %}