georg.brandl  committed 5d253be

Add general docstring processing support with a new event in autodoc.

  • Participants
  • Parent commits a8addca
  • Branches default

Comments (0)

Files changed (7)

   are now supported.
 * Extensions:
+  - The autodoc extension now offers a much more flexible way to
+    manipulate docstrings before including them into the output, via
+    the new `autodoc-process-docstring` event.
   - The `autodoc` extension accepts signatures for functions, methods
     and classes now that override the signature got via introspection
 # Documents to append as an appendix to all manuals.
 #latex_appendices = []
-automodule_skip_lines = 4
 # Extension interface
 # -------------------
 def setup(app):
+    from sphinx.ext.autodoc import cut_lines
+    app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
     app.add_description_unit('directive', 'dir', 'pair: %s; directive', parse_directive)
     app.add_description_unit('role', 'role', 'pair: %s; role', parse_role)
     app.add_description_unit('confval', 'confval', 'pair: %s; configuration value')

File doc/ext/appapi.rst

 Sphinx core events
-These events are known to the core.  The arguments showed are given to the
+These events are known to the core.  The arguments shown are given to the
 registered event handlers.
-.. event:: builder-inited ()
+.. event:: builder-inited (app)
    Emitted the builder object has been created.
-.. event:: doctree-read (doctree)
+.. event:: doctree-read (app, doctree)
    Emitted when a doctree has been parsed and read by the environment, and is
    about to be pickled.
-.. event:: doctree-resolved (doctree, docname)
+.. event:: doctree-resolved (app, doctree, docname)
    Emitted when a doctree has been "resolved" by the environment, that is, all
    references and TOCs have been inserted.
-.. event:: page-context (pagename, templatename, context, doctree)
+.. event:: page-context (app, pagename, templatename, context, doctree)
    Emitted when the HTML builder has created a context dictionary to render a
    template with -- this can be used to add custom elements to the context.

File doc/ext/autodoc.rst

    fields with version control tags, that you don't want to put in the generated
+   .. deprecated:: 0.4
+      Use the more versatile docstring processing provided by
+      :event:`autodoc-process-docstring`.
 .. confval:: autoclass_content
    This value selects what content will be inserted into the main body of an
       Only the ``__init__`` method's docstring is inserted.
    .. versionadded:: 0.3
+Docstring preprocessing
+.. versionadded:: 0.4
+autodoc provides the following additional event:
+.. event:: autodoc-process-docstring (app, what, name, obj, options, lines)
+   Emitted when autodoc has read and processed a docstring.  *lines* is a list
+   of strings -- the lines of the processed docstring -- that the event handler
+   can modify **in place** to change what Sphinx puts into the output.
+   :param app: the Sphinx application object
+   :param what: the type of the object which the docstring belongs to (one of
+      ``"module"``, ``"class"``, ``"exception"``, ``"function"``, ``"method"``,
+      ``"attribute"``)
+   :param name: the fully qualified name of the object
+   :param obj: the object itself
+   :param options: the options given to the directive: an object with attributes
+      ``inherited_members``, ``undoc_members``, ``show_inheritance`` and
+      ``noindex`` that are true if the flag option of same name was given to the
+      auto directive
+   :param lines: the lines of the docstring, see above
+The :mod:`sphinx.ext.autodoc` module provides factory functions for commonly
+needed docstring processing:
+.. autofunction:: cut_lines
+.. autofunction:: between

File sphinx/

         self.srcdir = srcdir
         self.config = config
+        # the application object; only set while update() runs
+ = None
         # the docutils settings for building
         self.settings = default_settings.copy()
         self.settings['env'] = self
         yield msg
         self.config = config
