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

This commit is contained in:
Georg Brandl 2008-06-22 21:02:50 +00:00
parent abddd96093
commit c1bedfc105
7 changed files with 117 additions and 19 deletions

View File

@ -67,6 +67,10 @@ New features added
are now supported. are now supported.
* Extensions: * 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 - The `autodoc` extension accepts signatures for functions, methods
and classes now that override the signature got via introspection and classes now that override the signature got via introspection

View File

@ -131,8 +131,6 @@ latex_logo = '_static/sphinx.png'
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#latex_appendices = [] #latex_appendices = []
automodule_skip_lines = 4
# Extension interface # Extension interface
# ------------------- # -------------------
@ -180,6 +178,8 @@ def parse_event(env, sig, signode):
def setup(app): 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('directive', 'dir', 'pair: %s; directive', parse_directive)
app.add_description_unit('role', 'role', 'pair: %s; role', parse_role) app.add_description_unit('role', 'role', 'pair: %s; role', parse_role)
app.add_description_unit('confval', 'confval', 'pair: %s; configuration value') app.add_description_unit('confval', 'confval', 'pair: %s; configuration value')

View File

@ -162,24 +162,24 @@ package.
Sphinx core events 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. registered event handlers.
.. event:: builder-inited () .. event:: builder-inited (app)
Emitted the builder object has been created. 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 Emitted when a doctree has been parsed and read by the environment, and is
about to be pickled. 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 Emitted when a doctree has been "resolved" by the environment, that is, all
references and TOCs have been inserted. 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 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. template with -- this can be used to add custom elements to the context.

View File

@ -148,6 +148,10 @@ There are also new config values that you can set:
fields with version control tags, that you don't want to put in the generated fields with version control tags, that you don't want to put in the generated
documentation. documentation.
.. deprecated:: 0.4
Use the more versatile docstring processing provided by
:event:`autodoc-process-docstring`.
.. confval:: autoclass_content .. confval:: autoclass_content
This value selects what content will be inserted into the main body of an This value selects what content will be inserted into the main body of an
@ -164,3 +168,36 @@ There are also new config values that you can set:
Only the ``__init__`` method's docstring is inserted. Only the ``__init__`` method's docstring is inserted.
.. versionadded:: 0.3 .. 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

View File

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

View File

@ -78,6 +78,51 @@ class AutodocReporter(object):
return self.system_message(4, *args, **kwargs) 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:`conf.py`)::
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): def isdescriptor(x):
"""Check if the object is some kind of descriptor.""" """Check if the object is some kind of descriptor."""
for item in '__get__', '__set__', '__delete__': for item in '__get__', '__set__', '__delete__':
@ -127,7 +172,7 @@ def get_module_charset(module):
return charset 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.""" """Format and yield lines of the docstring(s) for the object."""
docstrings = [] docstrings = []
if getattr(obj, '__doc__', None): if getattr(obj, '__doc__', None):
@ -168,7 +213,12 @@ def get_doc(what, obj, env):
except UnicodeError: except UnicodeError:
# last resort -- can't fail # last resort -- can't fail
docstring = docstring.decode('latin1') docstring = docstring.decode('latin1')
for line in prepare_docstring(docstring): docstringlines = prepare_docstring(docstring)
if env.app:
# let extensions preprocess docstrings
env.app.emit('autodoc-process-docstring',
what, name, obj, options, docstringlines)
for line in docstringlines:
yield line yield line
@ -336,7 +386,7 @@ def generate_rst(what, name, members, options, add_content, document, lineno,
sourcename = 'docstring of %s' % fullname sourcename = 'docstring of %s' % fullname
# add content from docstrings # 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) result.append(indent + line, sourcename, i)
# add source content, if present # add source content, if present
@ -364,7 +414,7 @@ def generate_rst(what, name, members, options, add_content, document, lineno,
members_check_module = True members_check_module = True
all_members = inspect.getmembers(todoc) all_members = inspect.getmembers(todoc)
else: else:
if options.inherited: if options.inherited_members:
# getmembers() uses dir() which pulls in members from all base classes # getmembers() uses dir() which pulls in members from all base classes
all_members = inspect.getmembers(todoc) all_members = inspect.getmembers(todoc)
else: else:
@ -378,7 +428,7 @@ def generate_rst(what, name, members, options, add_content, document, lineno,
continue continue
# ignore undocumented members if :undoc-members: is not given # ignore undocumented members if :undoc-members: is not given
doc = getattr(member, '__doc__', None) doc = getattr(member, '__doc__', None)
if not options.undoc and not doc: if not options.undoc_members and not doc:
continue continue
if what == 'module': if what == 'module':
if isinstance(member, types.FunctionType): if isinstance(member, types.FunctionType):
@ -420,11 +470,11 @@ def _auto_directive(dirname, arguments, options, content, lineno,
name = arguments[0] name = arguments[0]
genopt = Options() genopt = Options()
members = options.get('members', []) members = options.get('members', [])
genopt.inherited = 'inherited-members' in options genopt.inherited_members = 'inherited-members' in options
if genopt.inherited and not members: if genopt.inherited_members and not members:
# :inherited-members: implies :members: # :inherited-members: implies :members:
members = ['__all__'] members = ['__all__']
genopt.undoc = 'undoc-members' in options genopt.undoc_members = 'undoc-members' in options
genopt.show_inheritance = 'show-inheritance' in options genopt.show_inheritance = 'show-inheritance' in options
genopt.noindex = 'noindex' in options genopt.noindex = 'noindex' in options
@ -465,16 +515,16 @@ def auto_directive_withmembers(*args, **kwds):
return _auto_directive(*args, **kwds) return _auto_directive(*args, **kwds)
def members_directive(arg): def members_option(arg):
if arg is None: if arg is None:
return ['__all__'] return ['__all__']
return [x.strip() for x in arg.split(',')] return [x.strip() for x in arg.split(',')]
def setup(app): def setup(app):
mod_options = {'members': members_directive, 'undoc-members': directives.flag, mod_options = {'members': members_option, 'undoc-members': directives.flag,
'noindex': 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, 'noindex': directives.flag, 'inherited-members': directives.flag,
'show-inheritance': directives.flag} 'show-inheritance': directives.flag}
app.add_directive('automodule', auto_directive_withmembers, app.add_directive('automodule', auto_directive_withmembers,
@ -489,5 +539,7 @@ def setup(app):
noindex=directives.flag) noindex=directives.flag)
app.add_directive('autoattribute', auto_directive, 1, (1, 0, 1), app.add_directive('autoattribute', auto_directive, 1, (1, 0, 1),
noindex=directives.flag) noindex=directives.flag)
# deprecated: remove in some future version.
app.add_config_value('automodule_skip_lines', 0, True) app.add_config_value('automodule_skip_lines', 0, True)
app.add_config_value('autoclass_content', 'class', True) app.add_config_value('autoclass_content', 'class', True)
app.add_event('autodoc-process-docstring')

View File

@ -239,4 +239,3 @@ def setup(app):
app.add_config_value('coverage_c_path', [], False) app.add_config_value('coverage_c_path', [], False)
app.add_config_value('coverage_c_regexes', {}, False) app.add_config_value('coverage_c_regexes', {}, False)
app.add_config_value('coverage_ignore_c_items', {}, False) app.add_config_value('coverage_ignore_c_items', {}, False)