This commit is contained in:
Sebastian Wiesner
2008-12-23 20:25:15 +01:00
15 changed files with 502 additions and 83 deletions

13
CHANGES
View File

@@ -15,6 +15,13 @@ New features added
if name.startswith('_'):
return True
* Markup:
- The ``toctree`` directive now supports a ``:hidden:`` flag,
which will prevent links from being generated in place of
the directive -- this allows you to define your document
structure, but place the links yourself.
* Configuration:
- The new ``html_add_permalinks`` config value can be used to
@@ -28,7 +35,9 @@ New features added
- Italian by Sandro Dentella.
* Extension API:
* Extensions and API:
- Autodoc now handles inner classes and their methods.
- There is now a ``Sphinx.add_lexer()`` method to be able to use
custom Pygments lexers easily.
@@ -41,6 +50,8 @@ New features added
- There is now a ``doctest_global_setup`` config value that can
be used to give setup code for all doctests in the documentation.
- Source links in HTML are now generated with ``rel="nofollow"``.
Release 0.5.2 (in development)
==============================

View File

@@ -35,6 +35,7 @@ included, please mail to `the Google group
* Python: http://docs.python.org/dev/
* Satchmo: http://www.satchmoproject.com/docs/svn/
* Sphinx: http://sphinx.pocoo.org/
* SQLAlchemy: http://www.sqlalchemy.org/docs/
* SymPy: http://docs.sympy.org/
* tinyTiM: http://tinytim.sourceforge.net/docs/2.0/
* TurboGears: http://turbogears.org/2.0/docs/

View File

@@ -16,7 +16,7 @@ ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) \
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " web to make files usable by Sphinx.web"
@echo " pickle to make pickle files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview over all changed/added/deprecated items"
@@ -34,10 +34,6 @@ html:
pickle:
mkdir -p _build/pickle _build/doctrees
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
@echo
@echo "Build finished; now you can run"
@echo " python -m sphinx.web _build/pickle"
@echo "to start the server."
htmlhelp:
mkdir -p _build/htmlhelp _build/doctrees

View File

@@ -85,6 +85,18 @@ tables of contents. The ``toctree`` directive is the central element.
This includes first all documents whose names start with ``intro``, then all
documents in the ``recipe`` folder, then all remaining documents (except the
one containing the directive, of course.) [#]_
You can also give a "hidden" option to the directive, like this::
.. toctree::
:hidden:
doc_1
doc_2
This will still notify Sphinx of the document hierarchy, but not insert links
into the document at the location of the directive -- this makes sense if you
intend to insert these links yourself, in a different style.
In the end, all documents in the :term:`source directory` (or subdirectories)
must occur in some ``toctree`` directive; Sphinx will emit a warning if it
@@ -100,6 +112,9 @@ tables of contents. The ``toctree`` directive is the central element.
.. versionchanged:: 0.3
Added "globbing" option.
.. versionchanged:: 0.6
Added "hidden" option.
Special names
-------------

316
doc/ext/tutorial.rst Normal file
View File

@@ -0,0 +1,316 @@
.. _exttut:
Tutorial: Writing a simple extension
====================================
This section is intended as a walkthrough for the creation of custom extensions.
It covers the basics of writing and activating an extensions, as well as
commonly used features of extensions.
As an example, we will cover a "todo" extension that adds capabilities to
include todo entries in the documentation, and collecting these in a central
place. (A similar "todo" extension is distributed with Sphinx.)
Build Phases
------------
One thing that is vital in order to understand extension mechanisms is the way
in which a Sphinx project is built: this works in several phases.
**Phase 0: Initialization**
In this phase, almost nothing interesting for us happens. The source
directory is searched for source files, and extensions are initialized.
Should a stored build environment exist, it is loaded, otherwise a new one is
created.
**Phase 1: Reading**
In Phase 1, all source files (and on subsequent builds, those that are new or
changed) are read and parsed. This is the phase where directives and roles
are encountered by the docutils, and the corresponding functions are called.
The output of this phase is a *doctree* for each source files, that is a tree
of docutils nodes. For document elements that aren't fully known until all
existing files are read, temporary nodes are created.
During reading, the build environment is updated with all meta- and cross
reference data of the read documents, such as labels, the names of headings,
described Python objects and index entries. This will later be used to
replace the temporary nodes.
The parsed doctrees are stored on the disk, because it is not possible to
hold all of them in memory.
**Phase 2: Consistency checks**
Some checking is done to ensure no surprises in the built documents.
**Phase 3: Resolving**
Now that the metadata and cross-reference data of all existing documents is
known, all temporary nodes are replaced by nodes that can be converted into
output. For example, links are created for object references that exist, and
simple literal nodes are created for those that don't.
**Phase 4: Writing**
This phase converts the resolved doctrees to the desired output format, such
as HTML or LaTeX. This happens via a so-called docutils writer that visits
the individual nodes of each doctree and produces some output in the process.
.. note::
Some builders deviate from this general build plan, for example, the builder
that checks external links does not need anything more than the parsed
doctrees and therefore does not have phases 2--4.
Extension Design
----------------
We want the extension to add the following to Sphinx:
* A "todo" directive, containing some content that is marked with "TODO", and
only shown in the output if a new config value is set. (Todo entries should
not be in the output by default.)
* A "todolist" directive that creates a list of all todo entries throughout the
documentation.
For that, we will need to add the following elements to Sphinx:
* New directives, called ``todo`` and ``todolist``.
* New document tree nodes to represent these directives, conventionally also
called ``todo`` and ``todolist``. We wouldn't need new nodes if the new
directives only produced some content representable by existing nodes.
* A new config value ``todo_include_todos`` (config value names should start
with the extension name, in order to stay unique) that controls whether todo
entries make it into the output.
* New event handlers: one for the :event:`doctree-resolved` event, to replace
the todo and todolist nodes, and one for :event:`env-purge-doc` (the reason
for that will be covered later).
The Setup Function
------------------
.. currentmodule:: sphinx.application
The new elements are added in the extension's setup function. Let us create a
new Python module called :file:`todo.py` and add the setup function::
def setup(app):
app.add_config_value('todo_include_todos', False, False)
app.add_node(todolist)
app.add_node(todo,
html=(visit_todo_node, depart_todo_node),
latex=(visit_todo_node, depart_todo_node),
text=(visit_todo_node, depart_todo_node))
app.add_directive('todo', todo_directive, 1, (0, 0, 1))
app.add_directive('todolist', todolist_directive, 0, (0, 0, 0))
app.connect('doctree-resolved', process_todo_nodes)
app.connect('env-purge-doc', purge_todos)
The calls in this function refer to classes and functions not yet written. What
the individual calls do is the following:
* :meth:`~Sphinx.add_config_value` lets Sphinx know that it should recognize the
new *config value* ``todo_include_todos``, whose default value should be
``False`` (this also tells Sphinx that it is a boolean value).
If the third argument was ``True``, all documents would be re-read if the
config value changed its value. This is needed for config values that
influence reading (build phase 1).
* :meth:`~Sphinx.add_node` adds a new *node class* to the build system. It also
can specify visitor functions for each supported output format. These visitor
functions are needed when the new nodes stay until phase 4 -- since the
``todolist`` node is always replaced in phase 3, it doesn't need any.
We need to create the two node classes ``todo`` and ``todolist`` later.
* :meth:`~Sphinx.add_directive` adds a new *directive*, given by name, handler
function and two arguments that specify if the directive has content and how
many arguments it accepts.
The handler functions are created later.
* Finally, :meth:`~Sphinx.connect` adds an *event handler* to the event whose
name is given by the first argument. The event handler function is called
with several arguments which are documented with the event.
The Node Classes
----------------
Let's start with the node classes::
from docutils import nodes
class todo(nodes.Admonition, nodes.Element):
pass
class todolist(nodes.General, nodes.Element):
pass
def visit_todo_node(self, node):
self.visit_admonition(node)
def depart_todo_node(self, node):
self.depart_admonition(node)
Node classes usually don't have to do anything except inherit from the standard
docutils classes defined in :mod:`docutils.nodes`. ``todo`` inherits from
``Admonition`` because it should be handled like a note or warning, ``todolist``
is just a "general" node.
The Directive Handlers
----------------------
A directive handler is a function with a host of arguments, covered in detail in
the docutils documentation. It must return a list of nodes.
The ``todolist`` directive is quite simple::
def todolist_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
return [todolist('')]
An instance of our ``todolist`` node class is created and returned. The
todolist directive has neither content nor arguments that need to be handled.
The ``todo`` directive function looks like this::
from sphinx.util.compat import make_admonition
def todo_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
env = state.document.settings.env
targetid = "todo-%s" % env.index_num
env.index_num += 1
targetnode = nodes.target('', '', ids=[targetid])
ad = make_admonition(todo, name, [_('Todo')], options, content, lineno,
content_offset, block_text, state, state_machine)
if not hasattr(env, 'todo_all_todos'):
env.todo_all_todos = []
env.todo_all_todos.append({
'docname': env.docname,
'lineno': lineno,
'todo': ad[0].deepcopy(),
'target': targetnode,
})
return [targetnode] + ad
Several important things are covered here. First, as you can see, you can refer
to the build environment instance using ``state.document.settings.env``.
Then, to act as a link target (from the todolist), the todo directive needs to
return a target node in addition to the todo node. The target ID (in HTML, this
will be the anchor name) is generated by using ``env.index_num`` which is
persistent between directive calls and therefore leads to unique target names.
The target node is instantiated without any text (the first two arguments).
An admonition is created using a standard docutils function (wrapped in Sphinx
for docutils cross-version compatibility). The first argument gives the node
class, in our case ``todo``. The third argument gives the admonition title (use
``arguments`` here to let the user specify the title). A list of nodes is
returned from ``make_admonition``.
Then, the todo node is added to the environment. This is needed to be able to
create a list of all todo entries throughout the documentation, in the place
where the author puts a ``todolist`` directive. For this case, the environment
attribute ``todo_all_todos`` is used (again, the name should be unique, so it is
prefixed by the extension name). It does not exist when a new environment is
created, so the directive must check and create it if necessary. Various
information about the todo entry's location are stored along with a copy of the
node.
In the last line, the nodes that should be put into the doctree are returned:
the target node and the admonition node.
The Event Handlers
------------------
Finally, let's look at the event handlers. First, the one for the
:event:`env-purge-doc` event::
def purge_todos(app, env, docname):
if not hasattr(env, 'todo_all_todos'):
return
env.todo_all_todos = [todo for todo in env.todo_all_todos
if todo['docname'] != docname]
Since we store information from source files in the environment, which is
persistent, it may become out of date when the source file changes. Therefore,
before each source file is read, the environment's records of it are cleared,
and the :event:`env-purge-doc` event gives extensions a chance to do the same.
Here we clear out all todos whose docname matches the given one from the
``todo_all_todos`` list. If there are todos left in the document, they will be
added again during parsing.
The other handler belongs to the :event:`doctree-resolved` event. This event is
emitted at the end of phase 3 and allows custom resolving to be done::
def process_todo_nodes(app, doctree, fromdocname):
if not app.config.todo_include_todos:
for node in doctree.traverse(todo_node):
node.parent.remove(node)
# Replace all todolist nodes with a list of the collected todos.
# Augment each todo with a backlink to the original location.
env = app.builder.env
for node in doctree.traverse(todolist):
if not app.config.todo_include_todos:
node.replace_self([])
continue
content = []
for todo_info in env.todo_all_todos:
para = nodes.paragraph()
filename = env.doc2path(todo_info['docname'], base=None)
description = (
_('(The original entry is located in %s, line %d and can be found ') %
(filename, todo_info['lineno']))
para += nodes.Text(description, description)
# Create a reference
newnode = nodes.reference('', '')
innernode = nodes.emphasis(_('here'), _('here'))
newnode['refdocname'] = todo_info['docname']
newnode['refuri'] = app.builder.get_relative_uri(
fromdocname, todo_info['docname'])
newnode['refuri'] += '#' + todo_info['target']['refid']
newnode.append(innernode)
para += newnode
para += nodes.Text('.)', '.)')
# Insert into the todolist
content.append(todo_info['todo'])
content.append(para)
node.replace_self(content)
It is a bit more involved. If our new "todo_include_todos" config value is
false, all todo and todolist nodes are removed from the documents.
If not, todo nodes just stay where and how they are. Todolist nodes are
replaced by a list of todo entries, complete with backlinks to the location
where they come from. The list items are composed of the nodes from the todo
entry and docutils nodes created on the fly: a paragraph for each entry,
containing text that gives the location, and a link (reference node containing
an italic node) with the backreference. The reference URI is built by
``app.builder.get_relative_uri`` which creates a suitable URI depending on the
used builder, and appending the todo node's (the target's) ID as the anchor
name.

View File

@@ -9,17 +9,25 @@ Sphinx Extensions
Since many projects will need special features in their documentation, Sphinx is
designed to be extensible on several levels.
First, you can add new :term:`builder`\s to support new output formats or
actions on the parsed documents. Then, it is possible to register custom
reStructuredText roles and directives, extending the markup. And finally, there
are so-called "hook points" at strategic places throughout the build process,
where an extension can register a hook and run specialized code.
This is what you can do in an extension: First, you can add new
:term:`builder`\s to support new output formats or actions on the parsed
documents. Then, it is possible to register custom reStructuredText roles and
directives, extending the markup. And finally, there are so-called "hook
points" at strategic places throughout the build process, where an extension can
register a hook and run specialized code.
The configuration file itself can be an extension, see the :confval:`extensions`
configuration value docs.
An extension is simply a Python module. When an extension is loaded, Sphinx
imports this module and executes its ``setup()`` function, which in turn
notifies Sphinx of everything the extension offers -- see the extension tutorial
for examples.
The configuration file itself can be treated as an extension if it contains a
``setup()`` function. All other extensions to load must be listed in the
:confval:`extensions` configuration value.
.. toctree::
ext/tutorial
ext/appapi
ext/builderapi
@@ -48,4 +56,27 @@ Third-party extensions
There are several extensions that are not (yet) maintained in the Sphinx
distribution. The `Wiki at BitBucket`_ maintains a list of those.
If you write an extension that you think others will find useful, please write
to the project mailing list (sphinx-dev@googlegroups.com) and we'll find the
proper way of including or hosting it for the public.
.. _Wiki at BitBucket: http://www.bitbucket.org/birkenfeld/sphinx/wiki/Home
Where to put your own extensions?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Extensions local to a project should be put within the project's directory
structure. Set Python's module search path, ``sys.path``, accordingly so that
Sphinx can find them.
E.g., if your extension ``foo.py`` lies in the ``exts`` subdirectory of the
project root, put into :file:`conf.py`::
import sys, os
sys.path.append(os.path.abspath('exts'))
extensions = ['foo']
You can also install extensions anywhere else on ``sys.path``, e.g. in the
``site-packages`` directory.

View File

@@ -135,6 +135,10 @@ The following blocks exist in the ``layout`` template:
`sidebarrel`
The relation links (previous, next document) within the sidebar.
`sidebarsourcelink`
The "Show source" link within the sidebar (normally only shown if this is
enabled by :confval:`html_show_sourcelink`).
`sidebarsearch`
The search box within the sidebar. Override this if you want to place some
content at the bottom of the sidebar.

View File

@@ -29,7 +29,6 @@ def toctree_directive(name, arguments, options, content, lineno,
glob = 'glob' in options
ret = []
subnode = addnodes.toctree()
includefiles = []
includetitles = {}
all_docnames = env.found_docs.copy()
@@ -66,15 +65,18 @@ def toctree_directive(name, arguments, options, content, lineno,
ret.append(state.document.reporter.warning(
'toctree glob pattern %r didn\'t match any documents' % entry,
line=lineno))
subnode = addnodes.toctree()
subnode['includefiles'] = includefiles
subnode['includetitles'] = includetitles
subnode['maxdepth'] = options.get('maxdepth', -1)
subnode['glob'] = glob
subnode['hidden'] = 'hidden' in options
ret.append(subnode)
return ret
toctree_directive.content = 1
toctree_directive.options = {'maxdepth': int, 'glob': directives.flag}
toctree_directive.options = {'maxdepth': int, 'glob': directives.flag,
'hidden': directives.flag}
directives.register_directive('toctree', toctree_directive)

View File

@@ -902,6 +902,8 @@ class BuildEnvironment:
If *titles_only* is True, only toplevel document titles will be in the
resulting tree.
"""
if toctree.get('hidden', False):
return None
def _walk_depth(node, depth, maxdepth, titleoverrides):
"""Utility: Cut a TOC at a specified depth."""

View File

@@ -23,7 +23,6 @@ from docutils.parsers.rst import directives
from docutils.statemachine import ViewList
from sphinx.util import rpartition, nested_parse_with_titles
from sphinx.directives.desc import py_sig_re
try:
base_exception = BaseException
@@ -33,6 +32,14 @@ except NameError:
_charset_re = re.compile(r'coding[:=]\s*([-\w.]+)')
_module_charsets = {}
py_ext_sig_re = re.compile(
r'''^ ([\w.]+::)? # explicit module name
([\w.]+\.)? # module and/or class name(s)
(\w+) \s* # thing name
(?: \((.*)\) # optional arguments
(\s* -> \s* .*)? )? $ # optional return annotation
''', re.VERBOSE)
class Options(object):
pass
@@ -282,55 +289,64 @@ class RstGenerator(object):
# first, parse the definition -- auto directives for classes and functions
# can contain a signature which is then used instead of an autogenerated one
try:
path, base, args, retann = py_sig_re.match(name).groups()
mod, path, base, args, retann = py_ext_sig_re.match(name).groups()
except:
self.warn('invalid signature for auto%s (%r)' % (what, name))
return
# fullname is the fully qualified name, base the name after the last dot
fullname = (path or '') + base
return None, [], None, None
# support explicit module and class name separation via ::
if mod is not None:
mod = mod[:-2]
parents = path and path.rstrip('.').split('.') or []
else:
parents = []
if what == 'module':
if mod is not None:
self.warn('"::" in automodule name doesn\'t make sense')
if args or retann:
self.warn('ignoring signature arguments and return annotation '
'for automodule %s' % fullname)
return fullname, fullname, [], None, None
'for automodule %s' % name)
return (path or '') + base, [], None, None
elif what in ('class', 'exception', 'function'):
if path:
mod = path.rstrip('.')
else:
mod = None
# if documenting a toplevel object without explicit module, it can
# be contained in another auto directive ...
if hasattr(self.env, 'autodoc_current_module'):
mod = self.env.autodoc_current_module
# ... or in the scope of a module directive
if not mod:
mod = self.env.currmodule
return fullname, mod, [base], args, retann
elif what in ('exception', 'function', 'class'):
if mod is None:
if path:
mod = path.rstrip('.')
else:
# if documenting a toplevel object without explicit module, it can
# be contained in another auto directive ...
if hasattr(self.env, 'autodoc_current_module'):
mod = self.env.autodoc_current_module
# ... or in the scope of a module directive
if not mod:
mod = self.env.currmodule
return mod, parents + [base], args, retann
else:
if path:
mod_cls = path.rstrip('.')
else:
mod_cls = None
# if documenting a class-level object without path, there must be a
# current class, either from a parent auto directive ...
if hasattr(self.env, 'autodoc_current_class'):
mod_cls = self.env.autodoc_current_class
# ... or from a class directive
if mod_cls is None:
mod_cls = self.env.currclass
# ... if still None, there's no way to know
if mod_cls is None:
return fullname, None, [], args, retann
mod, cls = rpartition(mod_cls, '.')
# if the module name is still missing, get it like above
if not mod and hasattr(self.env, 'autodoc_current_module'):
mod = self.env.autodoc_current_module
if not mod:
mod = self.env.currmodule
return fullname, mod, [cls, base], args, retann
if mod is None:
if path:
mod_cls = path.rstrip('.')
else:
mod_cls = None
# if documenting a class-level object without path, there must be a
# current class, either from a parent auto directive ...
if hasattr(self.env, 'autodoc_current_class'):
mod_cls = self.env.autodoc_current_class
# ... or from a class directive
if mod_cls is None:
mod_cls = self.env.currclass
# ... if still None, there's no way to know
if mod_cls is None:
return None, [], None, None
mod, cls = rpartition(mod_cls, '.')
parents = [cls]
# if the module name is still missing, get it like above
if not mod and hasattr(self.env, 'autodoc_current_module'):
mod = self.env.autodoc_current_module
if not mod:
mod = self.env.currmodule
return mod, parents + [base], args, retann
def format_signature(self, what, name, obj, args, retann):
"""
@@ -386,13 +402,15 @@ class RstGenerator(object):
"""
Generate reST for the object in self.result.
"""
fullname, mod, objpath, args, retann = self.resolve_name(what, name)
mod, objpath, args, retann = self.resolve_name(what, name)
if not mod:
# need a module to import
self.warn('don\'t know which module to import for autodocumenting %r '
'(try placing a "module" or "currentmodule" directive in the '
'document, or giving an explicit module name)' % fullname)
'document, or giving an explicit module name)' % name)
return
# fully-qualified name
fullname = mod + (objpath and '.' + '.'.join(objpath) or '')
# the name to put into the generated directive -- doesn't contain the module
name_in_directive = '.'.join(objpath) or mod
@@ -423,7 +441,7 @@ class RstGenerator(object):
# format the object's signature, if any
try:
sig = self.format_signature(what, name, todoc, args, retann)
sig = self.format_signature(what, fullname, todoc, args, retann)
except Exception, err:
self.warn('error while formatting signature for %s: %s' %
(fullname, err))
@@ -548,8 +566,7 @@ class RstGenerator(object):
if isinstance(member, (types.FunctionType,
types.BuiltinFunctionType)):
memberwhat = 'function'
elif isinstance(member, types.ClassType) or \
isinstance(member, type):
elif isinstance(member, (types.ClassType, type)):
if issubclass(member, base_exception):
memberwhat = 'exception'
else:
@@ -558,14 +575,18 @@ class RstGenerator(object):
# XXX: todo -- attribute docs
continue
else:
if callable(member):
if isinstance(member, (types.ClassType, type)):
memberwhat = 'class'
elif callable(member):
memberwhat = 'method'
elif isdescriptor(member):
memberwhat = 'attribute'
else:
# XXX: todo -- attribute docs
continue
full_membername = fullname + '.' + membername
# give explicitly separated module name, so that members of inner classes
# can be documented
full_membername = mod + '::' + '.'.join(objpath + [membername])
self.generate(memberwhat, full_membername, ['__all__'], None, indent,
check_module=members_check_module)

View File

@@ -51,12 +51,14 @@
<p class="topless"><a href="{{ next.link|e }}" title="{{ _('next chapter') }}">{{ next.title }}</a></p>
{%- endif %}
{%- endblock %}
{%- block sidebarsourcelink %}
{%- if show_source and has_source and sourcename %}
<h3>{{ _('This Page') }}</h3>
<ul class="this-page-menu">
<li><a href="{{ pathto('_sources/' + sourcename, true)|e }}">{{ _('Show Source') }}</a></li>
<li><a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow">{{ _('Show Source') }}</a></li>
</ul>
{%- endif %}
{%- endblock %}
{%- if customsidebar %}
{% include customsidebar %}
{%- endif %}

View File

@@ -82,41 +82,41 @@ def skip_member(app, what, name, obj, skip, options):
def test_resolve_name():
# for modules
assert gen.resolve_name('module', 'test_autodoc') == \
('test_autodoc', 'test_autodoc', [], None, None)
('test_autodoc', [], None, None)
assert gen.resolve_name('module', 'test.test_autodoc') == \
('test.test_autodoc', 'test.test_autodoc', [], None, None)
('test.test_autodoc', [], None, None)
assert gen.resolve_name('module', 'test(arg)') == \
('test', 'test', [], None, None)
('test', [], None, None)
assert 'ignoring signature arguments' in gen.warnings[0]
del gen.warnings[:]
# for functions/classes
assert gen.resolve_name('function', 'util.raises') == \
('util.raises', 'util', ['raises'], None, None)
('util', ['raises'], None, None)
assert gen.resolve_name('function', 'util.raises(exc) -> None') == \
('util.raises', 'util', ['raises'], 'exc', ' -> None')
('util', ['raises'], 'exc', ' -> None')
gen.env.autodoc_current_module = 'util'
assert gen.resolve_name('function', 'raises') == \
('raises', 'util', ['raises'], None, None)
('util', ['raises'], None, None)
gen.env.autodoc_current_module = None
gen.env.currmodule = 'util'
assert gen.resolve_name('function', 'raises') == \
('raises', 'util', ['raises'], None, None)
('util', ['raises'], None, None)
assert gen.resolve_name('class', 'TestApp') == \
('TestApp', 'util', ['TestApp'], None, None)
('util', ['TestApp'], None, None)
# for members
gen.env.currmodule = 'foo'
assert gen.resolve_name('method', 'util.TestApp.cleanup') == \
('util.TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
('util', ['TestApp', 'cleanup'], None, None)
gen.env.currmodule = 'util'
gen.env.currclass = 'Foo'
gen.env.autodoc_current_class = 'TestApp'
assert gen.resolve_name('method', 'cleanup') == \
('cleanup', 'util', ['TestApp', 'cleanup'], None, None)
('util', ['TestApp', 'cleanup'], None, None)
assert gen.resolve_name('method', 'TestApp.cleanup') == \
('TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
('util', ['TestApp', 'cleanup'], None, None)
# and clean up
gen.env.currmodule = None
@@ -321,17 +321,17 @@ def test_generate():
assert_works('exception', 'test_autodoc.CustomEx', [], None)
# test diverse inclusion settings for members
should = [('class', 'Class')]
should = [('class', 'test_autodoc.Class')]
assert_processes(should, 'class', 'Class', [], None)
should.extend([('method', 'Class.meth')])
should.extend([('method', 'test_autodoc.Class.meth')])
assert_processes(should, 'class', 'Class', ['meth'], None)
should.extend([('attribute', 'Class.prop')])
should.extend([('attribute', 'test_autodoc.Class.prop')])
assert_processes(should, 'class', 'Class', ['__all__'], None)
options.undoc_members = True
should.append(('method', 'Class.undocmeth'))
should.append(('method', 'test_autodoc.Class.undocmeth'))
assert_processes(should, 'class', 'Class', ['__all__'], None)
options.inherited_members = True
should.append(('method', 'Class.inheritedmeth'))
should.append(('method', 'test_autodoc.Class.inheritedmeth'))
assert_processes(should, 'class', 'Class', ['__all__'], None)
# test module flags
@@ -363,6 +363,12 @@ def test_generate():
assert_result_contains('.. class:: CustomDict', 'class', 'CustomDict',
['__all__'], None)
# test inner class handling
assert_processes([('class', 'test_autodoc.Outer'),
('class', 'test_autodoc.Outer.Inner'),
('method', 'test_autodoc.Outer.Inner.meth')],
'class', 'Outer', ['__all__'], None)
# --- generate fodder ------------
@@ -404,3 +410,13 @@ def function(foo, *args, **kwds):
Return spam.
"""
pass
class Outer(object):
"""Foo"""
class Inner(object):
"""Foo"""
def meth(self):
"""Foo"""

View File

@@ -19,7 +19,8 @@ from subprocess import Popen, PIPE
from util import *
from etree13 import ElementTree as ET
from sphinx.builder import StandaloneHTMLBuilder, LaTeXBuilder
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.builders.latex import LaTeXBuilder
from sphinx.writers.latex import LaTeXTranslator

View File

@@ -12,7 +12,8 @@
from util import *
from sphinx.environment import BuildEnvironment
from sphinx.builder import StandaloneHTMLBuilder, LaTeXBuilder
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.builders.latex import LaTeXBuilder
app = env = None
warnings = []

View File

@@ -19,7 +19,7 @@ except ImportError:
# functools is new in 2.4
wraps = lambda f: (lambda w: w)
from sphinx import application, builder
from sphinx import application
from path import path