+ = app
         # clear all files no longer present
         for docname in removed:
             self.warn(None, 'master file %s not found' %
+ = None
         # remove all non-existing images from inventory
         for imgsrc in self.images.keys():
             if not os.access(path.join(self.srcdir, imgsrc), os.R_OK):

File sphinx/ext/

         return self.system_message(4, *args, **kwargs)
+# Some useful event listener factories for autodoc-process-docstring.
+def cut_lines(pre, post=0, what=None):
+    """
+    Return a listener that removes the first *pre* and last *post*
+    lines of every docstring.  If *what* is a sequence of strings,
+    only docstrings of a type in *what* will be processed.
+    Use like this (e.g. in the ``setup()`` function of :file:``)::
+       from sphinx.ext.autodoc import cut_lines
+       app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
+    This can (and should) be used in place of :confval:`automodule_skip_lines`.
+    """
+    def process(app, what_, name, obj, options, lines):
+        if what and what_ not in what:
+            return
+        del lines[:pre]
+        if post:
+            del lines[-post:]
+    return process
+def between(marker, what=None):
+    """
+    Return a listener that only keeps lines between the first two lines that
+    match the *marker* regular expression.  If *what* is a sequence of strings,
+    only docstrings of a type in *what* will be processed.
+    """
+    marker_re = re.compile(marker)
+    def process(app, what_, name, obj, options, lines):
+        if what and what_ not in what:
+            return
+        seen = 0
+        for i, line in enumerate(lines[:]):
+            if marker_re.match(line):
+                if not seen:
+                    del lines[:i+1]
+                    seen = i+1
+                else:
+                    del lines[i-seen:]
+                    break
+    return process
 def isdescriptor(x):
     """Check if the object is some kind of descriptor."""
     for item in '__get__', '__set__', '__delete__':
     return charset
-def get_doc(what, obj, env):
+def get_doc(what, name, obj, options, env):
     """Format and yield lines of the docstring(s) for the object."""
     docstrings = []
     if getattr(obj, '__doc__', None):
                 except UnicodeError:
                     # last resort -- can't fail
                     docstring = docstring.decode('latin1')
-        for line in prepare_docstring(docstring):
+        docstringlines = prepare_docstring(docstring)
+        if
+            # let extensions preprocess docstrings
+  'autodoc-process-docstring',
+                         what, name, obj, options, docstringlines)
+        for line in docstringlines:
             yield line
         sourcename = 'docstring of %s' % fullname
     # add content from docstrings
-    for i, line in enumerate(get_doc(what, todoc, env)):
+    for i, line in enumerate(get_doc(what, fullname, todoc, options, env)):
         result.append(indent + line, sourcename, i)
     # add source content, if present
             members_check_module = True
             all_members = inspect.getmembers(todoc)
-            if options.inherited:
+            if options.inherited_members:
                 # getmembers() uses dir() which pulls in members from all base classes
                 all_members = inspect.getmembers(todoc)
         # ignore undocumented members if :undoc-members: is not given
         doc = getattr(member, '__doc__', None)
-        if not options.undoc and not doc:
+        if not options.undoc_members and not doc:
         if what == 'module':
             if isinstance(member, types.FunctionType):
     name = arguments[0]
     genopt = Options()
     members = options.get('members', [])
-    genopt.inherited = 'inherited-members' in options
-    if genopt.inherited and not members:
+    genopt.inherited_members = 'inherited-members' in options
+    if genopt.inherited_members and not members:
         # :inherited-members: implies :members:
         members = ['__all__']
-    genopt.undoc = 'undoc-members' in options
+    genopt.undoc_members = 'undoc-members' in options
     genopt.show_inheritance = 'show-inheritance' in options
     genopt.noindex = 'noindex' in options
     return _auto_directive(*args, **kwds)
-def members_directive(arg):
+def members_option(arg):
     if arg is None:
         return ['__all__']
     return [x.strip() for x in arg.split(',')]
 def setup(app):
-    mod_options = {'members': members_directive, 'undoc-members': directives.flag,
+    mod_options = {'members': members_option, 'undoc-members': directives.flag,
                    'noindex': directives.flag}
-    cls_options = {'members': members_directive, 'undoc-members': directives.flag,
+    cls_options = {'members': members_option, 'undoc-members': directives.flag,
                    'noindex': directives.flag, 'inherited-members': directives.flag,
                    'show-inheritance': directives.flag}
     app.add_directive('automodule', auto_directive_withmembers,
     app.add_directive('autoattribute', auto_directive, 1, (1, 0, 1),
+    # deprecated: remove in some future version.
     app.add_config_value('automodule_skip_lines', 0, True)
     app.add_config_value('autoclass_content', 'class', True)
+    app.add_event('autodoc-process-docstring')

File sphinx/ext/

     app.add_config_value('coverage_c_path', [], False)
     app.add_config_value('coverage_c_regexes', {}, False)
     app.add_config_value('coverage_ignore_c_items', {}, False)