summaryrefslogtreecommitdiff
path: root/sphinx/ext
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/ext')
-rw-r--r--sphinx/ext/autodoc.py39
-rw-r--r--sphinx/ext/autosummary/__init__.py33
-rw-r--r--sphinx/ext/coverage.py20
-rw-r--r--sphinx/ext/doctest.py7
-rw-r--r--sphinx/ext/graphviz.py38
-rw-r--r--sphinx/ext/imgmath.py5
-rw-r--r--sphinx/ext/inheritance_diagram.py8
-rw-r--r--sphinx/ext/intersphinx.py119
-rw-r--r--sphinx/ext/jsmath.py6
-rw-r--r--sphinx/ext/mathbase.py186
-rw-r--r--sphinx/ext/mathjax.py5
-rw-r--r--sphinx/ext/napoleon/__init__.py95
-rw-r--r--sphinx/ext/napoleon/docstring.py127
-rw-r--r--sphinx/ext/todo.py7
-rw-r--r--sphinx/ext/viewcode.py5
15 files changed, 441 insertions, 259 deletions
diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py
index 405d24c88..59e585678 100644
--- a/sphinx/ext/autodoc.py
+++ b/sphinx/ext/autodoc.py
@@ -31,7 +31,7 @@ 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 getargspec, isdescriptor, safe_getmembers, \
- safe_getattr, object_description, is_builtin_class_method
+ safe_getattr, object_description, is_builtin_class_method, isenumattribute
from sphinx.util.docstrings import prepare_docstring
try:
@@ -86,17 +86,24 @@ class Options(dict):
class _MockModule(object):
"""Used by autodoc_mock_imports."""
+ __file__ = '/dev/null'
+ __path__ = '/dev/null'
+
def __init__(self, *args, **kwargs):
- pass
+ self.__all__ = []
def __call__(self, *args, **kwargs):
+ if args and type(args[0]) in [FunctionType, MethodType]:
+ # Appears to be a decorator, pass through unchanged
+ return args[0]
return _MockModule()
+ def _append_submodule(self, submod):
+ self.__all__.append(submod)
+
@classmethod
def __getattr__(cls, name):
- if name in ('__file__', '__path__'):
- return '/dev/null'
- elif name[0] == name[0].upper():
+ if name[0] == name[0].upper():
# Not very good, we assume Uppercase names are classes...
mocktype = type(name, (), {})
mocktype.__module__ = __name__
@@ -109,9 +116,12 @@ def mock_import(modname):
if '.' in modname:
pkg, _n, mods = modname.rpartition('.')
mock_import(pkg)
- mod = _MockModule()
- sys.modules[modname] = mod
- return mod
+ if isinstance(sys.modules[pkg], _MockModule):
+ sys.modules[pkg]._append_submodule(mods)
+
+ if modname not in sys.modules:
+ mod = _MockModule()
+ sys.modules[modname] = mod
ALL = object()
@@ -304,8 +314,9 @@ def format_annotation(annotation):
return '%s[%s]' % (qualified_name, param_str)
elif hasattr(typing, 'CallableMeta') and \
isinstance(annotation, typing.CallableMeta) and \
- hasattr(annotation, '__args__') and \
+ getattr(annotation, '__args__', None) is not None and \
hasattr(annotation, '__result__'):
+ # Skipped in the case of plain typing.Callable
args = annotation.__args__
if args is None:
return qualified_name
@@ -372,7 +383,9 @@ def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
formatted.append('*' + format_arg_with_annotation(varargs))
if kwonlyargs:
- formatted.append('*')
+ if not varargs:
+ formatted.append('*')
+
for kwarg in kwonlyargs:
arg_fd = StringIO()
arg_fd.write(format_arg_with_annotation(kwarg))
@@ -524,7 +537,7 @@ class Documenter(object):
try:
dbg('[autodoc] import %s', self.modname)
for modname in self.env.config.autodoc_mock_imports:
- dbg('[autodoc] adding a mock module %s!', self.modname)
+ dbg('[autodoc] adding a mock module %s!', modname)
mock_import(modname)
__import__(self.modname)
parent = None
@@ -1284,7 +1297,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
u':class:`%s`' % b.__name__ or
u':class:`%s.%s`' % (b.__module__, b.__name__)
for b in self.object.__bases__]
- self.add_line(_(u' Bases: %s') % ', '.join(bases),
+ self.add_line(u' ' + _(u'Bases: %s') % ', '.join(bases),
sourcename)
def get_doc(self, encoding=None, ignore=1):
@@ -1479,6 +1492,8 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
def import_object(self):
ret = ClassLevelDocumenter.import_object(self)
+ if isenumattribute(self.object):
+ self.object = self.object.value
if isdescriptor(self.object) and \
not isinstance(self.object, self.method_types):
self._datadescriptor = True
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index a941aa33a..030fec301 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -58,6 +58,7 @@ import re
import sys
import inspect
import posixpath
+from six import string_types
from types import ModuleType
from six import text_type
@@ -67,7 +68,7 @@ from docutils import nodes
import sphinx
from sphinx import addnodes
-from sphinx.util import rst
+from sphinx.util import import_object, rst
from sphinx.util.compat import Directive
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.ext.autodoc import Options
@@ -136,7 +137,7 @@ def autosummary_table_visit_html(self, node):
# -- autodoc integration -------------------------------------------------------
-class FakeDirective:
+class FakeDirective(object):
env = {}
genopt = Options()
@@ -543,6 +544,22 @@ def autolink_role(typ, rawtext, etext, lineno, inliner,
return r
+def get_rst_suffix(app):
+ def get_supported_format(suffix):
+ parser_class = app.config.source_parsers.get(suffix)
+ if parser_class is None:
+ return ('restructuredtext',)
+ if isinstance(parser_class, string_types):
+ parser_class = import_object(parser_class, 'source parser')
+ return parser_class.supported
+
+ for suffix in app.config.source_suffix:
+ if 'restructuredtext' in get_supported_format(suffix):
+ return suffix
+
+ return None
+
+
def process_generate_options(app):
genfiles = app.config.autosummary_generate
@@ -556,12 +573,18 @@ def process_generate_options(app):
from sphinx.ext.autosummary.generate import generate_autosummary_docs
- ext = app.config.source_suffix[0]
- genfiles = [genfile + (not genfile.endswith(ext) and ext or '')
+ ext = app.config.source_suffix
+ genfiles = [genfile + (not genfile.endswith(tuple(ext)) and ext[0] or '')
for genfile in genfiles]
+ suffix = get_rst_suffix(app)
+ if suffix is None:
+ app.warn('autosummary generats .rst files internally. '
+ 'But your source_suffix does not contain .rst. Skipped.')
+ return
+
generate_autosummary_docs(genfiles, builder=app.builder,
- warn=app.warn, info=app.info, suffix=ext,
+ warn=app.warn, info=app.info, suffix=suffix,
base_path=app.srcdir)
diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py
index 78281bb85..c08b1e706 100644
--- a/sphinx/ext/coverage.py
+++ b/sphinx/ext/coverage.py
@@ -87,8 +87,7 @@ class CoverageBuilder(Builder):
c_objects = self.env.domaindata['c']['objects']
for filename in self.c_sourcefiles:
undoc = set()
- f = open(filename, 'r')
- try:
+ with open(filename, 'r') as f:
for line in f:
for key, regex in self.c_regexes:
match = regex.match(line)
@@ -101,15 +100,12 @@ class CoverageBuilder(Builder):
else:
undoc.add((key, name))
continue
- finally:
- f.close()
if undoc:
self.c_undoc[filename] = undoc
def write_c_coverage(self):
output_file = path.join(self.outdir, 'c.txt')
- op = open(output_file, 'w')
- try:
+ with open(output_file, 'w') as op:
if self.config.coverage_write_headline:
write_header(op, 'Undocumented C API elements', '=')
op.write('\n')
@@ -119,8 +115,6 @@ class CoverageBuilder(Builder):
for typ, name in sorted(undoc):
op.write(' * %-50s [%9s]\n' % (name, typ))
op.write('\n')
- finally:
- op.close()
def build_py_coverage(self):
objects = self.env.domaindata['py']['objects']
@@ -214,9 +208,8 @@ class CoverageBuilder(Builder):
def write_py_coverage(self):
output_file = path.join(self.outdir, 'python.txt')
- op = open(output_file, 'w')
failed = []
- try:
+ with open(output_file, 'w') as op:
if self.config.coverage_write_headline:
write_header(op, 'Undocumented Python objects', '=')
keys = sorted(self.py_undoc.keys())
@@ -247,17 +240,12 @@ class CoverageBuilder(Builder):
if failed:
write_header(op, 'Modules that failed to import')
op.writelines(' * %s -- %s\n' % x for x in failed)
- finally:
- op.close()
def finish(self):
# dump the coverage data to a pickle file too
picklepath = path.join(self.outdir, 'undoc.pickle')
- dumpfile = open(picklepath, 'wb')
- try:
+ with open(picklepath, 'wb') as dumpfile:
pickle.dump((self.py_undoc, self.c_undoc), dumpfile)
- finally:
- dumpfile.close()
def setup(app):
diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py
index 0f5241a19..244762b69 100644
--- a/sphinx/ext/doctest.py
+++ b/sphinx/ext/doctest.py
@@ -214,8 +214,7 @@ class DocTestBuilder(Builder):
def init(self):
# default options
- self.opt = doctest.DONT_ACCEPT_TRUE_FOR_1 | doctest.ELLIPSIS | \
- doctest.IGNORE_EXCEPTION_DETAIL
+ self.opt = self.config.doctest_default_flags
# HACK HACK HACK
# doctest compiles its snippets with type 'single'. That is nice
@@ -464,4 +463,8 @@ def setup(app):
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)
+ app.add_config_value(
+ 'doctest_default_flags',
+ doctest.DONT_ACCEPT_TRUE_FOR_1 | doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL,
+ False)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py
index 4e06677ca..5e76eb8ba 100644
--- a/sphinx/ext/graphviz.py
+++ b/sphinx/ext/graphviz.py
@@ -43,6 +43,8 @@ class graphviz(nodes.General, nodes.Inline, nodes.Element):
def figure_wrapper(directive, node, caption):
figure_node = nodes.figure('', node)
+ if 'align' in node:
+ figure_node['align'] = node.attributes.pop('align')
parsed = nodes.Element()
directive.state.nested_parse(ViewList([caption], source=''),
@@ -55,6 +57,10 @@ def figure_wrapper(directive, node, caption):
return figure_node
+def align_spec(argument):
+ return directives.choice(argument, ('left', 'center', 'right'))
+
+
class Graphviz(Directive):
"""
Directive to insert arbitrary dot markup.
@@ -65,6 +71,7 @@ class Graphviz(Directive):
final_argument_whitespace = False
option_spec = {
'alt': directives.unchanged,
+ 'align': align_spec,
'inline': directives.flag,
'caption': directives.unchanged,
'graphviz_dot': directives.unchanged,
@@ -82,11 +89,8 @@ class Graphviz(Directive):
rel_filename, filename = env.relfn2path(argument)
env.note_dependency(rel_filename)
try:
- fp = codecs.open(filename, 'r', 'utf-8')
- try:
+ with codecs.open(filename, 'r', 'utf-8') as fp:
dotcode = fp.read()
- finally:
- fp.close()
except (IOError, OSError):
return [document.reporter.warning(
'External Graphviz file %r not found or reading '
@@ -104,6 +108,8 @@ class Graphviz(Directive):
node['options']['graphviz_dot'] = self.options['graphviz_dot']
if 'alt' in self.options:
node['alt'] = self.options['alt']
+ if 'align' in self.options:
+ node['align'] = self.options['align']
if 'inline' in self.options:
node['inline'] = True
@@ -124,6 +130,7 @@ class GraphvizSimple(Directive):
final_argument_whitespace = False
option_spec = {
'alt': directives.unchanged,
+ 'align': align_spec,
'inline': directives.flag,
'caption': directives.unchanged,
'graphviz_dot': directives.unchanged,
@@ -138,6 +145,8 @@ class GraphvizSimple(Directive):
node['options']['graphviz_dot'] = self.options['graphviz_dot']
if 'alt' in self.options:
node['alt'] = self.options['alt']
+ if 'align' in self.options:
+ node['align'] = self.options['align']
if 'inline' in self.options:
node['inline'] = True
@@ -239,11 +248,11 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
<p class="warning">%s</p></object>\n''' % (fname, alt)
self.body.append(svgtag)
else:
- mapfile = open(outfn + '.map', 'rb')
- try:
+ if 'align' in node:
+ self.body.append('<div align="%s" class="align-%s">' %
+ (node['align'], node['align']))
+ with open(outfn + '.map', 'rb') as mapfile:
imgmap = mapfile.readlines()
- finally:
- mapfile.close()
if len(imgmap) == 2:
# nothing in image map (the lines are <map> and </map>)
self.body.append('<img src="%s" alt="%s" %s/>\n' %
@@ -254,6 +263,8 @@ 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([item.decode('utf-8') for item in imgmap])
+ if 'align' in node:
+ self.body.append('</div>\n')
raise nodes.SkipNode
@@ -277,8 +288,19 @@ def render_dot_latex(self, node, code, options, prefix='graphviz'):
para_separator = '\n'
if fname is not None:
+ post = None
+ if not is_inline and 'align' in node:
+ if node['align'] == 'left':
+ self.body.append('{')
+ post = '\\hspace*{\\fill}}'
+ elif node['align'] == 'right':
+ self.body.append('{\\hspace*{\\fill}')
+ post = '}'
self.body.append('%s\\includegraphics{%s}%s' %
(para_separator, fname, para_separator))
+ if post:
+ self.body.append(post)
+
raise nodes.SkipNode
diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py
index f8e402be3..e5b8b26c5 100644
--- a/sphinx/ext/imgmath.py
+++ b/sphinx/ext/imgmath.py
@@ -22,6 +22,7 @@ from six import text_type
from docutils import nodes
import sphinx
+from sphinx.locale import _
from sphinx.errors import SphinxError, ExtensionError
from sphinx.util.png import read_png_depth, write_png_depth
from sphinx.util.osutil import ensuredir, ENOENT, cd
@@ -253,7 +254,9 @@ def html_visit_displaymath(self, node):
self.body.append(self.starttag(node, 'div', CLASS='math'))
self.body.append('<p>')
if node['number']:
- self.body.append('<span class="eqno">(%s)</span>' % node['number'])
+ self.body.append('<span class="eqno">(%s)' % node['number'])
+ self.add_permalink_ref(node, _('Permalink to this equation'))
+ self.body.append('</span>')
if fname is None:
# something failed -- use text-only as a bad substitute
self.body.append('<span class="math">%s</span></p>\n</div>' %
diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py
index a06c4b17c..11af67dc5 100644
--- a/sphinx/ext/inheritance_diagram.py
+++ b/sphinx/ext/inheritance_diagram.py
@@ -52,7 +52,7 @@ from docutils.parsers.rst import directives
import sphinx
from sphinx.ext.graphviz import render_dot_html, render_dot_latex, \
- render_dot_texinfo
+ render_dot_texinfo, figure_wrapper
from sphinx.pycode import ModuleAnalyzer
from sphinx.util import force_decode
from sphinx.util.compat import Directive
@@ -297,6 +297,7 @@ class InheritanceDiagram(Directive):
option_spec = {
'parts': directives.nonnegative_int,
'private-bases': directives.flag,
+ 'caption': directives.unchanged,
}
def run(self):
@@ -330,6 +331,11 @@ class InheritanceDiagram(Directive):
# Store the graph object so we can use it to generate the
# dot file later
node['graph'] = graph
+
+ # wrap the result in figure node
+ caption = self.options.get('caption')
+ if caption:
+ node = figure_wrapper(self, node, caption)
return [node]
diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py
index 7b68b3bb0..4ef7e4b9b 100644
--- a/sphinx/ext/intersphinx.py
+++ b/sphinx/ext/intersphinx.py
@@ -34,7 +34,6 @@ from os import path
import re
from six import iteritems, string_types
-from six.moves.urllib import request
from six.moves.urllib.parse import urlsplit, urlunsplit
from docutils import nodes
from docutils.utils import relative_path
@@ -42,17 +41,9 @@ from docutils.utils import relative_path
import sphinx
from sphinx.locale import _
from sphinx.builders.html import INVENTORY_FILENAME
+from sphinx.util.requests import requests, useragent_header
-default_handlers = [request.ProxyHandler(), request.HTTPRedirectHandler(),
- request.HTTPHandler()]
-try:
- default_handlers.append(request.HTTPSHandler)
-except AttributeError:
- pass
-
-default_opener = request.build_opener(*default_handlers)
-
UTF8StreamReader = codecs.lookup('utf-8')[2]
@@ -125,6 +116,14 @@ def read_inventory_v2(f, uri, join, bufsize=16*1024):
return invdata
+def read_inventory(f, uri, join, bufsize=16*1024):
+ line = f.readline().rstrip().decode('utf-8')
+ if line == '# Sphinx inventory version 1':
+ return read_inventory_v1(f, uri, join)
+ elif line == '# Sphinx inventory version 2':
+ return read_inventory_v2(f, uri, join, bufsize=bufsize)
+
+
def _strip_basic_auth(url):
"""Returns *url* with basic auth credentials removed. Also returns the
basic auth username and password if they're present in *url*.
@@ -136,30 +135,17 @@ def _strip_basic_auth(url):
:param url: url which may or may not contain basic auth credentials
:type url: ``str``
- :return: 3-``tuple`` of:
-
- * (``str``) -- *url* with any basic auth creds removed
- * (``str`` or ``NoneType``) -- basic auth username or ``None`` if basic
- auth username not given
- * (``str`` or ``NoneType``) -- basic auth password or ``None`` if basic
- auth password not given
-
- :rtype: ``tuple``
+ :return: *url* with any basic auth creds removed
+ :rtype: ``str``
"""
- url_parts = urlsplit(url)
- username = url_parts.username
- password = url_parts.password
- frags = list(url_parts)
+ frags = list(urlsplit(url))
# swap out "user[:pass]@hostname" for "hostname"
- if url_parts.port:
- frags[1] = "%s:%s" % (url_parts.hostname, url_parts.port)
- else:
- frags[1] = url_parts.hostname
- url = urlunsplit(frags)
- return (url, username, password)
+ if '@' in frags[1]:
+ frags[1] = frags[1].split('@')[1]
+ return urlunsplit(frags)
-def _read_from_url(url):
+def _read_from_url(url, timeout=None):
"""Reads data from *url* with an HTTP *GET*.
This function supports fetching from resources which use basic HTTP auth as
@@ -175,48 +161,35 @@ def _read_from_url(url):
:return: data read from resource described by *url*
:rtype: ``file``-like object
"""
- url, username, password = _strip_basic_auth(url)
- if username is not None and password is not None:
- # case: url contains basic auth creds
- password_mgr = request.HTTPPasswordMgrWithDefaultRealm()
- password_mgr.add_password(None, url, username, password)
- handler = request.HTTPBasicAuthHandler(password_mgr)
- opener = request.build_opener(*(default_handlers + [handler]))
- else:
- opener = default_opener
-
- return opener.open(url)
+ r = requests.get(url, stream=True, timeout=timeout, headers=dict(useragent_header))
+ r.raise_for_status()
+ r.raw.url = r.url
+ return r.raw
def _get_safe_url(url):
"""Gets version of *url* with basic auth passwords obscured. This function
returns results suitable for printing and logging.
- E.g.: https://user:12345@example.com => https://user:********@example.com
-
- .. note::
-
- The number of astrisks is invariant in the length of the basic auth
- password, so minimal information is leaked.
+ E.g.: https://user:12345@example.com => https://user@example.com
:param url: a url
:type url: ``str``
- :return: *url* with password obscured
+ :return: *url* with password removed
:rtype: ``str``
"""
- safe_url = url
- url, username, _ = _strip_basic_auth(url)
- if username is not None:
- # case: url contained basic auth creds; obscure password
- url_parts = urlsplit(url)
- safe_netloc = '{0}@{1}'.format(username, url_parts.hostname)
- # replace original netloc w/ obscured version
- frags = list(url_parts)
- frags[1] = safe_netloc
- safe_url = urlunsplit(frags)
+ parts = urlsplit(url)
+ if parts.username is None:
+ return url
+ else:
+ frags = list(parts)
+ if parts.port:
+ frags[1] = '{0}@{1}:{2}'.format(parts.username, parts.hostname, parts.port)
+ else:
+ frags[1] = '{0}@{1}'.format(parts.username, parts.hostname)
- return safe_url
+ return urlunsplit(frags)
def fetch_inventory(app, uri, inv):
@@ -226,11 +199,10 @@ def fetch_inventory(app, uri, inv):
localuri = '://' not in uri
if not localuri:
# case: inv URI points to remote resource; strip any existing auth
- uri, _, _ = _strip_basic_auth(uri)
- join = localuri and path.join or posixpath.join
+ uri = _strip_basic_auth(uri)
try:
if '://' in inv:
- f = _read_from_url(inv)
+ f = _read_from_url(inv, timeout=app.config.intersphinx_timeout)
else:
f = open(path.join(app.srcdir, inv), 'rb')
except Exception as err:
@@ -238,25 +210,19 @@ def fetch_inventory(app, uri, inv):
'%s: %s' % (inv, err.__class__, err))
return
try:
- if hasattr(f, 'geturl'):
- newinv = f.geturl()
+ if hasattr(f, 'url'):
+ newinv = f.url
if inv != newinv:
app.info('intersphinx inventory has moved: %s -> %s' % (inv, newinv))
if uri in (inv, path.dirname(inv), path.dirname(inv) + '/'):
uri = path.dirname(newinv)
- line = f.readline().rstrip().decode('utf-8')
- try:
- if line == '# Sphinx inventory version 1':
- invdata = read_inventory_v1(f, uri, join)
- elif line == '# Sphinx inventory version 2':
- invdata = read_inventory_v2(f, uri, join)
- else:
- raise ValueError
- f.close()
- except ValueError:
- f.close()
- raise ValueError('unknown or unsupported inventory version')
+ with f:
+ try:
+ join = localuri and path.join or posixpath.join
+ invdata = read_inventory(f, uri, join)
+ except ValueError:
+ raise ValueError('unknown or unsupported inventory version')
except Exception as err:
app.warn('intersphinx inventory %r not readable due to '
'%s: %s' % (inv, err.__class__.__name__, err))
@@ -394,6 +360,7 @@ def missing_reference(app, env, node, contnode):
def setup(app):
app.add_config_value('intersphinx_mapping', {}, True)
app.add_config_value('intersphinx_cache_limit', 5, False)
+ app.add_config_value('intersphinx_timeout', None, False)
app.connect('missing-reference', missing_reference)
app.connect('builder-inited', load_mappings)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py
index f36e12fed..9981ffd3a 100644
--- a/sphinx/ext/jsmath.py
+++ b/sphinx/ext/jsmath.py
@@ -13,6 +13,7 @@
from docutils import nodes
import sphinx
+from sphinx.locale import _
from sphinx.application import ExtensionError
from sphinx.ext.mathbase import setup_math as mathbase_setup
@@ -34,8 +35,9 @@ def html_visit_displaymath(self, node):
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'])
+ self.body.append('<span class="eqno">(%s)' % node['number'])
+ self.add_permalink_ref(node, _('Permalink to this equation'))
+ self.body.append('</span>')
self.body.append(self.starttag(node, 'div', CLASS='math'))
else:
# but only once!
diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py
index 430813a64..ae4b439b7 100644
--- a/sphinx/ext/mathbase.py
+++ b/sphinx/ext/mathbase.py
@@ -12,7 +12,10 @@
from docutils import nodes, utils
from docutils.parsers.rst import directives
-from sphinx.util.nodes import set_source_info
+from sphinx.roles import XRefRole
+from sphinx.locale import _
+from sphinx.domains import Domain
+from sphinx.util.nodes import make_refnode, set_source_info
from sphinx.util.compat import Directive
@@ -28,6 +31,76 @@ class eqref(nodes.Inline, nodes.TextElement):
pass
+class EqXRefRole(XRefRole):
+ def result_nodes(self, document, env, node, is_ref):
+ node['refdomain'] = 'math'
+ return [node], []
+
+
+class MathDomain(Domain):
+ """Mathematics domain."""
+ name = 'math'
+ label = 'mathematics'
+
+ initial_data = {
+ 'objects': {}, # labelid -> (docname, eqno)
+ }
+ dangling_warnings = {
+ 'eq': 'equation not found: %(target)s',
+ }
+
+ def clear_doc(self, docname):
+ for labelid, (doc, eqno) in list(self.data['objects'].items()):
+ if doc == docname:
+ del self.data['objects'][labelid]
+
+ def merge_domaindata(self, docnames, otherdata):
+ for labelid, (doc, eqno) in otherdata['objects'].items():
+ if doc in docnames:
+ self.data['objects'][labelid] = doc
+
+ def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+ assert typ == 'eq'
+ docname, number = self.data['objects'].get(target, (None, None))
+ if docname:
+ if builder.name == 'latex':
+ newnode = eqref('', **node.attributes)
+ newnode['docname'] = docname
+ newnode['target'] = target
+ return newnode
+ else:
+ title = nodes.Text("(%d)" % number)
+ return make_refnode(builder, fromdocname, docname,
+ "equation-" + target, title)
+ else:
+ return None
+
+ def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
+ refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode)
+ if refnode is None:
+ return []
+ else:
+ return [refnode]
+
+ def get_objects(self):
+ return []
+
+ def add_equation(self, env, docname, labelid):
+ equations = self.data['objects']
+ if labelid in equations:
+ path = env.doc2path(equations[labelid][0])
+ msg = _('duplicate label of equation %s, other instance in %s') % (labelid, path)
+ raise UserWarning(msg)
+ else:
+ eqno = self.get_next_equation_number(docname)
+ equations[labelid] = (docname, eqno)
+ return eqno
+
+ def get_next_equation_number(self, docname):
+ targets = [eq for eq in self.data['objects'].values() if eq[0] == docname]
+ return len(targets) + 1
+
+
def wrap_displaymath(math, label, numbering):
def is_equation(part):
return part.strip()
@@ -68,13 +141,6 @@ def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
return [math(latex=latex)], []
-def eq_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
- text = utils.unescape(text)
- node = eqref('(?)', '(?)', target=text)
- node['docname'] = inliner.document.settings.env.docname
- return [node], []
-
-
def is_in_section_title(node):
"""Determine whether the node is in a section title"""
from sphinx.util.nodes import traverse_parent
@@ -104,21 +170,47 @@ class MathDirective(Directive):
latex = self.arguments[0] + '\n\n' + latex
node = displaymath()
node['latex'] = latex
- node['label'] = self.options.get('name', None)
- if node['label'] is None:
- node['label'] = self.options.get('label', None)
+ node['number'] = None
+ node['label'] = None
+ if 'name' in self.options:
+ node['label'] = self.options['name']
+ if 'label' in self.options:
+ node['label'] = self.options['label']
node['nowrap'] = 'nowrap' in self.options
node['docname'] = self.state.document.settings.env.docname
ret = [node]
set_source_info(self, node)
if hasattr(self, 'src'):
node.source = self.src
- if node['label']:
- tnode = nodes.target('', '', ids=['equation-' + node['label']])
- self.state.document.note_explicit_target(tnode)
- ret.insert(0, tnode)
+ self.add_target(ret)
return ret
+ def add_target(self, ret):
+ node = ret[0]
+ env = self.state.document.settings.env
+
+ # assign label automatically if math_number_all enabled
+ if node['label'] == '' or (env.config.math_number_all and not node['label']):
+ seq = env.new_serialno('sphinx.ext.math#equations')
+ node['label'] = "%s:%d" % (env.docname, seq)
+
+ # no targets and numbers are needed
+ if not node['label']:
+ return
+
+ # register label to domain
+ domain = env.get_domain('math')
+ try:
+ eqno = domain.add_equation(env, env.docname, node['label'])
+ node['number'] = eqno
+
+ # add target node
+ target = nodes.target('', '', ids=['equation-' + node['label']])
+ self.state.document.note_explicit_target(target)
+ ret.insert(0, target)
+ except UserWarning as exc:
+ self.state_machine.reporter.warning(exc.args[0], line=self.lineno)
+
def latex_visit_math(self, node):
if is_in_section_title(node):
@@ -131,7 +223,11 @@ def latex_visit_math(self, node):
def latex_visit_displaymath(self, node):
- label = node['label'] and node['docname'] + '-' + node['label'] or None
+ if not node['label']:
+ label = None
+ else:
+ label = "equation:%s:%s" % (node['docname'], node['label'])
+
if node['nowrap']:
if label:
self.body.append(r'\label{%s}' % label)
@@ -143,7 +239,8 @@ def latex_visit_displaymath(self, node):
def latex_visit_eqref(self, node):
- self.body.append('\\eqref{%s-%s}' % (node['docname'], node['target']))
+ label = "equation:%s:%s" % (node['docname'], node['target'])
+ self.body.append('\\eqref{%s}' % label)
raise nodes.SkipNode
@@ -159,11 +256,6 @@ def text_visit_displaymath(self, node):
raise nodes.SkipNode
-def text_visit_eqref(self, node):
- self.add_text(node['target'])
- raise nodes.SkipNode
-
-
def man_visit_math(self, node):
self.body.append(node['latex'])
raise nodes.SkipNode
@@ -177,11 +269,6 @@ def man_depart_displaymath(self, node):
self.depart_centered(node)
-def man_visit_eqref(self, node):
- self.body.append(node['target'])
- raise nodes.SkipNode
-
-
def texinfo_visit_math(self, node):
self.body.append('@math{' + self.escape_arg(node['latex']) + '}')
raise nodes.SkipNode
@@ -198,40 +285,9 @@ 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'])
-
-
-def html_depart_eqref(self, node):
- self.body.append('</a>')
-
-
-def number_equations(app, doctree, docname):
- num = 0
- numbers = {}
- for node in doctree.traverse(displaymath):
- if node['label'] is not None or app.config.math_number_all:
- num += 1
- node['number'] = num
- if node['label'] is not None:
- numbers[node['label']] = num
- else:
- node['number'] = None
- for node in doctree.traverse(eqref):
- if node['target'] not in numbers:
- continue
- num = '(%d)' % numbers[node['target']]
- node[0] = nodes.Text(num, num)
-
-
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
- app.add_config_value('math_number_all', False, 'html')
+ app.add_config_value('math_number_all', False, 'env')
+ app.add_domain(MathDomain)
app.add_node(math, override=True,
latex=(latex_visit_math, None),
text=(text_visit_math, None),
@@ -244,13 +300,7 @@ def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
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),
- texinfo=(texinfo_visit_eqref, None),
- html=(html_visit_eqref, html_depart_eqref))
+ app.add_node(eqref, latex=(latex_visit_eqref, None))
app.add_role('math', math_role)
- app.add_role('eq', eq_role)
+ app.add_role('eq', EqXRefRole(warn_dangling=True))
app.add_directive('math', MathDirective)
- app.connect('doctree-resolved', number_equations)
diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py
index bdaab55e0..2f414f4e7 100644
--- a/sphinx/ext/mathjax.py
+++ b/sphinx/ext/mathjax.py
@@ -14,6 +14,7 @@
from docutils import nodes
import sphinx
+from sphinx.locale import _
from sphinx.errors import ExtensionError
from sphinx.ext.mathbase import setup_math as mathbase_setup
@@ -35,7 +36,9 @@ def html_visit_displaymath(self, node):
# necessary to e.g. set the id property correctly
if node['number']:
- self.body.append('<span class="eqno">(%s)</span>' % node['number'])
+ self.body.append('<span class="eqno">(%s)' % node['number'])
+ self.add_permalink_ref(node, _('Permalink to this equation'))
+ self.body.append('</span>')
self.body.append(self.builder.config.mathjax_display[0])
parts = [prt for prt in node['latex'].split('\n\n') if prt.strip()]
if len(parts) > 1: # Add alignment if there are more than 1 equation
diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py
index 85c8acec8..651355c57 100644
--- a/sphinx/ext/napoleon/__init__.py
+++ b/sphinx/ext/napoleon/__init__.py
@@ -33,6 +33,7 @@ class Config(object):
# Napoleon settings
napoleon_google_docstring = True
napoleon_numpy_docstring = True
+ napoleon_include_init_with_doc = False
napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = False
napoleon_use_admonition_for_examples = False
@@ -41,6 +42,7 @@ class Config(object):
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
+ napoleon_use_keyword = True
.. _Google style:
http://google.github.io/styleguide/pyguide.html
@@ -49,13 +51,29 @@ class Config(object):
Attributes
----------
- napoleon_google_docstring : bool, defaults to True
+ napoleon_google_docstring : :obj:`bool` (Defaults to True)
True to parse `Google style`_ docstrings. False to disable support
for Google style docstrings.
- napoleon_numpy_docstring : bool, defaults to True
+ napoleon_numpy_docstring : :obj:`bool` (Defaults to True)
True to parse `NumPy style`_ docstrings. False to disable support
for NumPy style docstrings.
- napoleon_include_private_with_doc : bool, defaults to False
+ napoleon_include_init_with_doc : :obj:`bool` (Defaults to False)
+ True to list ``__init___`` docstrings separately from the class
+ docstring. False to fall back to Sphinx's default behavior, which
+ considers the ``__init___`` docstring as part of the class
+ documentation.
+
+ **If True**::
+
+ def __init__(self):
+ \"\"\"
+ This will be included in the docs because it has a docstring
+ \"\"\"
+
+ def __init__(self):
+ # This will NOT be included in the docs
+
+ napoleon_include_private_with_doc : :obj:`bool` (Defaults to False)
True to include private members (like ``_membername``) with docstrings
in the documentation. False to fall back to Sphinx's default behavior.
@@ -71,7 +89,7 @@ class Config(object):
# This will NOT be included in the docs
pass
- napoleon_include_special_with_doc : bool, defaults to False
+ napoleon_include_special_with_doc : :obj:`bool` (Defaults to False)
True to include special members (like ``__membername__``) with
docstrings in the documentation. False to fall back to Sphinx's
default behavior.
@@ -88,7 +106,7 @@ class Config(object):
# This will NOT be included in the docs
return unicode(self.__class__.__name__)
- napoleon_use_admonition_for_examples : bool, defaults to False
+ napoleon_use_admonition_for_examples : :obj:`bool` (Defaults to False)
True to use the ``.. admonition::`` directive for the **Example** and
**Examples** sections. False to use the ``.. rubric::`` directive
instead. One may look better than the other depending on what HTML
@@ -112,7 +130,7 @@ class Config(object):
This is just a quick example
- napoleon_use_admonition_for_notes : bool, defaults to False
+ napoleon_use_admonition_for_notes : :obj:`bool` (Defaults to False)
True to use the ``.. admonition::`` directive for **Notes** sections.
False to use the ``.. rubric::`` directive instead.
@@ -125,7 +143,7 @@ class Config(object):
--------
:attr:`napoleon_use_admonition_for_examples`
- napoleon_use_admonition_for_references : bool, defaults to False
+ napoleon_use_admonition_for_references : :obj:`bool` (Defaults to False)
True to use the ``.. admonition::`` directive for **References**
sections. False to use the ``.. rubric::`` directive instead.
@@ -133,7 +151,7 @@ class Config(object):
--------
:attr:`napoleon_use_admonition_for_examples`
- napoleon_use_ivar : bool, defaults to False
+ napoleon_use_ivar : :obj:`bool` (Defaults to False)
True to use the ``:ivar:`` role for instance variables. False to use
the ``.. attribute::`` directive instead.
@@ -157,7 +175,7 @@ class Config(object):
Description of `attr1`
- napoleon_use_param : bool, defaults to True
+ napoleon_use_param : :obj:`bool` (Defaults to True)
True to use a ``:param:`` role for each function parameter. False to
use a single ``:parameters:`` role for all the parameters.
@@ -184,7 +202,22 @@ class Config(object):
* **arg2** (*int, optional*) --
Description of `arg2`, defaults to 0
- napoleon_use_rtype : bool, defaults to True
+ napoleon_use_keyword : :obj:`bool` (Defaults to True)
+ True to use a ``:keyword:`` role for each function keyword argument.
+ False to use a single ``:keyword arguments:`` role for all the
+ keywords.
+
+ This behaves similarly to :attr:`napoleon_use_param`. Note unlike
+ docutils, ``:keyword:`` and ``:param:`` will not be treated the same
+ way - there will be a separate "Keyword Arguments" section, rendered
+ in the same fashion as "Parameters" section (type links created if
+ possible)
+
+ See Also
+ --------
+ :attr:`napoleon_use_param`
+
+ napoleon_use_rtype : :obj:`bool` (Defaults to True)
True to use the ``:rtype:`` role for the return type. False to output
the return type inline with the description.
@@ -208,6 +241,7 @@ class Config(object):
_config_values = {
'napoleon_google_docstring': (True, 'env'),
'napoleon_numpy_docstring': (True, 'env'),
+ 'napoleon_include_init_with_doc': (False, 'env'),
'napoleon_include_private_with_doc': (False, 'env'),
'napoleon_include_special_with_doc': (False, 'env'),
'napoleon_use_admonition_for_examples': (False, 'env'),
@@ -216,6 +250,7 @@ class Config(object):
'napoleon_use_ivar': (False, 'env'),
'napoleon_use_param': (True, 'env'),
'napoleon_use_rtype': (True, 'env'),
+ 'napoleon_use_keyword': (True, 'env')
}
def __init__(self, **settings):
@@ -251,6 +286,8 @@ def setup(app):
if not isinstance(app, Sphinx):
return # probably called by tests
+ _patch_python_domain()
+
app.connect('autodoc-process-docstring', _process_docstring)
app.connect('autodoc-skip-member', _skip_member)
@@ -259,6 +296,26 @@ def setup(app):
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
+def _patch_python_domain():
+ try:
+ from sphinx.domains.python import PyTypedField
+ except ImportError:
+ pass
+ else:
+ import sphinx.domains.python
+ import sphinx.locale
+ l_ = sphinx.locale.lazy_gettext
+ for doc_field in sphinx.domains.python.PyObject.doc_field_types:
+ if doc_field.name == 'parameter':
+ doc_field.names = ('param', 'parameter', 'arg', 'argument')
+ break
+ sphinx.domains.python.PyObject.doc_field_types.append(
+ PyTypedField('keyword', label=l_('Keyword Arguments'),
+ names=('keyword', 'kwarg', 'kwparam'),
+ typerolename='obj', typenames=('paramtype', 'kwtype'),
+ can_collapse=True))
+
+
def _process_docstring(app, what, name, obj, options, lines):
"""Process the docstring for a given python object.
@@ -311,8 +368,10 @@ def _skip_member(app, what, name, obj, skip, options):
"""Determine if private and special class members are included in docs.
The following settings in conf.py determine if private and special class
- members are included in the generated documentation:
+ members or init methods are included in the generated documentation:
+ * ``napoleon_include_init_with_doc`` --
+ include init methods if they have docstrings
* ``napoleon_include_private_with_doc`` --
include private members if they have docstrings
* ``napoleon_include_special_with_doc`` --
@@ -349,7 +408,7 @@ def _skip_member(app, what, name, obj, skip, options):
"""
has_doc = getattr(obj, '__doc__', False)
is_member = (what == 'class' or what == 'exception' or what == 'module')
- if name != '__weakref__' and name != '__init__' and has_doc and is_member:
+ if name != '__weakref__' and has_doc and is_member:
cls_is_owner = False
if what == 'class' or what == 'exception':
if PY2:
@@ -382,10 +441,16 @@ def _skip_member(app, what, name, obj, skip, options):
cls_is_owner = True
if what == 'module' or cls_is_owner:
- is_special = name.startswith('__') and name.endswith('__')
- is_private = not is_special and name.startswith('_')
+ is_init = (name == '__init__')
+ is_special = (not is_init and name.startswith('__') and
+ name.endswith('__'))
+ is_private = (not is_init and not is_special and
+ name.startswith('_'))
+ inc_init = app.config.napoleon_include_init_with_doc
inc_special = app.config.napoleon_include_special_with_doc
inc_private = app.config.napoleon_include_private_with_doc
- if (is_special and inc_special) or (is_private and inc_private):
+ if ((is_special and inc_special) or
+ (is_private and inc_private) or
+ (is_init and inc_init)):
return False
return skip
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 742a9fbba..e526a11ae 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -24,8 +24,9 @@ from sphinx.util.pycompat import UnicodeMixin
_directive_regex = re.compile(r'\.\. \S+::')
_google_section_regex = re.compile(r'^(\s|\w)+:\s*$')
-_google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.+?)\s*\)')
+_google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.*[^\s]+)\s*\)')
_numpy_section_regex = re.compile(r'^[=\-`:\'"~^_*+#<>]{2,}\s*$')
+_single_colon_regex = re.compile(r'(?<!:):(?!:)')
_xref_regex = re.compile(r'(:\w+:\S+:`.+?`|:\S+:`.+?`|`.+?`)')
_bullet_list_regex = re.compile(r'^(\*|\+|\-)(\s+\S|\s*$)')
_enumerated_list_regex = re.compile(
@@ -39,36 +40,34 @@ class GoogleDocstring(UnicodeMixin):
Parameters
----------
- docstring : str or List[str]
+ docstring : :obj:`str` or :obj:`list` of :obj:`str`
The docstring to parse, given either as a string or split into
individual lines.
- config : Optional[sphinx.ext.napoleon.Config or sphinx.config.Config]
+ config: :obj:`sphinx.ext.napoleon.Config` or :obj:`sphinx.config.Config`
The configuration settings to use. If not given, defaults to the
config object on `app`; or if `app` is not given defaults to the
- a new `sphinx.ext.napoleon.Config` object.
+ a new :class:`sphinx.ext.napoleon.Config` object.
- See Also
- --------
- :class:`sphinx.ext.napoleon.Config`
Other Parameters
----------------
- app : Optional[sphinx.application.Sphinx]
+ app : :class:`sphinx.application.Sphinx`, optional
Application object representing the Sphinx process.
- what : Optional[str]
+ what : :obj:`str`, optional
A string specifying the type of the object to which the docstring
belongs. Valid values: "module", "class", "exception", "function",
"method", "attribute".
- name : Optional[str]
+ name : :obj:`str`, optional
The fully qualified name of the object.
obj : module, class, exception, function, method, or attribute
The object to which the docstring belongs.
- options : Optional[sphinx.ext.autodoc.Options]
+ options : :class:`sphinx.ext.autodoc.Options`, optional
The options given to the directive: an object with attributes
inherited_members, undoc_members, show_inheritance and noindex that
are True if the flag option of same name was given to the auto
directive.
+
Example
-------
>>> from sphinx.ext.napoleon import Config
@@ -174,7 +173,7 @@ class GoogleDocstring(UnicodeMixin):
Returns
-------
- List[str]
+ list(str)
The lines of the docstring in a list.
"""
@@ -256,12 +255,7 @@ class GoogleDocstring(UnicodeMixin):
else:
_desc = lines[1:]
- match = _google_typed_arg_regex.match(before)
- if match:
- _name = match.group(1)
- _type = match.group(2)
- else:
- _type = before
+ _type = before
_desc = self.__class__(_desc, self._config).lines()
return [(_name, _type, _desc,)]
@@ -307,6 +301,19 @@ class GoogleDocstring(UnicodeMixin):
else:
return name
+ def _fix_field_desc(self, desc):
+ if self._is_list(desc):
+ desc = [''] + desc
+ elif desc[0].endswith('::'):
+ desc_block = desc[1:]
+ indent = self._get_indent(desc[0])
+ block_indent = self._get_initial_indent(desc_block)
+ if block_indent > indent:
+ desc = [''] + desc
+ else:
+ desc = ['', desc[0]] + self._indent(desc_block, 4)
+ return desc
+
def _format_admonition(self, admonition, lines):
lines = self._strip_empty(lines)
if len(lines) == 1:
@@ -333,6 +340,22 @@ class GoogleDocstring(UnicodeMixin):
else:
return [prefix]
+ def _format_docutils_params(self, fields, field_role='param',
+ type_role='type'):
+ lines = []
+ for _name, _type, _desc in fields:
+ _desc = self._strip_empty(_desc)
+ if any(_desc):
+ _desc = self._fix_field_desc(_desc)
+ field = ':%s %s: ' % (field_role, _name)
+ lines.extend(self._format_block(field, _desc))
+ else:
+ lines.append(':%s %s:' % (field_role, _name))
+
+ if _type:
+ lines.append(':%s %s: %s' % (type_role, _name, _type))
+ return lines + ['']
+
def _format_field(self, _name, _type, _desc):
_desc = self._strip_empty(_desc)
has_desc = any(_desc)
@@ -354,9 +377,11 @@ class GoogleDocstring(UnicodeMixin):
field = ''
if has_desc:
- if self._is_list(_desc):
- return [field, ''] + _desc
- return [field + _desc[0]] + _desc[1:]
+ _desc = self._fix_field_desc(_desc)
+ if _desc[0]:
+ return [field + _desc[0]] + _desc[1:]
+ else:
+ return [field] + _desc
else:
return [field]
@@ -393,6 +418,12 @@ class GoogleDocstring(UnicodeMixin):
return i
return len(line)
+ def _get_initial_indent(self, lines):
+ for line in lines:
+ if line:
+ return self._get_indent(line)
+ return 0
+
def _get_min_indent(self, lines):
min_indent = None
for line in lines:
@@ -527,7 +558,14 @@ class GoogleDocstring(UnicodeMixin):
return [header, '']
def _parse_keyword_arguments_section(self, section):
- return self._format_fields('Keyword Arguments', self._consume_fields())
+ fields = self._consume_fields()
+ if self._config.napoleon_use_keyword:
+ return self._format_docutils_params(
+ fields,
+ field_role="keyword",
+ type_role="kwtype")
+ else:
+ return self._format_fields('Keyword Arguments', fields)
def _parse_methods_section(self, section):
lines = []
@@ -552,21 +590,7 @@ class GoogleDocstring(UnicodeMixin):
def _parse_parameters_section(self, section):
fields = self._consume_fields()
if self._config.napoleon_use_param:
- lines = []
- for _name, _type, _desc in fields:
- _desc = self._strip_empty(_desc)
- if any(_desc):
- if self._is_list(_desc):
- _desc = [''] + _desc
- field = ':param %s: ' % _name
- lines.extend(self._format_block(field, _desc))
- else:
- lines.append(':param %s:' % _name)
-
- if _type:
- lines.append(':type %s: %s' % (_name, _type))
-
- return lines + ['']
+ return self._format_docutils_params(fields)
else:
return self._format_fields('Parameters', fields)
@@ -668,11 +692,12 @@ class GoogleDocstring(UnicodeMixin):
if found_colon:
after_colon.append(source)
else:
- if (i % 2) == 0 and ":" in source:
+ m = _single_colon_regex.search(source)
+ if (i % 2) == 0 and m:
found_colon = True
- before, colon, after = source.partition(":")
- before_colon.append(before)
- after_colon.append(after)
+ colon = source[m.start(): m.end()]
+ before_colon.append(source[:m.start()])
+ after_colon.append(source[m.end():])
else:
before_colon.append(source)
@@ -705,36 +730,34 @@ class NumpyDocstring(GoogleDocstring):
Parameters
----------
- docstring : str or List[str]
+ docstring : :obj:`str` or :obj:`list` of :obj:`str`
The docstring to parse, given either as a string or split into
individual lines.
- config : Optional[sphinx.ext.napoleon.Config or sphinx.config.Config]
+ config: :obj:`sphinx.ext.napoleon.Config` or :obj:`sphinx.config.Config`
The configuration settings to use. If not given, defaults to the
config object on `app`; or if `app` is not given defaults to the
- a new `sphinx.ext.napoleon.Config` object.
+ a new :class:`sphinx.ext.napoleon.Config` object.
- See Also
- --------
- :class:`sphinx.ext.napoleon.Config`
Other Parameters
----------------
- app : Optional[sphinx.application.Sphinx]
+ app : :class:`sphinx.application.Sphinx`, optional
Application object representing the Sphinx process.
- what : Optional[str]
+ what : :obj:`str`, optional
A string specifying the type of the object to which the docstring
belongs. Valid values: "module", "class", "exception", "function",
"method", "attribute".
- name : Optional[str]
+ name : :obj:`str`, optional
The fully qualified name of the object.
obj : module, class, exception, function, method, or attribute
The object to which the docstring belongs.
- options : Optional[sphinx.ext.autodoc.Options]
+ options : :class:`sphinx.ext.autodoc.Options`, optional
The options given to the directive: an object with attributes
inherited_members, undoc_members, show_inheritance and noindex that
are True if the flag option of same name was given to the auto
directive.
+
Example
-------
>>> from sphinx.ext.napoleon import Config
@@ -791,7 +814,7 @@ class NumpyDocstring(GoogleDocstring):
Returns
-------
- List[str]
+ list(str)
The lines of the docstring in a list.
"""
diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py
index a853ec93c..f3b526ce6 100644
--- a/sphinx/ext/todo.py
+++ b/sphinx/ext/todo.py
@@ -70,6 +70,8 @@ def process_todos(app, doctree):
if not hasattr(env, 'todo_all_todos'):
env.todo_all_todos = []
for node in doctree.traverse(todo_node):
+ app.emit('todo-defined', node)
+
try:
targetnode = node.parent[node.parent.index(node) - 1]
if not isinstance(targetnode, nodes.target):
@@ -86,6 +88,9 @@ def process_todos(app, doctree):
'target': targetnode,
})
+ if env.config.todo_emit_warnings:
+ env.warn_node("TODO entry found: %s" % node[1].astext(), node)
+
class TodoList(Directive):
"""
@@ -187,8 +192,10 @@ def depart_todo_node(self, node):
def setup(app):
+ app.add_event('todo-defined')
app.add_config_value('todo_include_todos', False, 'html')
app.add_config_value('todo_link_only', False, 'html')
+ app.add_config_value('todo_emit_warnings', False, 'html')
app.add_node(todolist)
app.add_node(todo_node,
diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py
index a071c5533..276a137d5 100644
--- a/sphinx/ext/viewcode.py
+++ b/sphinx/ext/viewcode.py
@@ -46,6 +46,10 @@ def doctree_read(app, doctree):
env = app.builder.env
if not hasattr(env, '_viewcode_modules'):
env._viewcode_modules = {}
+ if app.builder.name == "singlehtml":
+ return
+ if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub:
+ return
def has_tag(modname, fullname, docname, refname):
entry = env._viewcode_modules.get(modname, None)
@@ -215,6 +219,7 @@ def collect_pages(app):
def setup(app):
app.add_config_value('viewcode_import', True, False)
+ app.add_config_value('viewcode_enable_epub', False, False)
app.connect('doctree-read', doctree_read)
app.connect('env-merge-info', env_merge_info)
app.connect('html-collect-pages', collect_pages)