summaryrefslogtreecommitdiff
path: root/sphinx/ext
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2011-09-22 13:56:16 +0200
committerGeorg Brandl <georg@python.org>2011-09-22 13:56:16 +0200
commit554b4d7314f6916882cc8156d12639dcb010063e (patch)
tree111248273d29bfb328cd4bccbf3d12fdeaa61f51 /sphinx/ext
parent8759282ac0f0b86373c82576dcb4ee0d6bcb70e1 (diff)
parent7c1dba82ea9a95e54ce4478ef30f83da438f8fff (diff)
downloadsphinx-554b4d7314f6916882cc8156d12639dcb010063e.tar.gz
Merge with 1.0
Diffstat (limited to 'sphinx/ext')
-rw-r--r--sphinx/ext/autodoc.py334
-rw-r--r--sphinx/ext/autosummary/__init__.py119
-rw-r--r--sphinx/ext/autosummary/generate.py21
-rw-r--r--sphinx/ext/coverage.py29
-rw-r--r--sphinx/ext/doctest.py88
-rw-r--r--sphinx/ext/graphviz.py146
-rw-r--r--sphinx/ext/inheritance_diagram.py53
-rw-r--r--sphinx/ext/intersphinx.py26
-rw-r--r--sphinx/ext/mathbase.py50
-rw-r--r--sphinx/ext/mathjax.py68
-rw-r--r--sphinx/ext/oldcmarkup.py1
-rw-r--r--sphinx/ext/pngmath.py29
-rw-r--r--sphinx/ext/todo.py3
-rw-r--r--sphinx/ext/viewcode.py8
14 files changed, 695 insertions, 280 deletions
diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py
index f72e7dfa..f19334a0 100644
--- a/sphinx/ext/autodoc.py
+++ b/sphinx/ext/autodoc.py
@@ -15,7 +15,7 @@ import re
import sys
import inspect
import traceback
-from types import FunctionType, BuiltinFunctionType, MethodType, ClassType
+from types import FunctionType, BuiltinFunctionType, MethodType
from docutils import nodes
from docutils.utils import assemble_option_dict
@@ -27,16 +27,12 @@ from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.application import ExtensionError
from sphinx.util.nodes import nested_parse_with_titles
from sphinx.util.compat import Directive
-from sphinx.util.inspect import isdescriptor, safe_getmembers, safe_getattr
+from sphinx.util.inspect import getargspec, isdescriptor, safe_getmembers, \
+ safe_getattr, safe_repr
+from sphinx.util.pycompat import base_exception, class_types
from sphinx.util.docstrings import prepare_docstring
-try:
- base_exception = BaseException
-except NameError:
- base_exception = Exception
-
-
#: extended signature RE: with explicit module name separated by ::
py_ext_sig_re = re.compile(
r'''^ ([\w.]+::)? # explicit module name
@@ -91,7 +87,8 @@ def members_set_option(arg):
def bool_option(arg):
"""Used to convert flag options to auto directives. (Instead of
- directives.flag(), which returns None.)"""
+ directives.flag(), which returns None).
+ """
return True
@@ -139,8 +136,7 @@ class AutodocReporter(object):
# 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*
+ """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.
@@ -166,9 +162,8 @@ def cut_lines(pre, post=0, what=None):
return process
def between(marker, what=None, keepempty=False, exclude=False):
- """
- Return a listener that either keeps, or if *exclude* is True excludes, lines
- between lines that match the *marker* regular expression. If no line
+ """Return a listener that either keeps, or if *exclude* is True excludes,
+ lines between lines that match the *marker* regular expression. If no line
matches, the resulting docstring would be empty, so no change will be made
unless *keepempty* is true.
@@ -270,8 +265,7 @@ class Documenter(object):
self.directive.result.append(self.indent + line, source, *lineno)
def resolve_name(self, modname, parents, path, base):
- """
- Resolve the module and name of the object to document given by the
+ """Resolve the module and name of the object to document given by the
arguments and the current module/class.
Must return a pair of the module name and a chain of attributes; for
@@ -281,8 +275,7 @@ class Documenter(object):
raise NotImplementedError('must be implemented in subclasses')
def parse_name(self):
- """
- Determine what module to import and what attribute to document.
+ """Determine what module to import and what attribute to document.
Returns True and sets *self.modname*, *self.objpath*, *self.fullname*,
*self.args* and *self.retann* if parsing and resolving was successful.
@@ -319,8 +312,7 @@ class Documenter(object):
return True
def import_object(self):
- """
- Import the object given by *self.modname* and *self.objpath* and sets
+ """Import the object given by *self.modname* and *self.objpath* and set
it as *self.object*.
Returns True if successful, False if an error occurred.
@@ -349,15 +341,15 @@ class Documenter(object):
return False
def get_real_modname(self):
- """
- Get the real module name of an object to document. (It can differ
- from the name of the module through which the object was imported.)
+ """Get the real module name of an object to document.
+
+ It can differ from the name of the module through which the object was
+ imported.
"""
return self.get_attr(self.object, '__module__', None) or self.modname
def check_module(self):
- """
- Check if *self.object* is really defined in the module given by
+ """Check if *self.object* is really defined in the module given by
*self.modname*.
"""
modname = self.get_attr(self.object, '__module__', None)
@@ -366,25 +358,26 @@ class Documenter(object):
return True
def format_args(self):
- """
- Format the argument signature of *self.object*. Should return None if
- the object does not have a signature.
+ """Format the argument signature of *self.object*.
+
+ Should return None if the object does not have a signature.
"""
return None
def format_name(self):
- """
- Format the name of *self.object*. This normally should be something
- that can be parsed by the generated directive, but doesn't need to be
- (Sphinx will display it unparsed then).
+ """Format the name of *self.object*.
+
+ This normally should be something that can be parsed by the generated
+ directive, but doesn't need to be (Sphinx will display it unparsed
+ then).
"""
# normally the name doesn't contain the module (except for module
# directives of course)
return '.'.join(self.objpath) or self.modname
def format_signature(self):
- """
- Format the signature (arguments and return annotation) of the object.
+ """Format the signature (arguments and return annotation) of the object.
+
Let the user process it via the ``autodoc-process-signature`` event.
"""
if self.args is not None:
@@ -426,13 +419,16 @@ class Documenter(object):
# etc. don't support a prepended module name
self.add_line(u' :module: %s' % self.modname, '<autodoc>')
- def get_doc(self, encoding=None):
+ def get_doc(self, encoding=None, ignore=1):
"""Decode and return lines of the docstring(s) for the object."""
docstring = self.get_attr(self.object, '__doc__', None)
- if docstring:
- # make sure we have Unicode docstrings, then sanitize and split
- # into lines
- return [prepare_docstring(force_decode(docstring, encoding))]
+ # make sure we have Unicode docstrings, then sanitize and split
+ # into lines
+ if isinstance(docstring, unicode):
+ return [prepare_docstring(docstring, ignore)]
+ elif docstring:
+ return [prepare_docstring(force_decode(docstring, encoding),
+ ignore)]
return []
def process_doc(self, docstrings):
@@ -451,8 +447,11 @@ class Documenter(object):
# set sourcename and add content from attribute documentation
if self.analyzer:
# prevent encoding errors when the file name is non-ASCII
- filename = unicode(self.analyzer.srcname,
- sys.getfilesystemencoding(), 'replace')
+ if not isinstance(self.analyzer.srcname, unicode):
+ filename = unicode(self.analyzer.srcname,
+ sys.getfilesystemencoding(), 'replace')
+ else:
+ filename = self.analyzer.srcname
sourcename = u'%s:docstring of %s' % (filename, self.fullname)
attr_docs = self.analyzer.find_attr_docs()
@@ -484,8 +483,7 @@ class Documenter(object):
self.add_line(line, src[0], src[1])
def get_object_members(self, want_all):
- """
- Return `(members_check_module, members)` where `members` is a
+ """Return `(members_check_module, members)` where `members` is a
list of `(membername, member)` pairs of the members of *self.object*.
If *want_all* is True, return all members. Else, only return those
@@ -529,11 +527,15 @@ class Documenter(object):
return False, sorted(members)
def filter_members(self, members, want_all):
- """
- Filter the given member list: members are skipped if
+ """Filter the given member list.
- - they are private (except if given explicitly)
- - they are undocumented (except if undoc-members is given)
+ Members are skipped if
+
+ - they are private (except if given explicitly or the private-members
+ option is set)
+ - they are special methods (except if given explicitly or the
+ special-members option is set)
+ - they are undocumented (except if the undoc-members option is set)
The user can override the skipping decision by connecting to the
``autodoc-skip-member`` event.
@@ -553,18 +555,33 @@ class Documenter(object):
# if isattr is True, the member is documented as an attribute
isattr = False
- if want_all and membername.startswith('_'):
+ doc = self.get_attr(member, '__doc__', None)
+ # if the member __doc__ is the same as self's __doc__, it's just
+ # inherited and therefore not the member's doc
+ cls = self.get_attr(member, '__class__', None)
+ if cls:
+ cls_doc = self.get_attr(cls, '__doc__', None)
+ if cls_doc == doc:
+ doc = None
+ has_doc = bool(doc)
+
+ keep = False
+ if want_all and membername.startswith('__') and \
+ membername.endswith('__') and len(membername) > 4:
+ # special __methods__
+ if self.options.special_members and membername != '__doc__':
+ keep = has_doc or self.options.undoc_members
+ elif want_all and membername.startswith('_'):
# ignore members whose name starts with _ by default
- skip = True
+ keep = self.options.private_members and \
+ (has_doc or self.options.undoc_members)
elif (namespace, membername) in attr_docs:
# keep documented attributes
- skip = False
+ keep = True
isattr = True
else:
- # ignore undocumented members if :undoc-members:
- # is not given
- doc = self.get_attr(member, '__doc__', None)
- skip = not self.options.undoc_members and not doc
+ # ignore undocumented members if :undoc-members: is not given
+ keep = has_doc or self.options.undoc_members
# give the user a chance to decide whether this member
# should be skipped
@@ -572,20 +589,20 @@ class Documenter(object):
# let extensions preprocess docstrings
skip_user = self.env.app.emit_firstresult(
'autodoc-skip-member', self.objtype, membername, member,
- skip, self.options)
+ not keep, self.options)
if skip_user is not None:
- skip = skip_user
- if skip:
- continue
+ keep = not skip_user
- ret.append((membername, member, isattr))
+ if keep:
+ ret.append((membername, member, isattr))
return ret
def document_members(self, all_members=False):
- """
- Generate reST for member documentation. If *all_members* is True,
- do all members, else those given by *self.options.members*.
+ """Generate reST for member documentation.
+
+ If *all_members* is True, do all members, else those given by
+ *self.options.members*.
"""
# set current namespace for finding members
self.env.temp_data['autodoc:module'] = self.modname
@@ -643,8 +660,8 @@ class Documenter(object):
def generate(self, more_content=None, real_modname=None,
check_module=False, all_members=False):
- """
- Generate reST for the object given by *self.name*, and possibly members.
+ """Generate reST for the object given by *self.name*, and possibly for
+ its members.
If *more_content* is given, include that content. If *real_modname* is
given, use that module name to find attribute docs. If *check_module* is
@@ -727,6 +744,7 @@ class ModuleDocumenter(Documenter):
'show-inheritance': bool_option, 'synopsis': identity,
'platform': identity, 'deprecated': bool_option,
'member-order': identity, 'exclude-members': members_set_option,
+ 'private-members': bool_option, 'special-members': bool_option,
}
@classmethod
@@ -833,7 +851,53 @@ class ClassLevelDocumenter(Documenter):
return modname, parents + [base]
-class FunctionDocumenter(ModuleLevelDocumenter):
+class DocstringSignatureMixin(object):
+ """
+ Mixin for FunctionDocumenter and MethodDocumenter to provide the
+ feature of reading the signature from the docstring.
+ """
+
+ def _find_signature(self, encoding=None):
+ docstrings = Documenter.get_doc(self, encoding, 2)
+ if len(docstrings) != 1:
+ return
+ doclines = docstrings[0]
+ setattr(self, '__new_doclines', doclines)
+ if not doclines:
+ return
+ # match first line of docstring against signature RE
+ match = py_ext_sig_re.match(doclines[0])
+ if not match:
+ return
+ exmod, path, base, args, retann = match.groups()
+ # the base name must match ours
+ if not self.objpath or base != self.objpath[-1]:
+ return
+ # ok, now jump over remaining empty lines and set the remaining
+ # lines as the new doclines
+ i = 1
+ while i < len(doclines) and not doclines[i].strip():
+ i += 1
+ setattr(self, '__new_doclines', doclines[i:])
+ return args, retann
+
+ def get_doc(self, encoding=None, ignore=1):
+ lines = getattr(self, '__new_doclines', None)
+ if lines is not None:
+ return [lines]
+ return Documenter.get_doc(self, encoding, ignore)
+
+ def format_signature(self):
+ if self.args is None and self.env.config.autodoc_docstring_signature:
+ # only act if a signature is not explicitly given already, and if
+ # the feature is enabled
+ result = self._find_signature()
+ if result is not None:
+ self.args, self.retann = result
+ return Documenter.format_signature(self)
+
+
+class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
"""
Specialized Documenter subclass for functions.
"""
@@ -847,18 +911,18 @@ class FunctionDocumenter(ModuleLevelDocumenter):
def format_args(self):
if inspect.isbuiltin(self.object) or \
inspect.ismethoddescriptor(self.object):
- # can never get arguments of a C function or method
+ # cannot introspect arguments of a C function or method
return None
try:
- argspec = inspect.getargspec(self.object)
+ argspec = getargspec(self.object)
except TypeError:
# if a class should be documented as function (yay duck
# typing) we try to use the constructor signature as function
# signature without the first argument.
try:
- argspec = inspect.getargspec(self.object.__new__)
+ argspec = getargspec(self.object.__new__)
except TypeError:
- argspec = inspect.getargspec(self.object.__init__)
+ argspec = getargspec(self.object.__init__)
if argspec[0]:
del argspec[0][0]
args = inspect.formatargspec(*argspec)
@@ -881,11 +945,12 @@ class ClassDocumenter(ModuleLevelDocumenter):
'noindex': bool_option, 'inherited-members': bool_option,
'show-inheritance': bool_option, 'member-order': identity,
'exclude-members': members_set_option,
+ 'private-members': bool_option, 'special-members': bool_option,
}
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
- return isinstance(member, (type, ClassType))
+ return isinstance(member, class_types)
def import_object(self):
ret = ModuleLevelDocumenter.import_object(self)
@@ -907,7 +972,7 @@ class ClassDocumenter(ModuleLevelDocumenter):
(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
return None
try:
- argspec = inspect.getargspec(initmeth)
+ argspec = getargspec(initmeth)
except TypeError:
# still not possible: happens e.g. for old-style classes
# with __init__ in C
@@ -937,7 +1002,7 @@ class ClassDocumenter(ModuleLevelDocumenter):
self.add_line(_(u' Bases: %s') % ', '.join(bases),
'<autodoc>')
- def get_doc(self, encoding=None):
+ def get_doc(self, encoding=None, ignore=1):
content = self.env.config.autoclass_content
docstrings = []
@@ -958,9 +1023,12 @@ class ClassDocumenter(ModuleLevelDocumenter):
docstrings = [initdocstring]
else:
docstrings.append(initdocstring)
-
- return [prepare_docstring(force_decode(docstring, encoding))
- for docstring in docstrings]
+ doc = []
+ for docstring in docstrings:
+ if not isinstance(docstring, unicode):
+ docstring = force_decode(docstring, encoding)
+ doc.append(prepare_docstring(docstring))
+ return doc
def add_content(self, more_content, no_docstring=False):
if self.doc_as_attr:
@@ -991,7 +1059,7 @@ class ExceptionDocumenter(ClassDocumenter):
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
- return isinstance(member, (type, ClassType)) and \
+ return isinstance(member, class_types) and \
issubclass(member, base_exception)
@@ -1001,16 +1069,26 @@ class DataDocumenter(ModuleLevelDocumenter):
"""
objtype = 'data'
member_order = 40
+ priority = -10
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return isinstance(parent, ModuleDocumenter) and isattr
+ def add_directive_header(self, sig):
+ ModuleLevelDocumenter.add_directive_header(self, sig)
+ try:
+ objrepr = safe_repr(self.object)
+ except ValueError:
+ pass
+ else:
+ self.add_line(u' :annotation: = ' + objrepr, '<autodoc>')
+
def document_members(self, all_members=False):
pass
-class MethodDocumenter(ClassLevelDocumenter):
+class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter):
"""
Specialized Documenter subclass for methods (normal, static and class).
"""
@@ -1023,31 +1101,45 @@ class MethodDocumenter(ClassLevelDocumenter):
return inspect.isroutine(member) and \
not isinstance(parent, ModuleDocumenter)
- def import_object(self):
- ret = ClassLevelDocumenter.import_object(self)
- if isinstance(self.object, classmethod) or \
- (isinstance(self.object, MethodType) and
- self.object.im_self is not None):
- self.directivetype = 'classmethod'
- # document class and static members before ordinary ones
- self.member_order = self.member_order - 1
- elif isinstance(self.object, FunctionType) or \
- (isinstance(self.object, BuiltinFunctionType) and
- hasattr(self.object, '__self__') and
- self.object.__self__ is not None):
- self.directivetype = 'staticmethod'
- # document class and static members before ordinary ones
- self.member_order = self.member_order - 1
- else:
- self.directivetype = 'method'
- return ret
+ if sys.version_info >= (3, 0):
+ def import_object(self):
+ ret = ClassLevelDocumenter.import_object(self)
+ obj_from_parent = self.parent.__dict__.get(self.object_name)
+ if isinstance(obj_from_parent, classmethod):
+ self.directivetype = 'classmethod'
+ self.member_order = self.member_order - 1
+ elif isinstance(obj_from_parent, staticmethod):
+ self.directivetype = 'staticmethod'
+ self.member_order = self.member_order - 1
+ else:
+ self.directivetype = 'method'
+ return ret
+ else:
+ def import_object(self):
+ ret = ClassLevelDocumenter.import_object(self)
+ if isinstance(self.object, classmethod) or \
+ (isinstance(self.object, MethodType) and
+ self.object.im_self is not None):
+ self.directivetype = 'classmethod'
+ # document class and static members before ordinary ones
+ self.member_order = self.member_order - 1
+ elif isinstance(self.object, FunctionType) or \
+ (isinstance(self.object, BuiltinFunctionType) and
+ hasattr(self.object, '__self__') and
+ self.object.__self__ is not None):
+ self.directivetype = 'staticmethod'
+ # document class and static members before ordinary ones
+ self.member_order = self.member_order - 1
+ else:
+ self.directivetype = 'method'
+ return ret
def format_args(self):
if inspect.isbuiltin(self.object) or \
inspect.ismethoddescriptor(self.object):
# can never get arguments of a C function or method
return None
- argspec = inspect.getargspec(self.object)
+ argspec = getargspec(self.object)
if argspec[0] and argspec[0][0] in ('cls', 'self'):
del argspec[0][0]
return inspect.formatargspec(*argspec)
@@ -1074,16 +1166,44 @@ class AttributeDocumenter(ClassLevelDocumenter):
isdatadesc = isdescriptor(member) and not \
isinstance(member, cls.method_types) and not \
type(member).__name__ == "method_descriptor"
- return isdatadesc or \
- (isattr and not isinstance(parent, ModuleDocumenter))
+ return isdatadesc or (not isinstance(parent, ModuleDocumenter)
+ and not inspect.isroutine(member)
+ and not isinstance(member, class_types))
def document_members(self, all_members=False):
pass
+ def import_object(self):
+ ret = ClassLevelDocumenter.import_object(self)
+ if isdescriptor(self.object) and \
+ not isinstance(self.object, self.method_types):
+ self._datadescriptor = True
+ else:
+ # if it's not a data descriptor
+ self._datadescriptor = False
+ return ret
+
def get_real_modname(self):
return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname
+ def add_directive_header(self, sig):
+ ClassLevelDocumenter.add_directive_header(self, sig)
+ if not self._datadescriptor:
+ try:
+ objrepr = safe_repr(self.object)
+ except ValueError:
+ pass
+ else:
+ self.add_line(u' :annotation: = ' + objrepr, '<autodoc>')
+
+ def add_content(self, more_content, no_docstring=False):
+ if not self._datadescriptor:
+ # if it's not a data descriptor, its docstring is very probably the
+ # wrong thing to display
+ no_docstring = True
+ ClassLevelDocumenter.add_content(self, more_content, no_docstring)
+
class InstanceAttributeDocumenter(AttributeDocumenter):
"""
@@ -1106,6 +1226,7 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
"""Never import anything."""
# disguise as an attribute
self.objtype = 'attribute'
+ self._datadescriptor = False
return True
def add_content(self, more_content, no_docstring=False):
@@ -1135,8 +1256,10 @@ class AutoDirective(Directive):
_special_attrgetters = {}
# flags that can be given in autodoc_default_flags
- _default_flags = set(['members', 'undoc-members', 'inherited-members',
- 'show-inheritance'])
+ _default_flags = set([
+ 'members', 'undoc-members', 'inherited-members', 'show-inheritance',
+ 'private-members', 'special-members',
+ ])
# standard docutils directive settings
has_content = True
@@ -1226,6 +1349,17 @@ def setup(app):
app.add_config_value('autoclass_content', 'class', True)
app.add_config_value('autodoc_member_order', 'alphabetic', True)
app.add_config_value('autodoc_default_flags', [], True)
+ app.add_config_value('autodoc_docstring_signature', True, True)
app.add_event('autodoc-process-docstring')
app.add_event('autodoc-process-signature')
app.add_event('autodoc-skip-member')
+
+
+class testcls:
+ """test doc string"""
+
+ def __getattr__(self, x):
+ return x
+
+ def __setattr__(self, x, y):
+ """Attr setter."""
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index a194fba8..0f5c2640 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -73,8 +73,7 @@ class autosummary_toc(nodes.comment):
pass
def process_autosummary_toc(app, doctree):
- """
- Insert items described in autosummary:: to the TOC tree, but do
+ """Insert items described in autosummary:: to the TOC tree, but do
not generate the toctree:: list.
"""
env = app.builder.env
@@ -126,22 +125,39 @@ def autosummary_table_visit_html(self, node):
# -- autodoc integration -------------------------------------------------------
-try:
- ismemberdescriptor = inspect.ismemberdescriptor
- isgetsetdescriptor = inspect.isgetsetdescriptor
-except AttributeError:
- def ismemberdescriptor(obj):
- return False
- isgetsetdescriptor = ismemberdescriptor
+class FakeDirective:
+ env = {}
+ genopt = {}
def get_documenter(obj, parent):
+ """Get an autodoc.Documenter class suitable for documenting the given
+ object.
+
+ *obj* is the Python object to be documented, and *parent* is an
+ another Python object (e.g. a module or a class) to which *obj*
+ belongs to.
"""
- Get an autodoc.Documenter class suitable for documenting the given object
- """
- from sphinx.ext.autodoc import AutoDirective, DataDocumenter
+ from sphinx.ext.autodoc import AutoDirective, DataDocumenter, \
+ ModuleDocumenter
+
+ if inspect.ismodule(obj):
+ # ModuleDocumenter.can_document_member always returns False
+ return ModuleDocumenter
+ # Construct a fake documenter for *parent*
+ if parent is not None:
+ parent_doc_cls = get_documenter(parent, None)
+ else:
+ parent_doc_cls = ModuleDocumenter
+
+ if hasattr(parent, '__name__'):
+ parent_doc = parent_doc_cls(FakeDirective(), parent.__name__)
+ else:
+ parent_doc = parent_doc_cls(FakeDirective(), "")
+
+ # Get the corrent documenter class for *obj*
classes = [cls for cls in AutoDirective._registry.values()
- if cls.can_document_member(obj, '', False, parent)]
+ if cls.can_document_member(obj, '', False, parent_doc)]
if classes:
classes.sort(key=lambda cls: cls.priority)
return classes[-1]
@@ -155,7 +171,7 @@ class Autosummary(Directive):
"""
Pretty table containing short signatures and summaries of functions etc.
- autosummary also generates a (hidden) toctree:: node.
+ autosummary can also optionally generate a hidden toctree:: node.
"""
required_arguments = 0
@@ -210,16 +226,12 @@ class Autosummary(Directive):
return self.warnings + nodes
def get_items(self, names):
- """
- Try to import the given names, and return a list of
+ """Try to import the given names, and return a list of
``[(name, signature, summary_string, real_name), ...]``.
"""
env = self.state.document.settings.env
- prefixes = ['']
- currmodule = env.temp_data.get('py:module')
- if currmodule:
- prefixes.insert(0, currmodule)
+ prefixes = get_import_prefixes_from_env(env)
items = []
@@ -279,8 +291,7 @@ class Autosummary(Directive):
return items
def get_table(self, items):
- """
- Generate a proper list of table nodes for autosummary:: directive.
+ """Generate a proper list of table nodes for autosummary:: directive.
*items* is a list produced by :meth:`get_items`.
"""
@@ -325,13 +336,29 @@ class Autosummary(Directive):
def mangle_signature(sig, max_chars=30):
"""Reformat a function signature to a more compact form."""
- sig = re.sub(r"^\((.*)\)$", r"\1", sig) + ", "
- r = re.compile(r"(?P<name>[a-zA-Z0-9_*]+)(?P<default>=.*?)?, ")
- items = r.findall(sig)
+ s = re.sub(r"^\((.*)\)$", r"\1", sig).strip()
+
+ # Strip strings (which can contain things that confuse the code below)
+ s = re.sub(r"\\\\", "", s)
+ s = re.sub(r"\\'", "", s)
+ s = re.sub(r"'[^']*'", "", s)
+
+ # Parse the signature to arguments + options
+ args = []
+ opts = []
+
+ opt_re = re.compile(r"^(.*, |)([a-zA-Z0-9_*]+)=")
+ while s:
+ m = opt_re.search(s)
+ if not m:
+ # The rest are arguments
+ args = s.split(', ')
+ break
- args = [name for name, default in items if not default]
- opts = [name for name, default in items if default]
+ opts.insert(0, m.group(2))
+ s = m.group(1)[:-2]
+ # Produce a more compact signature
sig = limited_join(", ", args, max_chars=max_chars-2)
if opts:
if not sig:
@@ -343,8 +370,7 @@ def mangle_signature(sig, max_chars=30):
return u"(%s)" % sig
def limited_join(sep, items, max_chars=30, overflow_marker="..."):
- """
- Join a number of strings to one, limiting the length to *max_chars*.
+ """Join a number of strings to one, limiting the length to *max_chars*.
If the string overflows this limit, replace the last fitting item by
*overflow_marker*.
@@ -368,9 +394,28 @@ def limited_join(sep, items, max_chars=30, overflow_marker="..."):
# -- Importing items -----------------------------------------------------------
-def import_by_name(name, prefixes=[None]):
+def get_import_prefixes_from_env(env):
+ """
+ Obtain current Python import prefixes (for `import_by_name`)
+ from ``document.env``
"""
- Import a Python object that has the given *name*, under one of the
+ prefixes = [None]
+
+ currmodule = env.temp_data.get('py:module')
+ if currmodule:
+ prefixes.insert(0, currmodule)
+
+ currclass = env.temp_data.get('py:class')
+ if currclass:
+ if currmodule:
+ prefixes.insert(0, currmodule + "." + currclass)
+ else:
+ prefixes.insert(0, currclass)
+
+ return prefixes
+
+def import_by_name(name, prefixes=[None]):
+ """Import a Python object that has the given *name*, under one of the
*prefixes*. The first name that succeeds is used.
"""
tried = []
@@ -431,8 +476,7 @@ def _import_by_name(name):
def autolink_role(typ, rawtext, etext, lineno, inliner,
options={}, content=[]):
- """
- Smart linking role.
+ """Smart linking role.
Expands to ':obj:`text`' if `text` is an object that can be imported;
otherwise expands to '*text*'.
@@ -442,8 +486,7 @@ def autolink_role(typ, rawtext, etext, lineno, inliner,
'obj', rawtext, etext, lineno, inliner, options, content)
pnode = r[0][0]
- prefixes = [None]
- #prefixes.insert(0, inliner.document.settings.env.currmodule)
+ prefixes = get_import_prefixes_from_env(env)
try:
name, obj, parent = import_by_name(pnode['reftarget'], prefixes)
except ImportError:
@@ -483,12 +526,14 @@ def setup(app):
html=(autosummary_toc_visit_html, autosummary_noop),
latex=(autosummary_noop, autosummary_noop),
text=(autosummary_noop, autosummary_noop),
- man=(autosummary_noop, autosummary_noop))
+ man=(autosummary_noop, autosummary_noop),
+ texinfo=(autosummary_noop, autosummary_noop))
app.add_node(autosummary_table,
html=(autosummary_table_visit_html, autosummary_noop),
latex=(autosummary_noop, autosummary_noop),
text=(autosummary_noop, autosummary_noop),
- man=(autosummary_noop, autosummary_noop))
+ man=(autosummary_noop, autosummary_noop),
+ texinfo=(autosummary_noop, autosummary_noop))
app.add_directive('autosummary', Autosummary)
app.add_role('autolink', autolink_role)
app.connect('doctree-read', process_autosummary_toc)
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index b5ff3f4c..089d181f 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -12,11 +12,12 @@
Example Makefile rule::
generate:
- sphinx-autogen source/*.rst source/generated
+ sphinx-autogen -o source/generated source/*.rst
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
+
import os
import re
import sys
@@ -193,8 +194,8 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
# -- Finding documented entries in files ---------------------------------------
def find_autosummary_in_files(filenames):
- """
- Find out what items are documented in source/*.rst.
+ """Find out what items are documented in source/*.rst.
+
See `find_autosummary_in_lines`.
"""
documented = []
@@ -206,8 +207,8 @@ def find_autosummary_in_files(filenames):
return documented
def find_autosummary_in_docstring(name, module=None, filename=None):
- """
- Find out what items are documented in the given object's docstring.
+ """Find out what items are documented in the given object's docstring.
+
See `find_autosummary_in_lines`.
"""
try:
@@ -221,8 +222,8 @@ def find_autosummary_in_docstring(name, module=None, filename=None):
return []
def find_autosummary_in_lines(lines, module=None, filename=None):
- """
- Find out what items appear in autosummary:: directives in the given lines.
+ """Find out what items appear in autosummary:: directives in the
+ given lines.
Returns a list of (name, toctree, template) where *name* is a name
of an object and *toctree* the :toctree: path of the corresponding
@@ -231,7 +232,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
*template* ``None`` if the directive does not have the
corresponding options set.
"""
- autosummary_re = re.compile(r'^\s*\.\.\s+autosummary::\s*')
+ autosummary_re = re.compile(r'^(\s*)\.\.\s+autosummary::\s*')
automodule_re = re.compile(
r'^\s*\.\.\s+automodule::\s*([A-Za-z0-9_.]+)\s*$')
module_re = re.compile(
@@ -246,6 +247,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
template = None
current_module = module
in_autosummary = False
+ base_indent = ""
for line in lines:
if in_autosummary:
@@ -276,7 +278,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
documented.append((name, toctree, template))
continue
- if not line.strip():
+ if not line.strip() or line.startswith(base_indent + " "):
continue
in_autosummary = False
@@ -284,6 +286,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
m = autosummary_re.match(line)
if m:
in_autosummary = True
+ base_indent = m.group(1)
toctree = None
template = None
continue
diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py
index cfd4265e..af1b4026 100644
--- a/sphinx/ext/coverage.py
+++ b/sphinx/ext/coverage.py
@@ -105,7 +105,8 @@ class CoverageBuilder(Builder):
output_file = path.join(self.outdir, 'c.txt')
op = open(output_file, 'w')
try:
- write_header(op, 'Undocumented C API elements', '=')
+ if self.config.coverage_write_headline:
+ write_header(op, 'Undocumented C API elements', '=')
op.write('\n')
for filename, undoc in self.c_undoc.iteritems():
@@ -120,6 +121,8 @@ class CoverageBuilder(Builder):
objects = self.env.domaindata['py']['objects']
modules = self.env.domaindata['py']['modules']
+ skip_undoc = self.config.coverage_skip_undoc_in_source
+
for mod_name in modules:
ignore = False
for exp in self.mod_ignorexps:
@@ -160,6 +163,8 @@ class CoverageBuilder(Builder):
if exp.match(name):
break
else:
+ if skip_undoc and not obj.__doc__:
+ continue
funcs.append(name)
elif inspect.isclass(obj):
for exp in self.cls_ignorexps:
@@ -167,17 +172,27 @@ class CoverageBuilder(Builder):
break
else:
if full_name not in objects:
+ if skip_undoc and not obj.__doc__:
+ continue
# not documented at all
classes[name] = []
continue
attrs = []
- for attr_name, attr in inspect.getmembers(
- obj, inspect.ismethod):
+ for attr_name in dir(obj):
+ if attr_name not in obj.__dict__:
+ continue
+ attr = getattr(obj, attr_name)
+ if not (inspect.ismethod(attr) or
+ inspect.isfunction(attr)):
+ continue
if attr_name[0] == '_':
# starts with an underscore, ignore it
continue
+ if skip_undoc and not attr.__doc__:
+ # skip methods without docstring if wished
+ continue
full_attr_name = '%s.%s' % (full_name, attr_name)
if full_attr_name not in objects:
@@ -194,8 +209,8 @@ class CoverageBuilder(Builder):
op = open(output_file, 'w')
failed = []
try:
- write_header(op, 'Undocumented Python objects', '=')
-
+ if self.config.coverage_write_headline:
+ write_header(op, 'Undocumented Python objects', '=')
keys = self.py_undoc.keys()
keys.sort()
for name in keys:
@@ -217,7 +232,7 @@ class CoverageBuilder(Builder):
if not methods:
op.write(' * %s\n' % name)
else:
- op.write(' * %s -- missing methods:\n' % name)
+ op.write(' * %s -- missing methods:\n\n' % name)
op.writelines(' - %s\n' % x for x in methods)
op.write('\n')
@@ -245,3 +260,5 @@ 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)
+ app.add_config_value('coverage_write_headline', True, False)
+ app.add_config_value('coverage_skip_undoc_in_source', False, False)
diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py
index dcee09f5..2952388f 100644
--- a/sphinx/ext/doctest.py
+++ b/sphinx/ext/doctest.py
@@ -56,7 +56,7 @@ class TestDirective(Directive):
test = code
code = doctestopt_re.sub('', code)
nodetype = nodes.literal_block
- if self.name == 'testsetup' or 'hide' in self.options:
+ if self.name in ('testsetup', 'testcleanup') or 'hide' in self.options:
nodetype = nodes.comment
if self.arguments:
groups = [x.strip() for x in self.arguments[0].split(',')]
@@ -86,6 +86,9 @@ class TestDirective(Directive):
class TestsetupDirective(TestDirective):
option_spec = {}
+class TestcleanupDirective(TestDirective):
+ option_spec = {}
+
class DoctestDirective(TestDirective):
option_spec = {
'hide': directives.flag,
@@ -113,6 +116,7 @@ class TestGroup(object):
self.name = name
self.setup = []
self.tests = []
+ self.cleanup = []
def add_code(self, code, prepend=False):
if code.type == 'testsetup':
@@ -120,6 +124,8 @@ class TestGroup(object):
self.setup.insert(0, code)
else:
self.setup.append(code)
+ elif code.type == 'testcleanup':
+ self.cleanup.append(code)
elif code.type == 'doctest':
self.tests.append([code])
elif code.type == 'testcode':
@@ -131,8 +137,8 @@ class TestGroup(object):
raise RuntimeError('invalid TestCode type')
def __repr__(self):
- return 'TestGroup(name=%r, setup=%r, tests=%r)' % (
- self.name, self.setup, self.tests)
+ return 'TestGroup(name=%r, setup=%r, cleanup=%r, tests=%r)' % (
+ self.name, self.setup, self.cleanup, self.tests)
class TestCode(object):
@@ -149,14 +155,14 @@ class TestCode(object):
class SphinxDocTestRunner(doctest.DocTestRunner):
def summarize(self, out, verbose=None):
- io = StringIO.StringIO()
+ string_io = StringIO.StringIO()
old_stdout = sys.stdout
- sys.stdout = io
+ sys.stdout = string_io
try:
res = doctest.DocTestRunner.summarize(self, verbose)
finally:
sys.stdout = old_stdout
- out(io.getvalue())
+ out(string_io.getvalue())
return res
def _DocTestRunner__patched_linecache_getlines(self, filename,
@@ -204,6 +210,8 @@ class DocTestBuilder(Builder):
self.total_tries = 0
self.setup_failures = 0
self.setup_tries = 0
+ self.cleanup_failures = 0
+ self.cleanup_tries = 0
date = time.strftime('%Y-%m-%d %H:%M:%S')
@@ -240,12 +248,14 @@ Doctest summary
%5d test%s
%5d failure%s in tests
%5d failure%s in setup code
+%5d failure%s in cleanup code
''' % (self.total_tries, s(self.total_tries),
self.total_failures, s(self.total_failures),
- self.setup_failures, s(self.setup_failures)))
+ self.setup_failures, s(self.setup_failures),
+ self.cleanup_failures, s(self.cleanup_failures)))
self.outfile.close()
- if self.total_failures or self.setup_failures:
+ if self.total_failures or self.setup_failures or self.cleanup_failures:
self.app.statuscode = 1
def write(self, build_docnames, updated_docnames, method='update'):
@@ -265,6 +275,12 @@ Doctest summary
optionflags=self.opt)
self.test_runner = SphinxDocTestRunner(verbose=False,
optionflags=self.opt)
+ self.cleanup_runner = SphinxDocTestRunner(verbose=False,
+ optionflags=self.opt)
+
+ self.test_runner._fakeout = self.setup_runner._fakeout
+ self.cleanup_runner._fakeout = self.setup_runner._fakeout
+
if self.config.doctest_test_doctest_blocks:
def condition(node):
return (isinstance(node, (nodes.literal_block, nodes.comment))
@@ -298,6 +314,11 @@ Doctest summary
'testsetup', lineno=0)
for group in groups.itervalues():
group.add_code(code, prepend=True)
+ if self.config.doctest_global_cleanup:
+ code = TestCode(self.config.doctest_global_cleanup,
+ 'testcleanup', lineno=0)
+ for group in groups.itervalues():
+ group.add_code(code)
if not groups:
return
@@ -313,29 +334,43 @@ Doctest summary
res_f, res_t = self.test_runner.summarize(self._out, verbose=True)
self.total_failures += res_f
self.total_tries += res_t
+ if self.cleanup_runner.tries:
+ res_f, res_t = self.cleanup_runner.summarize(self._out,
+ verbose=True)
+ self.cleanup_failures += res_f
+ self.cleanup_tries += res_t
def compile(self, code, name, type, flags, dont_inherit):
return compile(code, name, self.type, flags, dont_inherit)
def test_group(self, group, filename):
ns = {}
- setup_examples = []
- for setup in group.setup:
- setup_examples.append(doctest.Example(setup.code, '',
- lineno=setup.lineno))
- if setup_examples:
- # simulate a doctest with the setup code
- setup_doctest = doctest.DocTest(setup_examples, {},
- '%s (setup code)' % group.name,
- filename, 0, None)
- setup_doctest.globs = ns
- old_f = self.setup_runner.failures
+
+ def run_setup_cleanup(runner, testcodes, what):
+ examples = []
+ for testcode in testcodes:
+ examples.append(doctest.Example(testcode.code, '',
+ lineno=testcode.lineno))
+ if not examples:
+ return True
+ # simulate a doctest with the code
+ sim_doctest = doctest.DocTest(examples, {},
+ '%s (%s code)' % (group.name, what),
+ filename, 0, None)
+ sim_doctest.globs = ns
+ old_f = runner.failures
self.type = 'exec' # the snippet may contain multiple statements
- self.setup_runner.run(setup_doctest, out=self._warn_out,
- clear_globs=False)
- if self.setup_runner.failures > old_f:
- # don't run the group
- return
+ runner.run(sim_doctest, out=self._warn_out, clear_globs=False)
+ if runner.failures > old_f:
+ return False
+ return True
+
+ # run the setup code
+ if not run_setup_cleanup(self.setup_runner, group.setup, 'setup'):
+ # if setup failed, don't run the group
+ return
+
+ # run the tests
for code in group.tests:
if len(code) == 1:
# ordinary doctests (code/output interleaved)
@@ -373,9 +408,13 @@ Doctest summary
# also don't clear the globs namespace after running the doctest
self.test_runner.run(test, out=self._warn_out, clear_globs=False)
+ # run the cleanup
+ run_setup_cleanup(self.cleanup_runner, group.cleanup, 'cleanup')
+
def setup(app):
app.add_directive('testsetup', TestsetupDirective)
+ app.add_directive('testcleanup', TestcleanupDirective)
app.add_directive('doctest', DoctestDirective)
app.add_directive('testcode', TestcodeDirective)
app.add_directive('testoutput', TestoutputDirective)
@@ -384,3 +423,4 @@ def setup(app):
app.add_config_value('doctest_path', [], False)
app.add_config_value('doctest_test_doctest_blocks', 'default', False)
app.add_config_value('doctest_global_setup', '', False)
+ app.add_config_value('doctest_global_cleanup', '', False)
diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py
index bf62c063..ee935945 100644
--- a/sphinx/ext/graphviz.py
+++ b/sphinx/ext/graphviz.py
@@ -11,6 +11,7 @@
"""
import re
+import codecs
import posixpath
from os import path
from math import ceil
@@ -25,12 +26,11 @@ from docutils.parsers.rst import directives
from sphinx.errors import SphinxError
from sphinx.locale import _
-from sphinx.util.osutil import ensuredir, ENOENT, EPIPE
+from sphinx.util.osutil import ensuredir, ENOENT, EPIPE, EINVAL
from sphinx.util.compat import Directive
mapname_re = re.compile(r'<map id="(.*?)"')
-svg_dim_re = re.compile(r'<svg\swidth="(\d+)pt"\sheight="(\d+)pt"', re.M)
class GraphvizError(SphinxError):
@@ -47,23 +47,48 @@ class Graphviz(Directive):
"""
has_content = True
required_arguments = 0
- optional_arguments = 0
+ optional_arguments = 1
final_argument_whitespace = False
option_spec = {
'alt': directives.unchanged,
+ 'inline': directives.flag,
+ 'caption': directives.unchanged,
}
def run(self):
- dotcode = '\n'.join(self.content)
- if not dotcode.strip():
- return [self.state_machine.reporter.warning(
- 'Ignoring "graphviz" directive without content.',
- line=self.lineno)]
+ if self.arguments:
+ document = self.state.document
+ if self.content:
+ return [document.reporter.warning(
+ 'Graphviz directive cannot have both content and '
+ 'a filename argument', line=self.lineno)]
+ env = self.state.document.settings.env
+ rel_filename, filename = env.relfn2path(self.arguments[0])
+ env.note_dependency(rel_filename)
+ try:
+ fp = codecs.open(filename, 'r', 'utf-8')
+ try:
+ dotcode = fp.read()
+ finally:
+ fp.close()
+ except (IOError, OSError):
+ return [document.reporter.warning(
+ 'External Graphviz file %r not found or reading '
+ 'it failed' % filename, line=self.lineno)]
+ else:
+ dotcode = '\n'.join(self.content)
+ if not dotcode.strip():
+ return [self.state_machine.reporter.warning(
+ 'Ignoring "graphviz" directive without content.',
+ line=self.lineno)]
node = graphviz()
node['code'] = dotcode
node['options'] = []
if 'alt' in self.options:
node['alt'] = self.options['alt']
+ if 'caption' in self.options:
+ node['caption'] = self.options['caption']
+ node['inline'] = 'inline' in self.options
return [node]
@@ -77,6 +102,8 @@ class GraphvizSimple(Directive):
final_argument_whitespace = False
option_spec = {
'alt': directives.unchanged,
+ 'inline': directives.flag,
+ 'caption': directives.unchanged,
}
def run(self):
@@ -86,14 +113,16 @@ class GraphvizSimple(Directive):
node['options'] = []
if 'alt' in self.options:
node['alt'] = self.options['alt']
+ if 'caption' in self.options:
+ node['caption'] = self.options['caption']
+ node['inline'] = 'inline' in self.options
return [node]
def render_dot(self, code, options, format, prefix='graphviz'):
- """
- Render graphviz code into a PNG or PDF output file.
- """
+ """Render graphviz code into a PNG or PDF output file."""
hashkey = code.encode('utf-8') + str(options) + \
+ str(self.builder.config.graphviz_dot) + \
str(self.builder.config.graphviz_dot_args)
fname = '%s-%s.%s' % (prefix, sha(hashkey).hexdigest(), format)
if hasattr(self.builder, 'imgpath'):
@@ -134,6 +163,7 @@ def render_dot(self, code, options, format, prefix='graphviz'):
self.builder.config.graphviz_dot)
self.builder._graphviz_warned_dot = True
return None, None
+ wentWrong = False
try:
# Graphviz may close standard input when an error occurs,
# resulting in a broken pipe on communicate()
@@ -141,6 +171,12 @@ def render_dot(self, code, options, format, prefix='graphviz'):
except (OSError, IOError), err:
if err.errno != EPIPE:
raise
+ wentWrong = True
+ except IOError, err:
+ if err.errno != EINVAL:
+ raise
+ wentWrong = True
+ if wentWrong:
# in this case, read the standard output and standard error streams
# directly, to get the error message(s)
stdout, stderr = p.stdout.read(), p.stderr.read()
@@ -151,37 +187,6 @@ def render_dot(self, code, options, format, prefix='graphviz'):
return relfn, outfn
-def get_svg_tag(svgref, svgfile, imgcls=None):
- # Webkit can't figure out svg dimensions when using object tag
- # so we need to get it from the svg file
- fp = open(svgfile, 'r')
- try:
- for line in fp:
- match = svg_dim_re.match(line)
- if match:
- dimensions = match.groups()
- break
- else:
- dimensions = None
- finally:
- fp.close()
-
- # We need this hack to make WebKit show our object tag properly
- def pt2px(x):
- return int(ceil((96.0/72.0) * float(x)))
-
- if dimensions:
- style = ' width="%s" height="%s"' % tuple(map(pt2px, dimensions))
- else:
- style = ''
-
- # The object tag works fine on Firefox and WebKit
- # Besides it's a hack, this strategy does not mess with templates.
- imgcss = imgcls and ' class="%s"' % imgcls or ''
- return '<object type="image/svg+xml" data="%s"%s%s></object>\n' % \
- (svgref, imgcss, style)
-
-
def render_dot_html(self, node, code, options, prefix='graphviz',
imgcls=None, alt=None):
format = self.builder.config.graphviz_output_format
@@ -194,14 +199,21 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
self.builder.warn('dot code %r: ' % code + str(exc))
raise nodes.SkipNode
- self.body.append(self.starttag(node, 'p', CLASS='graphviz'))
+ inline = node.get('inline', False)
+ if inline:
+ wrapper = 'span'
+ else:
+ wrapper = 'p'
+
+ self.body.append(self.starttag(node, wrapper, CLASS='graphviz'))
if fname is None:
self.body.append(self.encode(code))
else:
if alt is None:
alt = node.get('alt', self.encode(code).strip())
+ imgcss = imgcls and 'class="%s"' % imgcls or ''
if format == 'svg':
- svgtag = get_svg_tag(fname, outfn, imgcls)
+ svgtag = '<img src="%s" alt="%s" %s/>\n' % (fname, alt, imgcss)
self.body.append(svgtag)
else:
mapfile = open(outfn + '.map', 'rb')
@@ -209,7 +221,6 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
imgmap = mapfile.readlines()
finally:
mapfile.close()
- imgcss = imgcls and 'class="%s"' % imgcls or ''
if len(imgmap) == 2:
# nothing in image map (the lines are <map> and </map>)
self.body.append('<img src="%s" alt="%s" %s/>\n' %
@@ -220,8 +231,11 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
(fname, alt, mapname, imgcss))
self.body.extend(imgmap)
+ if node.get('caption') and not inline:
+ self.body.append('</p>\n<p class="caption">')
+ self.body.append(self.encode(node['caption']))
- self.body.append('</p>\n')
+ self.body.append('</%s>\n' % wrapper)
raise nodes.SkipNode
@@ -236,8 +250,25 @@ def render_dot_latex(self, node, code, options, prefix='graphviz'):
self.builder.warn('dot code %r: ' % code + str(exc))
raise nodes.SkipNode
+ inline = node.get('inline', False)
+ if inline:
+ para_separator = ''
+ else:
+ para_separator = '\n'
+
if fname is not None:
- self.body.append('\n\\includegraphics{%s}\n' % fname)
+ caption = node.get('caption')
+ # XXX add ids from previous target node
+ if caption and not inline:
+ self.body.append('\n\\begin{figure}[h!]')
+ self.body.append('\n\\begin{center}')
+ self.body.append('\n\\caption{%s}' % self.encode(caption))
+ self.body.append('\n\\includegraphics{%s}' % fname)
+ self.body.append('\n\\end{center}')
+ self.body.append('\n\\end{figure}\n')
+ else:
+ self.body.append('%s\\includegraphics{%s}%s' %
+ (para_separator, fname, para_separator))
raise nodes.SkipNode
@@ -245,11 +276,29 @@ def latex_visit_graphviz(self, node):
render_dot_latex(self, node, node['code'], node['options'])
+def render_dot_texinfo(self, node, code, options, prefix='graphviz'):
+ try:
+ fname, outfn = render_dot(self, code, options, 'png', prefix)
+ except GraphvizError, exc:
+ self.builder.warn('dot code %r: ' % code + str(exc))
+ raise nodes.SkipNode
+ if fname is not None:
+ self.body.append('\n\n@float\n')
+ caption = node.get('caption')
+ if caption:
+ self.body.append('@caption{%s}\n' % self.escape_arg(caption))
+ self.body.append('@image{%s,,,[graphviz],png}\n'
+ '@end float\n\n' % fname[:-4])
+ raise nodes.SkipNode
+
+def texinfo_visit_graphviz(self, node):
+ render_dot_texinfo(self, node, node['code'], node['options'])
+
+
def text_visit_graphviz(self, node):
if 'alt' in node.attributes:
self.add_text(_('[graph: %s]') % node['alt'])
self.add_text(_('[graph]'))
- raise nodes.SkipNode
def man_visit_graphviz(self, node):
@@ -263,6 +312,7 @@ def setup(app):
app.add_node(graphviz,
html=(html_visit_graphviz, None),
latex=(latex_visit_graphviz, None),
+ texinfo=(texinfo_visit_graphviz, None),
text=(text_visit_graphviz, None),
man=(man_visit_graphviz, None))
app.add_directive('graphviz', Graphviz)
diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py
index 1b49720f..a2490486 100644
--- a/sphinx/ext/inheritance_diagram.py
+++ b/sphinx/ext/inheritance_diagram.py
@@ -47,7 +47,8 @@ except ImportError:
from docutils import nodes
from docutils.parsers.rst import directives
-from sphinx.ext.graphviz import render_dot_html, render_dot_latex
+from sphinx.ext.graphviz import render_dot_html, render_dot_latex, \
+ render_dot_texinfo
from sphinx.util.compat import Directive
@@ -66,24 +67,23 @@ class InheritanceGraph(object):
from all the way to the root "object", and then is able to generate a
graphviz dot graph from them.
"""
- def __init__(self, class_names, currmodule, show_builtins=False, parts=0):
- """
- *class_names* is a list of child classes to show bases from.
+ def __init__(self, class_names, currmodule, show_builtins=False,
+ private_bases=False, parts=0):
+ """*class_names* is a list of child classes to show bases from.
If *show_builtins* is True, then Python builtins will be shown
in the graph.
"""
self.class_names = class_names
classes = self._import_classes(class_names, currmodule)
- self.class_info = self._class_info(classes, show_builtins, parts)
+ self.class_info = self._class_info(classes, show_builtins,
+ private_bases, parts)
if not self.class_info:
raise InheritanceException('No classes found for '
'inheritance diagram')
def _import_class_or_module(self, name, currmodule):
- """
- Import a class using its fully-qualified *name*.
- """
+ """Import a class using its fully-qualified *name*."""
try:
path, base = class_sig_re.match(name).groups()
except (AttributeError, ValueError):
@@ -134,7 +134,7 @@ class InheritanceGraph(object):
classes.extend(self._import_class_or_module(name, currmodule))
return classes
- def _class_info(self, classes, show_builtins, parts):
+ def _class_info(self, classes, show_builtins, private_bases, parts):
"""Return name and bases for all classes that are ancestors of
*classes*.
@@ -147,6 +147,8 @@ class InheritanceGraph(object):
def recurse(cls):
if not show_builtins and cls in builtins:
return
+ if not private_bases and cls.__name__.startswith('_'):
+ return
nodename = self.class_name(cls, parts)
fullname = self.class_name(cls, 0)
@@ -156,6 +158,8 @@ class InheritanceGraph(object):
for base in cls.__bases__:
if not show_builtins and base in builtins:
continue
+ if not private_bases and base.__name__.startswith('_'):
+ continue
baselist.append(self.class_name(base, parts))
if base not in all_classes:
recurse(base)
@@ -182,9 +186,7 @@ class InheritanceGraph(object):
return '.'.join(name_parts[-parts:])
def get_all_class_names(self):
- """
- Get all of the class names involved in the graph.
- """
+ """Get all of the class names involved in the graph."""
return [fullname for (_, fullname, _) in self.class_info]
# These are the default attrs for graphviz
@@ -213,9 +215,8 @@ class InheritanceGraph(object):
def generate_dot(self, name, urls={}, env=None,
graph_attrs={}, node_attrs={}, edge_attrs={}):
- """
- Generate a graphviz dot graph from the classes that
- were passed in to __init__.
+ """Generate a graphviz dot graph from the classes that were passed in
+ to __init__.
*name* is the name of the graph.
@@ -274,6 +275,7 @@ class InheritanceDiagram(Directive):
final_argument_whitespace = True
option_spec = {
'parts': directives.nonnegative_int,
+ 'private-bases': directives.flag,
}
def run(self):
@@ -290,7 +292,8 @@ class InheritanceDiagram(Directive):
try:
graph = InheritanceGraph(
class_names, env.temp_data.get('py:module'),
- parts=node['parts'])
+ parts=node['parts'],
+ private_bases='private-bases' in self.options)
except InheritanceException, err:
return [node.document.reporter.warning(err.args[0],
line=self.lineno)]
@@ -352,6 +355,21 @@ def latex_visit_inheritance_diagram(self, node):
raise nodes.SkipNode
+def texinfo_visit_inheritance_diagram(self, node):
+ """
+ Output the graph for Texinfo. This will insert a PNG.
+ """
+ graph = node['graph']
+
+ graph_hash = get_graph_hash(node)
+ name = 'inheritance%s' % graph_hash
+
+ dotcode = graph.generate_dot(name, env=self.builder.env,
+ graph_attrs={'size': '"6.0,6.0"'})
+ render_dot_texinfo(self, node, dotcode, [], 'inheritance')
+ raise nodes.SkipNode
+
+
def skip(self, node):
raise nodes.SkipNode
@@ -363,7 +381,8 @@ def setup(app):
latex=(latex_visit_inheritance_diagram, None),
html=(html_visit_inheritance_diagram, None),
text=(skip, None),
- man=(skip, None))
+ man=(skip, None),
+ texinfo=(texinfo_visit_inheritance_diagram, None))
app.add_directive('inheritance-diagram', InheritanceDiagram)
app.add_config_value('inheritance_graph_attrs', {}, False),
app.add_config_value('inheritance_node_attrs', {}, False),
diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py
index 578dddee..9bfd53fd 100644
--- a/sphinx/ext/intersphinx.py
+++ b/sphinx/ext/intersphinx.py
@@ -26,6 +26,7 @@
import time
import zlib
+import codecs
import urllib2
import posixpath
from os import path
@@ -33,19 +34,26 @@ from os import path
from docutils import nodes
from sphinx.builders.html import INVENTORY_FILENAME
+from sphinx.util.pycompat import b
+
handlers = [urllib2.ProxyHandler(), urllib2.HTTPRedirectHandler(),
urllib2.HTTPHandler()]
-if hasattr(urllib2, 'HTTPSHandler'):
+try:
handlers.append(urllib2.HTTPSHandler)
+except NameError:
+ pass
urllib2.install_opener(urllib2.build_opener(*handlers))
+UTF8StreamReader = codecs.lookup('utf-8')[2]
+
def read_inventory_v1(f, uri, join):
+ f = UTF8StreamReader(f)
invdata = {}
line = f.next()
- projname = line.rstrip()[11:].decode('utf-8')
+ projname = line.rstrip()[11:]
line = f.next()
version = line.rstrip()[11:]
for line in f:
@@ -68,25 +76,25 @@ def read_inventory_v2(f, uri, join, bufsize=16*1024):
projname = line.rstrip()[11:].decode('utf-8')
line = f.readline()
version = line.rstrip()[11:].decode('utf-8')
- line = f.readline()
+ line = f.readline().decode('utf-8')
if 'zlib' not in line:
raise ValueError
def read_chunks():
decompressor = zlib.decompressobj()
- for chunk in iter(lambda: f.read(bufsize), ''):
+ for chunk in iter(lambda: f.read(bufsize), b('')):
yield decompressor.decompress(chunk)
yield decompressor.flush()
def split_lines(iter):
- buf = ''
+ buf = b('')
for chunk in iter:
buf += chunk
- lineend = buf.find('\n')
+ lineend = buf.find(b('\n'))
while lineend != -1:
yield buf[:lineend].decode('utf-8')
buf = buf[lineend+1:]
- lineend = buf.find('\n')
+ lineend = buf.find(b('\n'))
assert not buf
for line in split_lines(read_chunks()):
@@ -115,7 +123,7 @@ def fetch_inventory(app, uri, inv):
'%s: %s' % (inv, err.__class__, err))
return
try:
- line = f.readline().rstrip()
+ line = f.readline().rstrip().decode('utf-8')
try:
if line == '# Sphinx inventory version 1':
invdata = read_inventory_v1(f, uri, join)
@@ -150,7 +158,7 @@ def load_mappings(app):
# new format
name, (uri, inv) = key, value
if not name.isalnum():
- env.warn('intersphinx identifier %r is not alphanumeric' % name)
+ env.warn(docname=None, msg='intersphinx identifier %r is not alphanumeric' % name)
else:
# old format, no name
name, uri, inv = None, key, value
diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py
index e7ea82d7..1a2ca6af 100644
--- a/sphinx/ext/mathbase.py
+++ b/sphinx/ext/mathbase.py
@@ -56,6 +56,7 @@ class MathDirective(Directive):
final_argument_whitespace = True
option_spec = {
'label': directives.unchanged,
+ 'name': directives.unchanged,
'nowrap': directives.flag,
}
@@ -65,7 +66,9 @@ class MathDirective(Directive):
latex = self.arguments[0] + '\n\n' + latex
node = displaymath()
node['latex'] = latex
- node['label'] = self.options.get('label', None)
+ node['label'] = self.options.get('name', None)
+ if node['label'] is None:
+ node['label'] = self.options.get('label', None)
node['nowrap'] = 'nowrap' in self.options
node['docname'] = self.state.document.settings.env.docname
ret = [node]
@@ -125,6 +128,24 @@ def man_visit_eqref(self, node):
raise nodes.SkipNode
+def texinfo_visit_math(self, node):
+ self.body.append('@math{' + self.escape_arg(node['latex']) + '}')
+ raise nodes.SkipNode
+
+def texinfo_visit_displaymath(self, node):
+ if node.get('label'):
+ self.add_anchor(node['label'], node)
+ self.body.append('\n\n@example\n%s\n@end example\n\n' %
+ self.escape_arg(node['latex']))
+def texinfo_depart_displaymath(self, node):
+ pass
+
+def texinfo_visit_eqref(self, node):
+ self.add_xref(node['docname'] + ':' + node['target'],
+ node['target'], node)
+ raise nodes.SkipNode
+
+
def html_visit_eqref(self, node):
self.body.append('<a href="#equation-%s">' % node['target'])
@@ -151,20 +172,23 @@ def number_equations(app, doctree, docname):
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
app.add_node(math,
- latex=(latex_visit_math, None),
- text=(text_visit_math, None),
- man=(man_visit_math, None),
- html=htmlinlinevisitors)
+ latex=(latex_visit_math, None),
+ text=(text_visit_math, None),
+ man=(man_visit_math, None),
+ texinfo=(texinfo_visit_math, None),
+ html=htmlinlinevisitors)
app.add_node(displaymath,
- latex=(latex_visit_displaymath, None),
- text=(text_visit_displaymath, None),
- man=(man_visit_displaymath, man_depart_displaymath),
- html=htmldisplayvisitors)
+ latex=(latex_visit_displaymath, None),
+ text=(text_visit_displaymath, None),
+ man=(man_visit_displaymath, man_depart_displaymath),
+ texinfo=(texinfo_visit_displaymath, texinfo_depart_displaymath),
+ html=htmldisplayvisitors)
app.add_node(eqref,
- latex=(latex_visit_eqref, None),
- text=(text_visit_eqref, None),
- man=(man_visit_eqref, None),
- html=(html_visit_eqref, html_depart_eqref))
+ latex=(latex_visit_eqref, None),
+ text=(text_visit_eqref, None),
+ man=(man_visit_eqref, None),
+ texinfo=(texinfo_visit_eqref, None),
+ html=(html_visit_eqref, html_depart_eqref))
app.add_role('math', math_role)
app.add_role('eq', eq_role)
app.add_directive('math', MathDirective)
diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py
new file mode 100644
index 00000000..7a552364
--- /dev/null
+++ b/sphinx/ext/mathjax.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.ext.mathjax
+ ~~~~~~~~~~~~~~~~~~
+
+ Allow `MathJax <http://mathjax.org/>`_ to be used to display math in
+ Sphinx's HTML writer -- requires the MathJax JavaScript library on your
+ webserver/computer.
+
+ :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from docutils import nodes
+
+from sphinx.application import ExtensionError
+from sphinx.ext.mathbase import setup_math as mathbase_setup
+
+
+def html_visit_math(self, node):
+ self.body.append(self.starttag(node, 'span', '', CLASS='math'))
+ self.body.append(self.builder.config.mathjax_inline[0] +
+ self.encode(node['latex']) +
+ self.builder.config.mathjax_inline[1] + '</span>')
+ raise nodes.SkipNode
+
+def html_visit_displaymath(self, node):
+ self.body.append(self.starttag(node, 'div', CLASS='math'))
+ if node['nowrap']:
+ self.body.append(self.builder.config.mathjax_display[0] +
+ node['latex'] +
+ self.builder.config.mathjax_display[1])
+ self.body.append('</div>')
+ raise nodes.SkipNode
+
+ parts = [prt for prt in node['latex'].split('\n\n') if prt.strip()]
+ for i, part in enumerate(parts):
+ part = self.encode(part)
+ if i == 0:
+ # necessary to e.g. set the id property correctly
+ if node['number']:
+ self.body.append('<span class="eqno">(%s)</span>' %
+ node['number'])
+ if '&' in part or '\\\\' in part:
+ self.body.append(self.builder.config.mathjax_display[0] +
+ '\\begin{split}' + part + '\\end{split}' +
+ self.builder.config.mathjax_display[1])
+ else:
+ self.body.append(self.builder.config.mathjax_display[0] + part +
+ self.builder.config.mathjax_display[1])
+ self.body.append('</div>\n')
+ raise nodes.SkipNode
+
+def builder_inited(app):
+ if not app.config.mathjax_path:
+ raise ExtensionError('mathjax_path config value must be set for the '
+ 'mathjax extension to work')
+ app.add_javascript(app.config.mathjax_path)
+
+
+def setup(app):
+ mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
+ app.add_config_value('mathjax_path',
+ 'http://cdn.mathjax.org/mathjax/latest/MathJax.js?'
+ 'config=TeX-AMS-MML_HTMLorMML', False)
+ app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html')
+ app.add_config_value('mathjax_display', [r'\[', r'\]'], 'html')
+ app.connect('builder-inited', builder_inited)
diff --git a/sphinx/ext/oldcmarkup.py b/sphinx/ext/oldcmarkup.py
index 2bf9b65d..41969c36 100644
--- a/sphinx/ext/oldcmarkup.py
+++ b/sphinx/ext/oldcmarkup.py
@@ -18,6 +18,7 @@ WARNING_MSG = 'using old C markup; please migrate to new-style markup ' \
'(e.g. c:function instead of cfunction), see ' \
'http://sphinx.pocoo.org/domains.html'
+
class OldCDirective(Directive):
has_content = True
required_arguments = 1
diff --git a/sphinx/ext/pngmath.py b/sphinx/ext/pngmath.py
index 6fdfbfe7..78c331a6 100644
--- a/sphinx/ext/pngmath.py
+++ b/sphinx/ext/pngmath.py
@@ -26,6 +26,7 @@ from docutils import nodes
from sphinx.errors import SphinxError
from sphinx.util.png import read_png_depth, write_png_depth
from sphinx.util.osutil import ensuredir, ENOENT
+from sphinx.util.pycompat import b
from sphinx.ext.mathbase import setup_math as mathbase_setup, wrap_displaymath
class MathExtError(SphinxError):
@@ -58,11 +59,10 @@ DOC_BODY_PREVIEW = r'''
\end{document}
'''
-depth_re = re.compile(r'\[\d+ depth=(-?\d+)\]')
+depth_re = re.compile(b(r'\[\d+ depth=(-?\d+)\]'))
def render_math(self, math):
- """
- Render the LaTeX math expression *math* using latex and dvipng.
+ """Render the LaTeX math expression *math* using latex and dvipng.
Return the filename relative to the built document and the "depth",
that is, the distance of image bottom and baseline in pixels, if the
@@ -178,6 +178,11 @@ def cleanup_tempdir(app, exc):
except Exception:
pass
+def get_tooltip(self, node):
+ if self.builder.config.pngmath_add_tooltips:
+ return ' alt="%s"' % self.encode(node['latex']).strip()
+ return ''
+
def html_visit_math(self, node):
try:
fname, depth = render_math(self, '$'+node['latex']+'$')
@@ -193,15 +198,10 @@ def html_visit_math(self, node):
self.body.append('<span class="math">%s</span>' %
self.encode(node['latex']).strip())
else:
- if depth is None:
- self.body.append(
- '<img class="math" src="%s" alt="%s"/>' %
- (fname, self.encode(node['latex']).strip()))
- else:
- self.body.append(
- '<img class="math" src="%s" alt="%s" '
- 'style="vertical-align: %dpx"/>' %
- (fname, self.encode(node['latex']).strip(), -depth))
+ c = ('<img class="math" src="%s"' % fname) + get_tooltip(self, node)
+ if depth is not None:
+ c += ' style="vertical-align: %dpx"' % (-depth)
+ self.body.append(c + '/>')
raise nodes.SkipNode
def html_visit_displaymath(self, node):
@@ -226,8 +226,8 @@ def html_visit_displaymath(self, node):
self.body.append('<span class="math">%s</span></p>\n</div>' %
self.encode(node['latex']).strip())
else:
- self.body.append('<img src="%s" alt="%s" /></p>\n</div>' %
- (fname, self.encode(node['latex']).strip()))
+ self.body.append(('<img src="%s"' % fname) + get_tooltip(self, node)
+ + '/></p>\n</div>')
raise nodes.SkipNode
@@ -240,4 +240,5 @@ def setup(app):
['-gamma 1.5', '-D 110'], 'html')
app.add_config_value('pngmath_latex_args', [], 'html')
app.add_config_value('pngmath_latex_preamble', '', 'html')
+ app.add_config_value('pngmath_add_tooltips', True, 'html')
app.connect('build-finished', cleanup_tempdir)
diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py
index 2ba9d5e1..6a44a9b4 100644
--- a/sphinx/ext/todo.py
+++ b/sphinx/ext/todo.py
@@ -159,7 +159,8 @@ def setup(app):
html=(visit_todo_node, depart_todo_node),
latex=(visit_todo_node, depart_todo_node),
text=(visit_todo_node, depart_todo_node),
- man=(visit_todo_node, depart_todo_node))
+ man=(visit_todo_node, depart_todo_node),
+ texinfo=(visit_todo_node, depart_todo_node))
app.add_directive('todo', Todo)
app.add_directive('todolist', TodoList)
diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py
index 4994f125..32840f30 100644
--- a/sphinx/ext/viewcode.py
+++ b/sphinx/ext/viewcode.py
@@ -31,7 +31,11 @@ def doctree_read(app, doctree):
env._viewcode_modules[modname] = False
return
analyzer.find_tags()
- entry = analyzer.code.decode(analyzer.encoding), analyzer.tags, {}
+ if not isinstance(analyzer.code, unicode):
+ code = analyzer.code.decode(analyzer.encoding)
+ else:
+ code = analyzer.code
+ entry = code, analyzer.tags, {}
env._viewcode_modules[modname] = entry
elif entry is False:
return
@@ -94,7 +98,7 @@ def collect_pages(app):
# construct a page name for the highlighted source
pagename = '_modules/' + modname.replace('.', '/')
# highlight the source using the builder's highlighter
- highlighted = highlighter.highlight_block(code, 'python', False)
+ highlighted = highlighter.highlight_block(code, 'python', linenos=False)
# split the code into lines
lines = highlighted.splitlines()
# split off wrap markup from the first line of the actual code