summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS2
-rw-r--r--CHANGES25
-rw-r--r--doc/_templates/index.html5
-rw-r--r--doc/config.rst5
-rw-r--r--doc/ext/doctest.rst23
-rw-r--r--doc/ext/inheritance.rst5
-rw-r--r--doc/ext/napoleon.rst16
-rw-r--r--doc/theming.rst2
-rw-r--r--setup.py1
-rw-r--r--sphinx/__init__.py6
-rw-r--r--sphinx/apidoc.py5
-rw-r--r--sphinx/builders/applehelp.py5
-rw-r--r--sphinx/builders/changes.py31
-rw-r--r--sphinx/builders/devhelp.py5
-rw-r--r--sphinx/builders/epub.py20
-rw-r--r--sphinx/builders/epub3.py10
-rw-r--r--sphinx/builders/gettext.py6
-rw-r--r--sphinx/builders/html.py32
-rw-r--r--sphinx/builders/htmlhelp.py20
-rw-r--r--sphinx/builders/linkcheck.py111
-rw-r--r--sphinx/builders/qthelp.py10
-rw-r--r--sphinx/builders/texinfo.py5
-rw-r--r--sphinx/builders/text.py5
-rw-r--r--sphinx/builders/xml.py5
-rw-r--r--sphinx/config.py6
-rw-r--r--sphinx/directives/code.py14
-rw-r--r--sphinx/environment.py27
-rw-r--r--sphinx/ext/coverage.py20
-rw-r--r--sphinx/ext/doctest.py7
-rw-r--r--sphinx/ext/graphviz.py10
-rw-r--r--sphinx/ext/inheritance_diagram.py8
-rw-r--r--sphinx/ext/napoleon/__init__.py70
-rw-r--r--sphinx/ext/napoleon/docstring.py42
-rw-r--r--sphinx/jinja2glue.py4
-rw-r--r--sphinx/make_mode.py62
-rw-r--r--sphinx/pycode/pgen2/driver.py5
-rw-r--r--sphinx/quickstart.py73
-rw-r--r--sphinx/search/__init__.py16
-rw-r--r--sphinx/texinputs/sphinx.sty2
-rw-r--r--sphinx/util/png.py15
-rw-r--r--sphinx/util/pycompat.py5
-rw-r--r--sphinx/websupport/__init__.py12
-rw-r--r--test-reqs.txt1
-rw-r--r--tests/roots/test-ext-inheritance_diagram/index.rst3
-rw-r--r--tests/roots/test-search/conf.py3
-rw-r--r--tests/roots/test-search/index.rst8
-rw-r--r--tests/test_config.py2
-rw-r--r--tests/test_ext_inheritance_diagram.py25
-rw-r--r--tests/test_ext_napoleon_docstring.py27
-rw-r--r--tests/test_search.py38
50 files changed, 494 insertions, 371 deletions
diff --git a/AUTHORS b/AUTHORS
index e1e4e8a59..dc8e82482 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -29,7 +29,7 @@ Other contributors, listed alphabetically, are:
* Horst Gutmann -- internationalization support
* Martin Hans -- autodoc improvements
* Doug Hellmann -- graphviz improvements
-* Timotheus Kampik - JS enhancements, stop words language fix
+* Timotheus Kampik - JS theme & search enhancements
* Takeshi Komiya -- numref feature
* Dave Kuhlman -- original LaTeX writer
* Blaise Laflamme -- pyramid theme
diff --git a/CHANGES b/CHANGES
index 676bb2fda..757192a3a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,28 @@
+Release 1.5 (in development)
+============================
+
+Incompatible changes
+--------------------
+
+* LaTeX package fancybox is not longer a dependency of sphinx.sty
+* Use ``'locales'`` as a default value of `locale_dirs`
+
+Features added
+--------------
+
+* Add ``:caption:`` option for sphinx.ext.inheritance_diagram.
+* #894: Add ``lualatexpdf`` and ``xelatexpdf`` as a make target to build PDF using lualatex or xelatex
+* #2471: Add config variable for default doctest flags.
+* Convert linkcheck builder to requests for better encoding handling
+* #2463, #2516: Add keywords of "meta" directive to search index
+
+Bugs fixed
+----------
+
+Documentation
+-------------
+
+
Release 1.4.2 (in development)
==============================
diff --git a/doc/_templates/index.html b/doc/_templates/index.html
index 30373f06b..2053610f0 100644
--- a/doc/_templates/index.html
+++ b/doc/_templates/index.html
@@ -13,9 +13,8 @@
Sphinx is a tool that makes it easy to create intelligent and beautiful
documentation, written by Georg Brandl and licensed under the BSD license.{%endtrans%}</p>
<p>{%trans%}It was originally created for <a href="https://docs.python.org/">the
- new Python documentation</a>, and it has excellent facilities for the
- documentation of Python projects, but C/C++ is already supported as well,
- and it is planned to add special support for other languages as well. Of
+ Python documentation</a>, and it has excellent facilities for the
+ documentation of software projects in a range of languages. Of
course, this site is also created from reStructuredText sources using
Sphinx! The following features should be highlighted:{%endtrans%}
</p>
diff --git a/doc/config.rst b/doc/config.rst
index 47e6da8ef..d4c2784aa 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -516,7 +516,10 @@ documentation on :ref:`intl` for details.
:file:`./locale/{language}/LC_MESSAGES/sphinx.mo`. The text domain of
individual documents depends on :confval:`gettext_compact`.
- The default is ``[]``.
+ The default is ``['locales']``.
+
+ .. versionchanged:: 1.5
+ Use ``locales`` directory as a default value
.. confval:: gettext_compact
diff --git a/doc/ext/doctest.rst b/doc/ext/doctest.rst
index 2d06b69e6..3d4f66a9a 100644
--- a/doc/ext/doctest.rst
+++ b/doc/ext/doctest.rst
@@ -59,12 +59,9 @@ a comma-separated list of group names.
.. rst:directive:: .. doctest:: [group]
A doctest-style code block. You can use standard :mod:`doctest` flags for
- controlling how actual output is compared with what you give as output. By
- default, these options are enabled: ``ELLIPSIS`` (allowing you to put
- ellipses in the expected output that match anything in the actual output),
- ``IGNORE_EXCEPTION_DETAIL`` (not comparing tracebacks),
- ``DONT_ACCEPT_TRUE_FOR_1`` (by default, doctest accepts "True" in the output
- where "1" is given -- this is a relic of pre-Python 2.2 times).
+ controlling how actual output is compared with what you give as output. The
+ default set of flags is specified by the :confval:`doctest_default_flags`
+ configuration variable.
This directive supports two options:
@@ -182,6 +179,20 @@ Configuration
The doctest extension uses the following configuration values:
+.. confval:: doctest_default_flags
+
+ By default, these options are enabled:
+
+ - ``ELLIPSIS``, allowing you to put ellipses in the expected output that
+ match anything in the actual output;
+ - ``IGNORE_EXCEPTION_DETAIL``, causing everything following the leftmost
+ colon and any module information in the exception name to be ignored;
+ - ``DONT_ACCEPT_TRUE_FOR_1``, rejecting "True" in the output where "1" is
+ given -- the default behavior of accepting this substitution is a relic of
+ pre-Python 2.2 times.
+
+ .. versionadded:: 1.5
+
.. confval:: doctest_path
A list of directories that will be added to :data:`sys.path` when the doctest
diff --git a/doc/ext/inheritance.rst b/doc/ext/inheritance.rst
index 5e0a76fcc..dd8d5aa99 100644
--- a/doc/ext/inheritance.rst
+++ b/doc/ext/inheritance.rst
@@ -33,10 +33,15 @@ It adds this directive:
It also supports a ``private-bases`` flag option; if given, private base
classes (those whose name starts with ``_``) will be included.
+ You can use ``caption`` option to give a caption to the diagram.
+
.. versionchanged:: 1.1
Added ``private-bases`` option; previously, all bases were always
included.
+ .. versionchanged:: 1.5
+ Added ``caption`` option
+
New config values are:
diff --git a/doc/ext/napoleon.rst b/doc/ext/napoleon.rst
index 304d8ac22..2f2fb4376 100644
--- a/doc/ext/napoleon.rst
+++ b/doc/ext/napoleon.rst
@@ -371,6 +371,22 @@ enabled in `conf.py`::
* **arg2** (*int, optional*) --
Description of `arg2`, defaults to 0
+.. confval:: napoleon_use_keyword
+
+ True to use a ``:keyword:`` role for each function keyword argument.
+ False to use a single ``:keyword arguments:`` role for all the
+ keywords.
+ *Defaults to True.*
+
+ 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)
+
+ .. seealso::
+
+ :attr:`napoleon_use_param`
+
.. confval:: napoleon_use_rtype
True to use the ``:rtype:`` role for the return type. False to output
diff --git a/doc/theming.rst b/doc/theming.rst
index f124542c7..df0755f1f 100644
--- a/doc/theming.rst
+++ b/doc/theming.rst
@@ -273,7 +273,7 @@ These themes are:
.. versionchanged:: 1.3
The 'default' theme has been renamed to 'classic'. 'default' is still
- available, however it will emit notice a recommendation that using new
+ available, however it will emit a notice that it is an alias for the new
'alabaster' theme.
Creating themes
diff --git a/setup.py b/setup.py
index a35c7690b..3d792b2dc 100644
--- a/setup.py
+++ b/setup.py
@@ -49,6 +49,7 @@ requires = [
'babel>=1.3,!=2.0',
'alabaster>=0.7,<0.8',
'imagesize',
+ 'requests',
]
extras_require = {
# Environment Marker works for wheel 0.24 or later
diff --git a/sphinx/__init__.py b/sphinx/__init__.py
index 4e5e97a04..e28316f8d 100644
--- a/sphinx/__init__.py
+++ b/sphinx/__init__.py
@@ -15,13 +15,13 @@
import sys
from os import path
-__version__ = '1.4.1+'
-__released__ = '1.4.1' # used when Sphinx builds its own docs
+__version__ = '1.5a0'
+__released__ = '1.5a0' # used when Sphinx builds its own docs
# version info for better programmatic use
# possible values for 3rd element: 'alpha', 'beta', 'rc', 'final'
# 'final' has 0 as the last element
-version_info = (1, 4, 2, 'beta', 1)
+version_info = (1, 5, 0, 'alpha', 0)
package_dir = path.abspath(path.dirname(__file__))
diff --git a/sphinx/apidoc.py b/sphinx/apidoc.py
index 58724fd5a..c0639acdf 100644
--- a/sphinx/apidoc.py
+++ b/sphinx/apidoc.py
@@ -62,11 +62,8 @@ def write_file(name, text, opts):
print('File %s already exists, skipping.' % fname)
else:
print('Creating file %s.' % fname)
- f = open(fname, 'w')
- try:
+ with open(fname, 'w') as f:
f.write(text)
- finally:
- f.close()
def format_heading(level, text):
diff --git a/sphinx/builders/applehelp.py b/sphinx/builders/applehelp.py
index 53a0c99ad..d3ad861dc 100644
--- a/sphinx/builders/applehelp.py
+++ b/sphinx/builders/applehelp.py
@@ -178,14 +178,11 @@ class AppleHelpBuilder(StandaloneHTMLBuilder):
# Build the access page
self.info(bold('building access page...'), nonl=True)
- f = codecs.open(path.join(language_dir, '_access.html'), 'w')
- try:
+ with codecs.open(path.join(language_dir, '_access.html'), 'w') as f:
f.write(access_page_template % {
'toc': htmlescape(toc, quote=True),
'title': htmlescape(self.config.applehelp_title)
})
- finally:
- f.close()
self.info('done')
# Generate the help index
diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py
index c077b7dd2..ed9edc403 100644
--- a/sphinx/builders/changes.py
+++ b/sphinx/builders/changes.py
@@ -101,16 +101,10 @@ class ChangesBuilder(Builder):
'show_copyright': self.config.html_show_copyright,
'show_sphinx': self.config.html_show_sphinx,
}
- f = codecs.open(path.join(self.outdir, 'index.html'), 'w', 'utf8')
- try:
+ with codecs.open(path.join(self.outdir, 'index.html'), 'w', 'utf8') as f:
f.write(self.templates.render('changes/frameset.html', ctx))
- finally:
- f.close()
- f = codecs.open(path.join(self.outdir, 'changes.html'), 'w', 'utf8')
- try:
+ with codecs.open(path.join(self.outdir, 'changes.html'), 'w', 'utf8') as f:
f.write(self.templates.render('changes/versionchanges.html', ctx))
- finally:
- f.close()
hltext = ['.. versionadded:: %s' % version,
'.. versionchanged:: %s' % version,
@@ -126,27 +120,22 @@ class ChangesBuilder(Builder):
self.info(bold('copying source files...'))
for docname in self.env.all_docs:
- f = codecs.open(self.env.doc2path(docname), 'r',
- self.env.config.source_encoding)
- try:
- lines = f.readlines()
- except UnicodeDecodeError:
- self.warn('could not read %r for changelog creation' % docname)
- continue
- finally:
- f.close()
+ with codecs.open(self.env.doc2path(docname), 'r',
+ self.env.config.source_encoding) as f:
+ try:
+ lines = f.readlines()
+ except UnicodeDecodeError:
+ self.warn('could not read %r for changelog creation' % docname)
+ continue
targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html'
ensuredir(path.dirname(targetfn))
- f = codecs.open(targetfn, 'w', 'utf-8')
- try:
+ with codecs.open(targetfn, 'w', 'utf-8') as f:
text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines))
ctx = {
'filename': self.env.doc2path(docname, None),
'text': text
}
f.write(self.templates.render('changes/rstsource.html', ctx))
- finally:
- f.close()
themectx = dict(('theme_' + key, val) for (key, val) in
iteritems(self.theme.get_options({})))
copy_static_entry(path.join(package_dir, 'themes', 'default',
diff --git a/sphinx/builders/devhelp.py b/sphinx/builders/devhelp.py
index 62e2c9843..eb3e997d8 100644
--- a/sphinx/builders/devhelp.py
+++ b/sphinx/builders/devhelp.py
@@ -127,8 +127,5 @@ class DevhelpBuilder(StandaloneHTMLBuilder):
write_index(title, refs, subitems)
# Dump the XML file
- f = comp_open(path.join(outdir, outname + '.devhelp'), 'w')
- try:
+ with comp_open(path.join(outdir, outname + '.devhelp'), 'w') as f:
tree.write(f, 'utf-8')
- finally:
- f.close()
diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py
index cc839d757..349574ae0 100644
--- a/sphinx/builders/epub.py
+++ b/sphinx/builders/epub.py
@@ -496,11 +496,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
def build_mimetype(self, outdir, outname):
"""Write the metainfo file mimetype."""
self.info('writing %s file...' % outname)
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(self.mimetype_template)
- finally:
- f.close()
def build_container(self, outdir, outname):
"""Write the metainfo file META-INF/cointainer.xml."""
@@ -511,11 +508,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
except OSError as err:
if err.errno != EEXIST:
raise
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(self.container_template)
- finally:
- f.close()
def content_metadata(self, files, spine, guide):
"""Create a dictionary with all metadata for the content.opf
@@ -652,12 +646,9 @@ class EpubBuilder(StandaloneHTMLBuilder):
guide = '\n'.join(guide)
# write the project file
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(content_tmpl %
self.content_metadata(projectfiles, spine, guide))
- finally:
- f.close()
def new_navpoint(self, node, level, incr=True):
"""Create a new entry in the toc from the node at given level."""
@@ -749,11 +740,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
navpoints = self.build_navpoints(refnodes)
level = max(item['level'] for item in self.refnodes)
level = min(level, self.config.epub_tocdepth)
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(self.toc_template % self.toc_metadata(level, navpoints))
- finally:
- f.close()
def build_epub(self, outdir, outname):
"""Write the epub file.
diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py
index 0fd347735..b243486f6 100644
--- a/sphinx/builders/epub3.py
+++ b/sphinx/builders/epub3.py
@@ -203,11 +203,9 @@ class Epub3Builder(EpubBuilder):
# 'includehidden'
refnodes = self.refnodes
navlist = self.build_navlist(refnodes)
- f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f:
f.write(self.navigation_doc_template %
self.navigation_doc_metadata(navlist))
- finally:
- f.close()
- # Add nav.xhtml to epub file
- self.files.append(outname)
+
+ # Add nav.xhtml to epub file
+ self.files.append(outname)
diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py
index 1c4789392..c8f4dab4f 100644
--- a/sphinx/builders/gettext.py
+++ b/sphinx/builders/gettext.py
@@ -211,8 +211,7 @@ class MessageCatalogBuilder(I18nBuilder):
ensuredir(path.join(self.outdir, path.dirname(textdomain)))
pofn = path.join(self.outdir, textdomain + '.pot')
- pofile = open(pofn, 'w', encoding='utf-8')
- try:
+ with open(pofn, 'w', encoding='utf-8') as pofile:
pofile.write(POHEADER % data)
for message in catalog.messages:
@@ -234,6 +233,3 @@ class MessageCatalogBuilder(I18nBuilder):
replace('"', r'\"'). \
replace('\n', '\\n"\n"')
pofile.write('msgid "%s"\nmsgstr ""\n\n' % message)
-
- finally:
- pofile.close()
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index f541daa1a..63ccdd66c 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -175,8 +175,7 @@ class StandaloneHTMLBuilder(Builder):
self.tags_hash = get_stable_hash(sorted(self.tags))
old_config_hash = old_tags_hash = ''
try:
- fp = open(path.join(self.outdir, '.buildinfo'))
- try:
+ with open(path.join(self.outdir, '.buildinfo')) as fp:
version = fp.readline()
if version.rstrip() != '# Sphinx build info version 1':
raise ValueError
@@ -187,8 +186,6 @@ class StandaloneHTMLBuilder(Builder):
tag, old_tags_hash = fp.readline().strip().split(': ')
if tag != 'tags':
raise ValueError
- finally:
- fp.close()
except ValueError:
self.warn('unsupported build info format in %r, building all' %
path.join(self.outdir, '.buildinfo'))
@@ -657,15 +654,12 @@ class StandaloneHTMLBuilder(Builder):
def write_buildinfo(self):
# write build info file
- fp = open(path.join(self.outdir, '.buildinfo'), 'w')
- try:
+ with open(path.join(self.outdir, '.buildinfo'), 'w') as fp:
fp.write('# Sphinx build info version 1\n'
'# This file hashes the configuration used when building'
' these files. When it is not found, a full rebuild will'
' be done.\nconfig: %s\ntags: %s\n' %
(self.config_hash, self.tags_hash))
- finally:
- fp.close()
def cleanup(self):
# clean up theme stuff
@@ -705,10 +699,8 @@ class StandaloneHTMLBuilder(Builder):
f = codecs.open(searchindexfn, 'r', encoding='utf-8')
else:
f = open(searchindexfn, 'rb')
- try:
+ with f:
self.indexer.load(f, self.indexer_format)
- finally:
- f.close()
except (IOError, OSError, ValueError):
if keep:
self.warn('search index couldn\'t be loaded, but not all '
@@ -812,11 +804,8 @@ class StandaloneHTMLBuilder(Builder):
# outfilename's path is in general different from self.outdir
ensuredir(path.dirname(outfilename))
try:
- f = codecs.open(outfilename, 'w', encoding, 'xmlcharrefreplace')
- try:
+ with codecs.open(outfilename, 'w', encoding, 'xmlcharrefreplace') as f:
f.write(output)
- finally:
- f.close()
except (IOError, OSError) as err:
self.warn("error writing file %s: %s" % (outfilename, err))
if self.copysource and ctx.get('sourcename'):
@@ -833,8 +822,7 @@ class StandaloneHTMLBuilder(Builder):
def dump_inventory(self):
self.info(bold('dumping object inventory... '), nonl=True)
- f = open(path.join(self.outdir, INVENTORY_FILENAME), 'wb')
- try:
+ with open(path.join(self.outdir, INVENTORY_FILENAME), 'wb') as f:
f.write((u'# Sphinx inventory version 2\n'
u'# Project: %s\n'
u'# Version: %s\n'
@@ -856,8 +844,6 @@ class StandaloneHTMLBuilder(Builder):
(u'%s %s:%s %s %s %s\n' % (name, domainname, type,
prio, uri, dispname)).encode('utf-8')))
f.write(compressor.flush())
- finally:
- f.close()
self.info('done')
def dump_search_index(self):
@@ -872,10 +858,8 @@ class StandaloneHTMLBuilder(Builder):
f = codecs.open(searchindexfn + '.tmp', 'w', encoding='utf-8')
else:
f = open(searchindexfn + '.tmp', 'wb')
- try:
+ with f:
self.indexer.dump(f, self.indexer_format)
- finally:
- f.close()
movefile(searchindexfn + '.tmp', searchindexfn)
self.info('done')
@@ -1086,10 +1070,8 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
f = codecs.open(filename, 'w', encoding='utf-8')
else:
f = open(filename, 'wb')
- try:
+ with f:
self.implementation.dump(context, f, *self.additional_dump_args)
- finally:
- f.close()
def handle_page(self, pagename, ctx, templatename='page.html',
outfilename=None, event_arg=None):
diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py
index f4003c4c9..b1a5d7dda 100644
--- a/sphinx/builders/htmlhelp.py
+++ b/sphinx/builders/htmlhelp.py
@@ -198,16 +198,12 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
def build_hhx(self, outdir, outname):
self.info('dumping stopword list...')
- f = self.open_file(outdir, outname+'.stp')
- try:
+ with self.open_file(outdir, outname+'.stp') as f:
for word in sorted(stopwords):
print(word, file=f)
- finally:
- f.close()
self.info('writing project file...')
- f = self.open_file(outdir, outname+'.hhp')
- try:
+ with self.open_file(outdir, outname+'.hhp') as f:
f.write(project_template % {'outname': outname,
'title': self.config.html_title,
'version': self.config.version,
@@ -223,12 +219,9 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
fn.endswith('.html'):
print(path.join(root, fn)[olen:].replace(os.sep, '\\'),
file=f)
- finally:
- f.close()
self.info('writing TOC file...')
- f = self.open_file(outdir, outname+'.hhc')
- try:
+ with self.open_file(outdir, outname+'.hhc') as f:
f.write(contents_header)
# special books
f.write('<LI> ' + object_sitemap % (self.config.html_short_title,
@@ -266,13 +259,10 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
for node in tocdoc.traverse(istoctree):
write_toc(node)
f.write(contents_footer)
- finally:
- f.close()
self.info('writing index file...')
index = self.env.create_index(self)
- f = self.open_file(outdir, outname+'.hhk')
- try:
+ with self.open_file(outdir, outname+'.hhk') as f:
f.write('<UL>\n')
def write_index(title, refs, subitems):
@@ -302,5 +292,3 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
for title, (refs, subitems, key_) in group:
write_index(title, refs, subitems)
f.write('</UL>\n')
- finally:
- f.close()
diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py
index 0533f1c50..700ea2880 100644
--- a/sphinx/builders/linkcheck.py
+++ b/sphinx/builders/linkcheck.py
@@ -14,11 +14,13 @@ import socket
import codecs
import threading
from os import path
+import warnings
+import pkg_resources
+import requests
+from requests.exceptions import HTTPError
from six.moves import queue
-from six.moves.urllib.request import build_opener, Request, HTTPRedirectHandler
from six.moves.urllib.parse import unquote
-from six.moves.urllib.error import HTTPError
from six.moves.html_parser import HTMLParser
from docutils import nodes
@@ -36,28 +38,25 @@ from sphinx.builders import Builder
from sphinx.util import encode_uri
from sphinx.util.console import purple, red, darkgreen, darkgray, \
darkred, turquoise
-from sphinx.util.pycompat import TextIOWrapper
-
-class RedirectHandler(HTTPRedirectHandler):
- """A RedirectHandler that records the redirect code we got."""
-
- def redirect_request(self, req, fp, code, msg, headers, newurl):
- new_req = HTTPRedirectHandler.redirect_request(self, req, fp, code,
- msg, headers, newurl)
- req.redirect_code = code
- return new_req
-
-# create an opener that will simulate a browser user-agent
-opener = build_opener(RedirectHandler)
-opener.addheaders = [('User-agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) '
- 'Gecko/20100101 Firefox/25.0')]
-
-
-class HeadRequest(Request):
- """Subclass of urllib2.Request that sends a HEAD request."""
- def get_method(self):
- return 'HEAD'
+try:
+ pkg_resources.require(['requests[security]'])
+except pkg_resources.DistributionNotFound:
+ import ssl
+ if not getattr(ssl, 'HAS_SNI', False):
+ # don't complain on each url processed about the SSL issue
+ requests.packages.urllib3.disable_warnings(
+ requests.packages.urllib3.exceptions.InsecurePlatformWarning)
+ warnings.warn(
+ 'Some links may return broken results due to being unable to '
+ 'check the Server Name Indication (SNI) in the returned SSL cert '
+ 'against the hostname in the url requested. Recommended to '
+ 'install "requests[security]" as a dependency or upgrade to '
+ 'a python version with SNI support (Python 3 and Python 2.7.9+).'
+ )
+
+requests_user_agent = [('User-agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) '
+ 'Gecko/20100101 Firefox/25.0')]
class AnchorCheckParser(HTMLParser):
@@ -75,18 +74,18 @@ class AnchorCheckParser(HTMLParser):
self.found = True
-def check_anchor(f, anchor):
- """Reads HTML data from a filelike object 'f' searching for *anchor*.
+def check_anchor(response, anchor):
+ """Reads HTML data from a response object `response` searching for `anchor`.
Returns True if anchor was found, False otherwise.
"""
parser = AnchorCheckParser(anchor)
try:
- # Read file in chunks of 8192 bytes. If we find a matching anchor, we
- # break the loop early in hopes not to have to download the whole thing.
- chunk = f.read(8192)
- while chunk and not parser.found:
+ # Read file in chunks. If we find a matching anchor, we break
+ # the loop early in hopes not to have to download the whole thing.
+ for chunk in response.iter_content():
parser.feed(chunk)
- chunk = f.read(8192)
+ if parser.found:
+ break
parser.close()
except HTMLParseError:
# HTMLParser is usually pretty good with sloppy HTML, but it tends to
@@ -95,17 +94,6 @@ def check_anchor(f, anchor):
return parser.found
-def get_content_charset(f):
- content_type = f.headers.get('content-type')
- if content_type:
- params = (p.strip() for p in content_type.split(';')[1:])
- for param in params:
- if param.startswith('charset='):
- return param[8:]
-
- return None
-
-
class CheckExternalLinksBuilder(Builder):
"""
Checks for broken external links.
@@ -122,6 +110,9 @@ class CheckExternalLinksBuilder(Builder):
# create output file
open(path.join(self.outdir, 'output.txt'), 'w').close()
+ self.session = requests.Session()
+ self.session.headers = dict(requests_user_agent)
+
# create queues and worker threads
self.wqueue = queue.Queue()
self.rqueue = queue.Queue()
@@ -137,6 +128,8 @@ class CheckExternalLinksBuilder(Builder):
if self.app.config.linkcheck_timeout:
kwargs['timeout'] = self.app.config.linkcheck_timeout
+ kwargs['allow_redirects'] = True
+
def check_uri():
# split off anchor
if '#' in uri:
@@ -157,16 +150,8 @@ class CheckExternalLinksBuilder(Builder):
# Read the whole document and see if #anchor exists
# (Anchors starting with ! are ignored since they are
# commonly used for dynamic pages)
- req = Request(req_url)
- f = opener.open(req, **kwargs)
- encoding = 'utf-8'
- if hasattr(f.headers, 'get_content_charset'):
- encoding = f.headers.get_content_charset() or encoding
- else:
- encoding = get_content_charset(f) or encoding
- found = check_anchor(TextIOWrapper(f, encoding),
- unquote(anchor))
- f.close()
+ response = requests.get(req_url, stream=True, **kwargs)
+ found = check_anchor(response, unquote(anchor))
if not found:
raise Exception("Anchor '%s' not found" % anchor)
@@ -174,32 +159,32 @@ class CheckExternalLinksBuilder(Builder):
try:
# try a HEAD request, which should be easier on
# the server and the network
- req = HeadRequest(req_url)
- f = opener.open(req, **kwargs)
- f.close()
+ response = requests.head(req_url, **kwargs)
+ response.raise_for_status()
except HTTPError as err:
- if err.code != 405:
+ if err.response.status_code not in (403, 405):
raise
# retry with GET if that fails, some servers
- # don't like HEAD requests and reply with 405
- req = Request(req_url)
- f = opener.open(req, **kwargs)
- f.close()
+ # don't like HEAD requests and reply with 403 or 405
+ response = requests.get(req_url, stream=True, **kwargs)
+ response.raise_for_status()
except HTTPError as err:
- if err.code == 401:
+ if err.response.status_code == 401:
# We'll take "Unauthorized" as working.
return 'working', ' - unauthorized', 0
else:
return 'broken', str(err), 0
except Exception as err:
return 'broken', str(err), 0
- if f.url.rstrip('/') == req_url.rstrip('/'):
+ if response.url.rstrip('/') == req_url.rstrip('/'):
return 'working', '', 0
else:
- new_url = f.url
+ new_url = response.url
if anchor:
new_url += '#' + anchor
- code = getattr(req, 'redirect_code', 0)
+ # history contains any redirects, get last
+ if response.history:
+ code = response.history[-1].status_code
return 'redirected', new_url, code
def check():
diff --git a/sphinx/builders/qthelp.py b/sphinx/builders/qthelp.py
index 0a7e85c92..4139c92c6 100644
--- a/sphinx/builders/qthelp.py
+++ b/sphinx/builders/qthelp.py
@@ -179,8 +179,7 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
nspace = nspace.lower()
# write the project file
- f = codecs.open(path.join(outdir, outname+'.qhp'), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname+'.qhp'), 'w', 'utf-8') as f:
f.write(project_template % {
'outname': htmlescape(outname),
'title': htmlescape(self.config.html_title),
@@ -191,23 +190,18 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
'sections': sections,
'keywords': keywords,
'files': projectfiles})
- finally:
- f.close()
homepage = 'qthelp://' + posixpath.join(
nspace, 'doc', self.get_target_uri(self.config.master_doc))
startpage = 'qthelp://' + posixpath.join(nspace, 'doc', 'index.html')
self.info('writing collection project file...')
- f = codecs.open(path.join(outdir, outname+'.qhcp'), 'w', 'utf-8')
- try:
+ with codecs.open(path.join(outdir, outname+'.qhcp'), 'w', 'utf-8') as f:
f.write(collection_template % {
'outname': htmlescape(outname),
'title': htmlescape(self.config.html_short_title),
'homepage': htmlescape(homepage),
'startpage': htmlescape(startpage)})
- finally:
- f.close()
def isdocnode(self, node):
if not isinstance(node, nodes.list_item):
diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py
index dec278c86..8c4bd2419 100644
--- a/sphinx/builders/texinfo.py
+++ b/sphinx/builders/texinfo.py
@@ -220,11 +220,8 @@ class TexinfoBuilder(Builder):
fn = path.join(self.outdir, 'Makefile')
self.info(fn, nonl=1)
try:
- mkfile = open(fn, 'w')
- try:
+ with open(fn, 'w') as mkfile:
mkfile.write(TEXINFO_MAKEFILE)
- finally:
- mkfile.close()
except (IOError, OSError) as err:
self.warn("error writing file %s: %s" % (fn, err))
self.info(' done')
diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py
index 85da4a1a2..202ec20db 100644
--- a/sphinx/builders/text.py
+++ b/sphinx/builders/text.py
@@ -60,11 +60,8 @@ class TextBuilder(Builder):
outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix)
ensuredir(path.dirname(outfilename))
try:
- f = codecs.open(outfilename, 'w', 'utf-8')
- try:
+ with codecs.open(outfilename, 'w', 'utf-8') as f:
f.write(self.writer.output)
- finally:
- f.close()
except (IOError, OSError) as err:
self.warn("error writing file %s: %s" % (outfilename, err))
diff --git a/sphinx/builders/xml.py b/sphinx/builders/xml.py
index 91cb273f5..589e8a63a 100644
--- a/sphinx/builders/xml.py
+++ b/sphinx/builders/xml.py
@@ -77,11 +77,8 @@ class XMLBuilder(Builder):
outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix)
ensuredir(path.dirname(outfilename))
try:
- f = codecs.open(outfilename, 'w', 'utf-8')
- try:
+ with codecs.open(outfilename, 'w', 'utf-8') as f:
f.write(self.writer.output)
- finally:
- f.close()
except (IOError, OSError) as err:
self.warn("error writing file %s: %s" % (outfilename, err))
diff --git a/sphinx/config.py b/sphinx/config.py
index 9cbf655f8..ef57334fe 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -59,7 +59,7 @@ class Config(object):
today_fmt = (None, 'env', string_classes),
language = (None, 'env', string_classes),
- locale_dirs = ([], 'env'),
+ locale_dirs = (['locales'], 'env'),
figure_language_filename = (u'{root}.{language}{ext}', 'env', [str]),
master_doc = ('contents', 'env'),
@@ -86,8 +86,8 @@ class Config(object):
primary_domain = ('py', 'env', [NoneType]),
needs_sphinx = (None, None, string_classes),
needs_extensions = ({}, None),
- nitpicky = (False, 'env'),
- nitpick_ignore = ([], 'html'),
+ nitpicky = (False, None),
+ nitpick_ignore = ([], None),
numfig = (False, 'env'),
numfig_secnum_depth = (1, 'env'),
numfig_format = ({'figure': l_('Fig. %s'),
diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py
index fac8f6419..de804f988 100644
--- a/sphinx/directives/code.py
+++ b/sphinx/directives/code.py
@@ -173,13 +173,12 @@ class LiteralInclude(Directive):
}
def read_with_encoding(self, filename, document, codec_info, encoding):
- f = None
try:
- f = codecs.StreamReaderWriter(open(filename, 'rb'), codec_info[2],
- codec_info[3], 'strict')
- lines = f.readlines()
- lines = dedent_lines(lines, self.options.get('dedent'))
- return lines
+ with codecs.StreamReaderWriter(open(filename, 'rb'), codec_info[2],
+ codec_info[3], 'strict') as f:
+ lines = f.readlines()
+ lines = dedent_lines(lines, self.options.get('dedent'))
+ return lines
except (IOError, OSError):
return [document.reporter.warning(
'Include file %r not found or reading it failed' % filename,
@@ -189,9 +188,6 @@ class LiteralInclude(Directive):
'Encoding %r used for reading included file %r seems to '
'be wrong, try giving an :encoding: option' %
(encoding, filename))]
- finally:
- if f is not None:
- f.close()
def run(self):
document = self.state.document
diff --git a/sphinx/environment.py b/sphinx/environment.py
index d5848a429..92064f911 100644
--- a/sphinx/environment.py
+++ b/sphinx/environment.py
@@ -103,11 +103,8 @@ class BuildEnvironment:
@staticmethod
def frompickle(srcdir, config, filename):
- picklefile = open(filename, 'rb')
- try:
+ with open(filename, 'rb') as picklefile:
env = pickle.load(picklefile)
- finally:
- picklefile.close()
if env.version != ENV_VERSION:
raise IOError('build environment version not current')
if env.srcdir != srcdir:
@@ -123,7 +120,6 @@ class BuildEnvironment:
del self.config.values
domains = self.domains
del self.domains
- picklefile = open(filename, 'wb')
# remove potentially pickling-problematic values from config
for key, val in list(vars(self.config).items()):
if key.startswith('_') or \
@@ -131,10 +127,8 @@ class BuildEnvironment:
isinstance(val, types.FunctionType) or \
isinstance(val, class_types):
del self.config[key]
- try:
+ with open(filename, 'wb') as picklefile:
pickle.dump(self, picklefile, pickle.HIGHEST_PROTOCOL)
- finally:
- picklefile.close()
# reset attributes
self.domains = domains
self.config.values = values
@@ -751,12 +745,9 @@ class BuildEnvironment:
if self.versioning_compare:
# get old doctree
try:
- f = open(self.doc2path(docname,
- self.doctreedir, '.doctree'), 'rb')
- try:
+ with open(self.doc2path(docname,
+ self.doctreedir, '.doctree'), 'rb') as f:
old_doctree = pickle.load(f)
- finally:
- f.close()
except EnvironmentError:
pass
@@ -786,11 +777,8 @@ class BuildEnvironment:
doctree_filename = self.doc2path(docname, self.doctreedir,
'.doctree')
ensuredir(path.dirname(doctree_filename))
- f = open(doctree_filename, 'wb')
- try:
+ with open(doctree_filename, 'wb') as f:
pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL)
- finally:
- f.close()
# utilities to use while reading a document
@@ -1226,11 +1214,8 @@ class BuildEnvironment:
def get_doctree(self, docname):
"""Read the doctree for a file from the pickle and return it."""
doctree_filename = self.doc2path(docname, self.doctreedir, '.doctree')
- f = open(doctree_filename, 'rb')
- try:
+ with open(doctree_filename, 'rb') as f:
doctree = pickle.load(f)
- finally:
- f.close()
doctree.settings.env = self
doctree.reporter = Reporter(self.doc2path(docname), 2, 5,
stream=WarningStream(self._warnfunc))
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..527cb3704 100644
--- a/sphinx/ext/graphviz.py
+++ b/sphinx/ext/graphviz.py
@@ -82,11 +82,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 '
@@ -239,11 +236,8 @@ 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:
+ 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' %
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/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py
index 85c8acec8..9dedc15d7 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
@@ -55,6 +57,21 @@ class Config(object):
napoleon_numpy_docstring : bool, defaults to True
True to parse `NumPy style`_ docstrings. False to disable support
for NumPy style docstrings.
+ napoleon_include_init_with_doc : bool, defaults to False
+ True to include init methods (i.e. ``__init___``) with
+ docstrings in the documentation. False to fall back to Sphinx's
+ default behavior.
+
+ **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 : 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.
@@ -184,6 +201,20 @@ class Config(object):
* **arg2** (*int, optional*) --
Description of `arg2`, defaults to 0
+ napoleon_use_keyword : 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 : 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 +239,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 +248,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 +284,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 +294,23 @@ def setup(app):
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
+def _patch_python_domain():
+ import sphinx.domains.python
+ from sphinx.domains.python import PyTypedField
+ 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 +363,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 +403,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 +436,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..c12b75e6d 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -527,7 +527,15 @@ 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._generate_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,24 +560,26 @@ 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._generate_docutils_params(fields)
else:
return self._format_fields('Parameters', fields)
+ def _generate_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):
+ if self._is_list(_desc):
+ _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 _parse_raises_section(self, section):
fields = self._consume_fields(parse_type=False, prefer_type=True)
field_type = ':raises:'
diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py
index f3ce87a8c..c5e0e29e5 100644
--- a/sphinx/jinja2glue.py
+++ b/sphinx/jinja2glue.py
@@ -70,10 +70,8 @@ class SphinxFileSystemLoader(FileSystemLoader):
f = open_if_exists(filename)
if f is None:
continue
- try:
+ with f:
contents = f.read().decode(self.encoding)
- finally:
- f.close()
mtime = path.getmtime(filename)
diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py
index 67ba9e1e1..9a17895bd 100644
--- a/sphinx/make_mode.py
+++ b/sphinx/make_mode.py
@@ -30,30 +30,32 @@ proj_name = os.getenv('SPHINXPROJ', '<project>')
BUILDERS = [
- ("", "html", "to make standalone HTML files"),
- ("", "dirhtml", "to make HTML files named index.html in directories"),
- ("", "singlehtml", "to make a single large HTML file"),
- ("", "pickle", "to make pickle files"),
- ("", "json", "to make JSON files"),
- ("", "htmlhelp", "to make HTML files and a HTML help project"),
- ("", "qthelp", "to make HTML files and a qthelp project"),
- ("", "devhelp", "to make HTML files and a Devhelp project"),
- ("", "epub", "to make an epub"),
- ("", "latex", "to make LaTeX files, you can set PAPER=a4 or PAPER=letter"),
- ("posix", "latexpdf", "to make LaTeX files and run them through pdflatex"),
- ("posix", "latexpdfja", "to make LaTeX files and run them through platex/dvipdfmx"),
- ("", "text", "to make text files"),
- ("", "man", "to make manual pages"),
- ("", "texinfo", "to make Texinfo files"),
- ("posix", "info", "to make Texinfo files and run them through makeinfo"),
- ("", "gettext", "to make PO message catalogs"),
- ("", "changes", "to make an overview of all changed/added/deprecated items"),
- ("", "xml", "to make Docutils-native XML files"),
- ("", "pseudoxml", "to make pseudoxml-XML files for display purposes"),
- ("", "linkcheck", "to check all external links for integrity"),
- ("", "doctest", "to run all doctests embedded in the documentation "
- "(if enabled)"),
- ("", "coverage", "to run coverage check of the documentation (if enabled)"),
+ ("", "html", "to make standalone HTML files"),
+ ("", "dirhtml", "to make HTML files named index.html in directories"),
+ ("", "singlehtml", "to make a single large HTML file"),
+ ("", "pickle", "to make pickle files"),
+ ("", "json", "to make JSON files"),
+ ("", "htmlhelp", "to make HTML files and a HTML help project"),
+ ("", "qthelp", "to make HTML files and a qthelp project"),
+ ("", "devhelp", "to make HTML files and a Devhelp project"),
+ ("", "epub", "to make an epub"),
+ ("", "latex", "to make LaTeX files, you can set PAPER=a4 or PAPER=letter"),
+ ("posix", "latexpdf", "to make LaTeX files and run them through pdflatex"),
+ ("posix", "latexpdfja", "to make LaTeX files and run them through platex/dvipdfmx"),
+ ("posix", "lualatexpdf", "to make LaTeX files and run them through lualatex"),
+ ("posix", "xelatexpdf", "to make LaTeX files and run them through xelatex"),
+ ("", "text", "to make text files"),
+ ("", "man", "to make manual pages"),
+ ("", "texinfo", "to make Texinfo files"),
+ ("posix", "info", "to make Texinfo files and run them through makeinfo"),
+ ("", "gettext", "to make PO message catalogs"),
+ ("", "changes", "to make an overview of all changed/added/deprecated items"),
+ ("", "xml", "to make Docutils-native XML files"),
+ ("", "pseudoxml", "to make pseudoxml-XML files for display purposes"),
+ ("", "linkcheck", "to check all external links for integrity"),
+ ("", "doctest", "to run all doctests embedded in the documentation "
+ "(if enabled)"),
+ ("", "coverage", "to run coverage check of the documentation (if enabled)"),
]
@@ -171,6 +173,18 @@ class Make(object):
with cd(self.builddir_join('latex')):
os.system('make all-pdf-ja')
+ def build_lualatexpdf(self):
+ if self.run_generic_build('latex') > 0:
+ return 1
+ with cd(self.builddir_join('latex')):
+ os.system('make PDFLATEX=lualatex all-pdf')
+
+ def build_xelatexpdf(self):
+ if self.run_generic_build('latex') > 0:
+ return 1
+ with cd(self.builddir_join('latex')):
+ os.system('make PDFLATEX=xelatex all-pdf')
+
def build_text(self):
if self.run_generic_build('text') > 0:
return 1
diff --git a/sphinx/pycode/pgen2/driver.py b/sphinx/pycode/pgen2/driver.py
index c531edb34..6bdcebece 100644
--- a/sphinx/pycode/pgen2/driver.py
+++ b/sphinx/pycode/pgen2/driver.py
@@ -92,11 +92,8 @@ class Driver(object):
def parse_file(self, filename, debug=False):
"""Parse a file and return the syntax tree."""
- stream = open(filename)
- try:
+ with open(filename) as stream:
return self.parse_stream(stream, debug)
- finally:
- stream.close()
def parse_string(self, text, debug=False):
"""Parse a string and return the syntax tree."""
diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py
index 1f14c0d09..eb54ca9cf 100644
--- a/sphinx/quickstart.py
+++ b/sphinx/quickstart.py
@@ -545,33 +545,35 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) %(rsrcdir)s
.PHONY: help
help:
\t@echo "Please use \\`make <target>' where <target> is one of"
-\t@echo " html to make standalone HTML files"
-\t@echo " dirhtml to make HTML files named index.html in directories"
-\t@echo " singlehtml to make a single large HTML file"
-\t@echo " pickle to make pickle files"
-\t@echo " json to make JSON files"
-\t@echo " htmlhelp to make HTML files and a HTML help project"
-\t@echo " qthelp to make HTML files and a qthelp project"
-\t@echo " applehelp to make an Apple Help Book"
-\t@echo " devhelp to make HTML files and a Devhelp project"
-\t@echo " epub to make an epub"
-\t@echo " epub3 to make an epub3"
-\t@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-\t@echo " latexpdf to make LaTeX files and run them through pdflatex"
-\t@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
-\t@echo " text to make text files"
-\t@echo " man to make manual pages"
-\t@echo " texinfo to make Texinfo files"
-\t@echo " info to make Texinfo files and run them through makeinfo"
-\t@echo " gettext to make PO message catalogs"
-\t@echo " changes to make an overview of all changed/added/deprecated items"
-\t@echo " xml to make Docutils-native XML files"
-\t@echo " pseudoxml to make pseudoxml-XML files for display purposes"
-\t@echo " linkcheck to check all external links for integrity"
-\t@echo " doctest to run all doctests embedded in the documentation \
+\t@echo " html to make standalone HTML files"
+\t@echo " dirhtml to make HTML files named index.html in directories"
+\t@echo " singlehtml to make a single large HTML file"
+\t@echo " pickle to make pickle files"
+\t@echo " json to make JSON files"
+\t@echo " htmlhelp to make HTML files and a HTML help project"
+\t@echo " qthelp to make HTML files and a qthelp project"
+\t@echo " applehelp to make an Apple Help Book"
+\t@echo " devhelp to make HTML files and a Devhelp project"
+\t@echo " epub to make an epub"
+\t@echo " epub3 to make an epub3"
+\t@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+\t@echo " latexpdf to make LaTeX files and run them through pdflatex"
+\t@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+\t@echo " lualatexpdf to make LaTeX files and run them through pdflatex"
+\t@echo " xelatexpdf to make LaTeX files and run them through pdflatex"
+\t@echo " text to make text files"
+\t@echo " man to make manual pages"
+\t@echo " texinfo to make Texinfo files"
+\t@echo " info to make Texinfo files and run them through makeinfo"
+\t@echo " gettext to make PO message catalogs"
+\t@echo " changes to make an overview of all changed/added/deprecated items"
+\t@echo " xml to make Docutils-native XML files"
+\t@echo " pseudoxml to make pseudoxml-XML files for display purposes"
+\t@echo " linkcheck to check all external links for integrity"
+\t@echo " doctest to run all doctests embedded in the documentation \
(if enabled)"
-\t@echo " coverage to run coverage check of the documentation (if enabled)"
-\t@echo " dummy to check syntax errors of document sources"
+\t@echo " coverage to run coverage check of the documentation (if enabled)"
+\t@echo " dummy to check syntax errors of document sources"
.PHONY: clean
clean:
@@ -678,6 +680,20 @@ latexpdfja:
\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
\t@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+.PHONY: lualatexpdf
+lualatexpdf:
+\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+\t@echo "Running LaTeX files through lualatex..."
+\t$(MAKE) PDFLATEX=lualatex -C $(BUILDDIR)/latex all-pdf
+\t@echo "lualatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+.PHONY: xelatexpdf
+xelatexpdf:
+\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+\t@echo "Running LaTeX files through xelatex..."
+\t$(MAKE) PDFLATEX=xelatex -C $(BUILDDIR)/latex all-pdf
+\t@echo "xelatex finished; the PDF files are in $(BUILDDIR)/latex."
+
.PHONY: text
text:
\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@@ -1451,11 +1467,8 @@ def generate(d, overwrite=True, silent=False):
def write_file(fpath, content, newline=None):
if overwrite or not path.isfile(fpath):
print('Creating file %s.' % fpath)
- f = open(fpath, 'wt', encoding='utf-8', newline=newline)
- try:
+ with open(fpath, 'wt', encoding='utf-8', newline=newline) as f:
f.write(content)
- finally:
- f.close()
else:
print('File %s already exists, skipping.' % fpath)
diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py
index 13fb4d528..2ba0c3802 100644
--- a/sphinx/search/__init__.py
+++ b/sphinx/search/__init__.py
@@ -15,6 +15,7 @@ from six.moves import cPickle as pickle
from docutils.nodes import raw, comment, title, Text, NodeVisitor, SkipNode
from os import path
+import sphinx
from sphinx.util import jsdump, rpartition
from sphinx.util.pycompat import htmlescape
@@ -180,6 +181,16 @@ class WordCollector(NodeVisitor):
self.found_title_words = []
self.lang = lang
+ def is_meta_keywords(self, node, nodetype):
+ if isinstance(node, sphinx.addnodes.meta) and node.get('name') == 'keywords':
+ meta_lang = node.get('lang')
+ if meta_lang is None: # lang not specified
+ return True
+ elif meta_lang == self.lang.lang: # matched to html_search_language
+ return True
+
+ return False
+
def dispatch_visit(self, node):
nodetype = type(node)
if issubclass(nodetype, comment):
@@ -197,6 +208,10 @@ class WordCollector(NodeVisitor):
self.found_words.extend(self.lang.split(node.astext()))
elif issubclass(nodetype, title):
self.found_title_words.extend(self.lang.split(node.astext()))
+ elif self.is_meta_keywords(node, nodetype):
+ keywords = node['content']
+ keywords = [keyword.strip() for keyword in keywords.split(',')]
+ self.found_words.extend(keywords)
class IndexBuilder(object):
@@ -353,7 +368,6 @@ class IndexBuilder(object):
def feed(self, filename, title, doctree):
"""Feed a doctree to the index."""
self._titles[filename] = title
-
visitor = WordCollector(doctree, self.lang)
doctree.walk(visitor)
diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty
index 38ec46d86..956924e99 100644
--- a/sphinx/texinputs/sphinx.sty
+++ b/sphinx/texinputs/sphinx.sty
@@ -11,8 +11,6 @@
\@ifclassloaded{memoir}{}{\RequirePackage{fancyhdr}}
\RequirePackage{textcomp}
-% fancybox not used anymore and will be removed at Sphinx-1.5
-\RequirePackage{fancybox}
\RequirePackage{titlesec}
\RequirePackage{tabulary}
\RequirePackage{makeidx}
diff --git a/sphinx/util/png.py b/sphinx/util/png.py
index e28445a42..476d45ccd 100644
--- a/sphinx/util/png.py
+++ b/sphinx/util/png.py
@@ -23,18 +23,14 @@ IEND_CHUNK = b'\x00\x00\x00\x00IEND\xAE\x42\x60\x82'
def read_png_depth(filename):
"""Read the special tEXt chunk indicating the depth from a PNG file."""
- result = None
- f = open(filename, 'rb')
- try:
+ with open(filename, 'rb') as f:
f.seek(- (LEN_IEND + LEN_DEPTH), 2)
depthchunk = f.read(LEN_DEPTH)
if not depthchunk.startswith(DEPTH_CHUNK_LEN + DEPTH_CHUNK_START):
# either not a PNG file or not containing the depth chunk
return None
- result = struct.unpack('!i', depthchunk[14:18])[0]
- finally:
- f.close()
- return result
+ else:
+ return struct.unpack('!i', depthchunk[14:18])[0]
def write_png_depth(filename, depth):
@@ -43,8 +39,7 @@ def write_png_depth(filename, depth):
The chunk is placed immediately before the special IEND chunk.
"""
data = struct.pack('!i', depth)
- f = open(filename, 'r+b')
- try:
+ with open(filename, 'r+b') as f:
# seek to the beginning of the IEND chunk
f.seek(-LEN_IEND, 2)
# overwrite it with the depth chunk
@@ -54,5 +49,3 @@ def write_png_depth(filename, depth):
f.write(struct.pack('!I', crc))
# replace the IEND chunk
f.write(IEND_CHUNK)
- finally:
- f.close()
diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py
index 0daa87981..c9474af9b 100644
--- a/sphinx/util/pycompat.py
+++ b/sphinx/util/pycompat.py
@@ -105,11 +105,8 @@ def execfile_(filepath, _globals, open=open):
from sphinx.util.osutil import fs_encoding
# get config source -- 'b' is a no-op under 2.x, while 'U' is
# ignored under 3.x (but 3.x compile() accepts \r\n newlines)
- f = open(filepath, 'rbU')
- try:
+ with open(filepath, 'rbU') as f:
source = f.read()
- finally:
- f.close()
# py26 accept only LF eol instead of CRLF
if sys.version_info[:2] == (2, 6):
diff --git a/sphinx/websupport/__init__.py b/sphinx/websupport/__init__.py
index 606d549a6..69914da95 100644
--- a/sphinx/websupport/__init__.py
+++ b/sphinx/websupport/__init__.py
@@ -130,11 +130,8 @@ class WebSupport(object):
"""Load and return the "global context" pickle."""
if not self._globalcontext:
infilename = path.join(self.datadir, 'globalcontext.pickle')
- f = open(infilename, 'rb')
- try:
+ with open(infilename, 'rb') as f:
self._globalcontext = pickle.load(f)
- finally:
- f.close()
return self._globalcontext
def get_document(self, docname, username='', moderator=False):
@@ -185,14 +182,11 @@ class WebSupport(object):
infilename = docpath + '.fpickle'
try:
- f = open(infilename, 'rb')
+ with open(infilename, 'rb') as f:
+ document = pickle.load(f)
except IOError:
raise errors.DocumentNotFoundError(
'The document "%s" could not be found' % docname)
- try:
- document = pickle.load(f)
- finally:
- f.close()
comment_opts = self._make_comment_options(username, moderator)
comment_meta = self._make_metadata(
diff --git a/test-reqs.txt b/test-reqs.txt
index 0edb725dc..582afe69f 100644
--- a/test-reqs.txt
+++ b/test-reqs.txt
@@ -12,3 +12,4 @@ whoosh>=2.0
alabaster
sphinx_rtd_theme
imagesize
+requests
diff --git a/tests/roots/test-ext-inheritance_diagram/index.rst b/tests/roots/test-ext-inheritance_diagram/index.rst
index 876996ca8..777192bd7 100644
--- a/tests/roots/test-ext-inheritance_diagram/index.rst
+++ b/tests/roots/test-ext-inheritance_diagram/index.rst
@@ -3,3 +3,6 @@ test-ext-inheritance_diagram
============================
.. inheritance-diagram:: test.Foo
+
+.. inheritance-diagram:: test.Foo
+ :caption: Test Foo!
diff --git a/tests/roots/test-search/conf.py b/tests/roots/test-search/conf.py
new file mode 100644
index 000000000..38b8b28c5
--- /dev/null
+++ b/tests/roots/test-search/conf.py
@@ -0,0 +1,3 @@
+master_doc = 'index'
+exclude_patterns = ['_build']
+html_search_language = 'en'
diff --git a/tests/roots/test-search/index.rst b/tests/roots/test-search/index.rst
new file mode 100644
index 000000000..f2536ce9a
--- /dev/null
+++ b/tests/roots/test-search/index.rst
@@ -0,0 +1,8 @@
+meta keywords
+=============
+
+.. meta::
+ :keywords lang=en: findthiskey, thistoo, notgerman
+ :keywords: thisonetoo
+ :keywords lang=de: onlygerman, onlytoogerman
+ :description: thisnoteither \ No newline at end of file
diff --git a/tests/test_config.py b/tests/test_config.py
index c05222601..d01d6afb4 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -38,7 +38,7 @@ def test_core_config(app, status, warning):
# simple default values
assert 'locale_dirs' not in cfg.__dict__
- assert cfg.locale_dirs == []
+ assert cfg.locale_dirs == ['locales']
assert cfg.trim_footnote_reference_space is False
# complex default values
diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py
index 64446eed8..bf1bbbac0 100644
--- a/tests/test_ext_inheritance_diagram.py
+++ b/tests/test_ext_inheritance_diagram.py
@@ -9,9 +9,34 @@
:license: BSD, see LICENSE for details.
"""
+import re
from util import with_app
+from test_ext_graphviz import skip_if_graphviz_not_found
@with_app('html', testroot='ext-inheritance_diagram')
+@skip_if_graphviz_not_found
def test_inheritance_diagram_html(app, status, warning):
app.builder.build_all()
+
+ content = (app.outdir / 'index.html').text()
+
+ pattern = ('<div class="figure" id="id1">\n'
+ '<img src="_images/inheritance-\w+.png" alt="Inheritance diagram of test.Foo" '
+ 'class="inheritance"/>\n<p class="caption"><span class="caption-text">'
+ 'Test Foo!</span><a class="headerlink" href="#id1" '
+ 'title="Permalink to this image">\xb6</a></p>')
+ assert re.search(pattern, content, re.M)
+
+
+@with_app('latex', testroot='ext-inheritance_diagram')
+@skip_if_graphviz_not_found
+def test_inheritance_diagram_latex(app, status, warning):
+ app.builder.build_all()
+
+ content = (app.outdir / 'Python.tex').text()
+
+ pattern = ('\\\\begin{figure}\\[htbp]\n\\\\centering\n\\\\capstart\n\n'
+ '\\\\includegraphics{inheritance-\\w+.pdf}\n'
+ '\\\\caption{Test Foo!}\\\\label{index:id1}\\\\end{figure}')
+ assert re.search(pattern, content, re.M)
diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py
index 17c1a7a56..ece55ebe4 100644
--- a/tests/test_ext_napoleon_docstring.py
+++ b/tests/test_ext_napoleon_docstring.py
@@ -249,7 +249,11 @@ class GoogleDocstringTest(BaseDocstringTest):
)]
def test_docstrings(self):
- config = Config(napoleon_use_param=False, napoleon_use_rtype=False)
+ config = Config(
+ napoleon_use_param=False,
+ napoleon_use_rtype=False,
+ napoleon_use_keyword=False
+ )
for docstring, expected in self.docstrings:
actual = str(GoogleDocstring(dedent(docstring), config))
expected = dedent(expected)
@@ -1046,7 +1050,10 @@ class NumpyDocstringTest(BaseDocstringTest):
)]
def test_docstrings(self):
- config = Config(napoleon_use_param=False, napoleon_use_rtype=False)
+ config = Config(
+ napoleon_use_param=False,
+ napoleon_use_rtype=False,
+ napoleon_use_keyword=False)
for docstring, expected in self.docstrings:
actual = str(NumpyDocstring(dedent(docstring), config))
expected = dedent(expected)
@@ -1736,3 +1743,19 @@ definition_after_normal_text : int
config = Config(napoleon_use_param=False)
actual = str(NumpyDocstring(docstring, config))
self.assertEqual(expected, actual)
+
+ def test_keywords_with_types(self):
+ docstring = """\
+Do as you please
+
+Keyword Args:
+ gotham_is_yours (None): shall interfere.
+"""
+ actual = str(GoogleDocstring(docstring))
+ expected = """\
+Do as you please
+
+:keyword gotham_is_yours: shall interfere.
+:kwtype gotham_is_yours: None
+"""
+ self.assertEqual(expected, actual)
diff --git a/tests/test_search.py b/tests/test_search.py
index cd2ff76f2..212ce778c 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -8,6 +8,7 @@
:copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
+import os
from docutils import frontend, utils
from docutils.parsers import rst
@@ -27,6 +28,17 @@ def setup_module():
parser = rst.Parser()
+def jsload(path):
+ searchindex = path.text()
+ assert searchindex.startswith('Search.setIndex(')
+
+ return jsdump.loads(searchindex[16:-2])
+
+
+def is_registered_term(index, keyword):
+ return index['terms'].get(keyword, []) != []
+
+
FILE_CONTENTS = '''\
.. test that comments are not indexed: boson
@@ -52,3 +64,29 @@ def test_objects_are_escaped(app, status, warning):
index = jsdump.loads(searchindex[16:-2])
assert 'n::Array&lt;T, d&gt;' in index.get('objects').get('') # n::Array<T,d> is escaped
+
+
+@with_app(testroot='search')
+def test_meta_keys_are_handled_for_language_en(app, status, warning):
+ app.builder.build_all()
+ searchindex = jsload(app.outdir / 'searchindex.js')
+ assert not is_registered_term(searchindex, 'thisnoteith')
+ assert is_registered_term(searchindex, 'thisonetoo')
+ assert is_registered_term(searchindex, 'findthiskei')
+ assert is_registered_term(searchindex, 'thistoo')
+ assert not is_registered_term(searchindex, 'onlygerman')
+ assert is_registered_term(searchindex, 'notgerman')
+ assert not is_registered_term(searchindex, 'onlytoogerman')
+
+
+@with_app(testroot='search', confoverrides={'html_search_language': 'de'})
+def test_meta_keys_are_handled_for_language_de(app, status, warning):
+ app.builder.build_all()
+ searchindex = jsload(app.outdir / 'searchindex.js')
+ assert not is_registered_term(searchindex, 'thisnoteith')
+ assert is_registered_term(searchindex, 'thisonetoo')
+ assert not is_registered_term(searchindex, 'findthiskei')
+ assert not is_registered_term(searchindex, 'thistoo')
+ assert is_registered_term(searchindex, 'onlygerman')
+ assert not is_registered_term(searchindex, 'notgerman')
+ assert is_registered_term(searchindex, 'onlytoogerman')