mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge with http://bitbucket.org/birkenfeld/sphinx
This commit is contained in:
13
CHANGES
13
CHANGES
@@ -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)
|
||||
==============================
|
||||
|
||||
1
EXAMPLES
1
EXAMPLES
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
316
doc/ext/tutorial.rst
Normal 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user