From c1bedfc1055b303b067b4acd4e1194a370f8a39d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 22 Jun 2008 21:02:50 +0000 Subject: [PATCH] Add general docstring processing support with a new event in autodoc. --- CHANGES | 4 +++ doc/conf.py | 4 +-- doc/ext/appapi.rst | 10 +++--- doc/ext/autodoc.rst | 37 +++++++++++++++++++++ sphinx/environment.py | 6 ++++ sphinx/ext/autodoc.py | 74 +++++++++++++++++++++++++++++++++++------- sphinx/ext/coverage.py | 1 - 7 files changed, 117 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index 90a12feac..520d88b3a 100644 --- a/CHANGES +++ b/CHANGES @@ -67,6 +67,10 @@ New features added 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 diff --git a/doc/conf.py b/doc/conf.py index bead46107..06dca15df 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -131,8 +131,6 @@ latex_logo = '_static/sphinx.png' # Documents to append as an appendix to all manuals. #latex_appendices = [] -automodule_skip_lines = 4 - # Extension interface # ------------------- @@ -180,6 +178,8 @@ def parse_event(env, sig, signode): 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') diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index edd0537a6..deff82b4b 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -162,24 +162,24 @@ package. 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. diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index 50e9efc7f..2d329af3b 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -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 documentation. + .. 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 @@ -164,3 +168,36 @@ There are also new config values that you can set: 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 diff --git a/sphinx/environment.py b/sphinx/environment.py index 749f92c0d..a10adb5f4 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -209,6 +209,9 @@ class BuildEnvironment: self.srcdir = srcdir self.config = config + # the application object; only set while update() runs + self.app = None + # the docutils settings for building self.settings = default_settings.copy() self.settings['env'] = self @@ -420,6 +423,7 @@ class BuildEnvironment: yield msg self.config = config + self.app = app # clear all files no longer present for docname in removed: @@ -434,6 +438,8 @@ class BuildEnvironment: self.warn(None, 'master file %s not found' % self.doc2path(config.master_doc)) + self.app = 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): diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index ab1b12b36..2e1950fbd 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -78,6 +78,51 @@ class AutodocReporter(object): 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): """Check if the object is some kind of descriptor.""" for item in '__get__', '__set__', '__delete__': @@ -127,7 +172,7 @@ def get_module_charset(module): 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): @@ -168,7 +213,12 @@ def get_doc(what, obj, env): except UnicodeError: # last resort -- can't fail 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 @@ -336,7 +386,7 @@ def generate_rst(what, name, members, options, add_content, document, lineno, 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 @@ -364,7 +414,7 @@ def generate_rst(what, name, members, options, add_content, document, lineno, members_check_module = True all_members = inspect.getmembers(todoc) else: - if options.inherited: + if options.inherited_members: # getmembers() uses dir() which pulls in members from all base classes all_members = inspect.getmembers(todoc) else: @@ -378,7 +428,7 @@ def generate_rst(what, name, members, options, add_content, document, lineno, continue # 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: continue if what == 'module': if isinstance(member, types.FunctionType): @@ -420,11 +470,11 @@ def _auto_directive(dirname, arguments, options, content, lineno, 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 @@ -465,16 +515,16 @@ def auto_directive_withmembers(*args, **kwds): 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, @@ -489,5 +539,7 @@ def setup(app): noindex=directives.flag) app.add_directive('autoattribute', auto_directive, 1, (1, 0, 1), noindex=directives.flag) + # 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') diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 9b3826e76..98b291316 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -239,4 +239,3 @@ def setup(app): 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) -