Merge branch '2.0' into 6165_tab_width

This commit is contained in:
Takeshi KOMIYA
2019-05-12 17:06:49 +09:00
committed by GitHub
33 changed files with 802 additions and 334 deletions

View File

@@ -11,11 +11,13 @@ Incompatible changes
API directly
* #6230: The anchor of term in glossary directive is changed if it is consisted
by non-ASCII characters
* #4550: html: Centering tables by default using CSS
Deprecated
----------
* ``sphinx.builders.latex.LaTeXBuilder.apply_transforms()``
* ``sphinx.builders._epub_base.EpubBuilder.esc()``
* ``sphinx.directives.Acks``
* ``sphinx.directives.Author``
* ``sphinx.directives.Centered``
@@ -40,6 +42,8 @@ Deprecated
* ``sphinx.domains.std.StandardDomain.note_citation_refs()``
* ``sphinx.domains.std.StandardDomain.note_labels()``
* ``sphinx.environment.NoUri``
* ``sphinx.ext.apidoc.format_directive()``
* ``sphinx.ext.apidoc.format_heading()``
* ``sphinx.ext.autodoc.importer.MockFinder``
* ``sphinx.ext.autodoc.importer.MockLoader``
* ``sphinx.ext.autodoc.importer.mock()``
@@ -91,6 +95,9 @@ Bugs fixed
* #6213: ifconfig: contents after headings are not shown
* commented term in glossary directive is wrongly recognized
* #6299: rst domain: rst:directive directive generates waste space
* #6331: man: invalid output when doctest follows rubric
* #6351: "Hyperlink target is not referenced" message is shown even if
referenced
* #6165: autodoc: ``tab_width`` setting of docutils has been ignored
Testing

View File

@@ -31,6 +31,11 @@ The following is a list of deprecated interfaces.
- 4.0
- N/A
* - ``sphinx.builders._epub_base.EpubBuilder.esc()``
- 2.1
- 4.0
- ``html.escape()``
* - ``sphinx.directives.Acks``
- 2.1
- 4.0
@@ -156,6 +161,15 @@ The following is a list of deprecated interfaces.
- 2.1
- 4.0
- ``sphinx.errors.NoUri``
* - ``sphinx.ext.apidoc.format_directive()``
- 2.1
- 4.0
- N/A
* - ``sphinx.ext.apidoc.format_heading()``
- 2.1
- 4.0
- N/A
* - ``sphinx.ext.autodoc.importer.MockFinder``
- 2.1

View File

@@ -55,6 +55,11 @@ strict_optional = False
filterwarnings =
all
ignore::DeprecationWarning:docutils.io
markers =
sphinx
apidoc
setup_command
test_params
[coverage:run]
branch = True

View File

@@ -38,6 +38,9 @@ extras_require = {
':sys_platform=="win32"': [
'colorama>=0.3.5',
],
'docs': [
'sphinxcontrib-websupport',
],
'test': [
'pytest',
'pytest-cov',

View File

@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""
import html
import os
import re
import warnings
@@ -178,7 +179,9 @@ class EpubBuilder(StandaloneHTMLBuilder):
def esc(self, name):
# type: (str) -> str
"""Replace all characters not allowed in text an attribute values."""
# Like cgi.escape, but also replace apostrophe
warnings.warn(
'%s.esc() is deprecated. Use html.escape() instead.' % self.__class__.__name__,
RemovedInSphinx40Warning)
name = name.replace('&', '&')
name = name.replace('<', '&lt;')
name = name.replace('>', '&gt;')
@@ -201,8 +204,8 @@ class EpubBuilder(StandaloneHTMLBuilder):
if (self.toctree_template % level) in classes:
result.append({
'level': level,
'refuri': self.esc(refuri),
'text': ssp(self.esc(doctree.astext()))
'refuri': html.escape(refuri),
'text': ssp(html.escape(doctree.astext()))
})
break
elif isinstance(doctree, nodes.Element):
@@ -241,21 +244,21 @@ class EpubBuilder(StandaloneHTMLBuilder):
"""
refnodes.insert(0, {
'level': 1,
'refuri': self.esc(self.config.master_doc + self.out_suffix),
'text': ssp(self.esc(
'refuri': html.escape(self.config.master_doc + self.out_suffix),
'text': ssp(html.escape(
self.env.titles[self.config.master_doc].astext()))
})
for file, text in reversed(self.config.epub_pre_files):
refnodes.insert(0, {
'level': 1,
'refuri': self.esc(file),
'text': ssp(self.esc(text))
'refuri': html.escape(file),
'text': ssp(html.escape(text))
})
for file, text in self.config.epub_post_files:
refnodes.append({
'level': 1,
'refuri': self.esc(file),
'text': ssp(self.esc(text))
'refuri': html.escape(file),
'text': ssp(html.escape(text))
})
def fix_fragment(self, prefix, fragment):
@@ -511,15 +514,15 @@ class EpubBuilder(StandaloneHTMLBuilder):
file properly escaped.
"""
metadata = {} # type: Dict[str, Any]
metadata['title'] = self.esc(self.config.epub_title)
metadata['author'] = self.esc(self.config.epub_author)
metadata['uid'] = self.esc(self.config.epub_uid)
metadata['lang'] = self.esc(self.config.epub_language)
metadata['publisher'] = self.esc(self.config.epub_publisher)
metadata['copyright'] = self.esc(self.config.epub_copyright)
metadata['scheme'] = self.esc(self.config.epub_scheme)
metadata['id'] = self.esc(self.config.epub_identifier)
metadata['date'] = self.esc(format_date("%Y-%m-%d"))
metadata['title'] = html.escape(self.config.epub_title)
metadata['author'] = html.escape(self.config.epub_author)
metadata['uid'] = html.escape(self.config.epub_uid)
metadata['lang'] = html.escape(self.config.epub_language)
metadata['publisher'] = html.escape(self.config.epub_publisher)
metadata['copyright'] = html.escape(self.config.epub_copyright)
metadata['scheme'] = html.escape(self.config.epub_scheme)
metadata['id'] = html.escape(self.config.epub_identifier)
metadata['date'] = html.escape(format_date("%Y-%m-%d"))
metadata['manifest_items'] = []
metadata['spines'] = []
metadata['guides'] = []
@@ -566,9 +569,9 @@ class EpubBuilder(StandaloneHTMLBuilder):
type='epub', subtype='unknown_project_files')
continue
filename = filename.replace(os.sep, '/')
item = ManifestItem(self.esc(filename),
self.esc(self.make_id(filename)),
self.esc(self.media_types[ext]))
item = ManifestItem(html.escape(filename),
html.escape(self.make_id(filename)),
html.escape(self.media_types[ext]))
metadata['manifest_items'].append(item)
self.files.append(filename)
@@ -579,21 +582,21 @@ class EpubBuilder(StandaloneHTMLBuilder):
continue
if refnode['refuri'] in self.ignored_files:
continue
spine = Spine(self.esc(self.make_id(refnode['refuri'])), True)
spine = Spine(html.escape(self.make_id(refnode['refuri'])), True)
metadata['spines'].append(spine)
spinefiles.add(refnode['refuri'])
for info in self.domain_indices:
spine = Spine(self.esc(self.make_id(info[0] + self.out_suffix)), True)
spine = Spine(html.escape(self.make_id(info[0] + self.out_suffix)), True)
metadata['spines'].append(spine)
spinefiles.add(info[0] + self.out_suffix)
if self.use_index:
spine = Spine(self.esc(self.make_id('genindex' + self.out_suffix)), True)
spine = Spine(html.escape(self.make_id('genindex' + self.out_suffix)), True)
metadata['spines'].append(spine)
spinefiles.add('genindex' + self.out_suffix)
# add auto generated files
for name in self.files:
if name not in spinefiles and name.endswith(self.out_suffix):
spine = Spine(self.esc(self.make_id(name)), False)
spine = Spine(html.escape(self.make_id(name)), False)
metadata['spines'].append(spine)
# add the optional cover
@@ -601,18 +604,18 @@ class EpubBuilder(StandaloneHTMLBuilder):
if self.config.epub_cover:
image, html_tmpl = self.config.epub_cover
image = image.replace(os.sep, '/')
metadata['cover'] = self.esc(self.make_id(image))
metadata['cover'] = html.escape(self.make_id(image))
if html_tmpl:
spine = Spine(self.esc(self.make_id(self.coverpage_name)), True)
spine = Spine(html.escape(self.make_id(self.coverpage_name)), True)
metadata['spines'].insert(0, spine)
if self.coverpage_name not in self.files:
ext = path.splitext(self.coverpage_name)[-1]
self.files.append(self.coverpage_name)
item = ManifestItem(self.esc(self.coverpage_name),
self.esc(self.make_id(self.coverpage_name)),
self.esc(self.media_types[ext]))
item = ManifestItem(html.escape(self.coverpage_name),
html.escape(self.make_id(self.coverpage_name)),
html.escape(self.media_types[ext]))
metadata['manifest_items'].append(item)
ctx = {'image': self.esc(image), 'title': self.config.project}
ctx = {'image': html.escape(image), 'title': self.config.project}
self.handle_page(
path.splitext(self.coverpage_name)[0], ctx, html_tmpl)
spinefiles.add(self.coverpage_name)
@@ -628,17 +631,17 @@ class EpubBuilder(StandaloneHTMLBuilder):
auto_add_cover = False
if type == 'toc':
auto_add_toc = False
metadata['guides'].append(Guide(self.esc(type),
self.esc(title),
self.esc(uri)))
metadata['guides'].append(Guide(html.escape(type),
html.escape(title),
html.escape(uri)))
if auto_add_cover and html_tmpl:
metadata['guides'].append(Guide('cover',
self.guide_titles['cover'],
self.esc(self.coverpage_name)))
html.escape(self.coverpage_name)))
if auto_add_toc and self.refnodes:
metadata['guides'].append(Guide('toc',
self.guide_titles['toc'],
self.esc(self.refnodes[0]['refuri'])))
html.escape(self.refnodes[0]['refuri'])))
# write the project file
copy_asset_file(path.join(self.template_dir, 'content.opf_t'),
@@ -707,7 +710,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
"""
metadata = {} # type: Dict[str, Any]
metadata['uid'] = self.config.epub_uid
metadata['title'] = self.esc(self.config.epub_title)
metadata['title'] = html.escape(self.config.epub_title)
metadata['level'] = level
metadata['navpoints'] = navpoints
return metadata

View File

@@ -9,6 +9,7 @@
:license: BSD, see LICENSE for details.
"""
import html
import warnings
from collections import namedtuple
from os import path
@@ -98,12 +99,12 @@ class Epub3Builder(_epub_base.EpubBuilder):
writing_mode = self.config.epub_writing_mode
metadata = super().content_metadata()
metadata['description'] = self.esc(self.config.epub_description)
metadata['contributor'] = self.esc(self.config.epub_contributor)
metadata['description'] = html.escape(self.config.epub_description)
metadata['contributor'] = html.escape(self.config.epub_contributor)
metadata['page_progression_direction'] = PAGE_PROGRESSION_DIRECTIONS.get(writing_mode)
metadata['ibook_scroll_axis'] = IBOOK_SCROLL_AXIS.get(writing_mode)
metadata['date'] = self.esc(format_date("%Y-%m-%dT%H:%M:%SZ"))
metadata['version'] = self.esc(self.config.version)
metadata['date'] = html.escape(format_date("%Y-%m-%dT%H:%M:%SZ"))
metadata['version'] = html.escape(self.config.version)
metadata['epub_version'] = self.config.epub_version
return metadata
@@ -166,8 +167,8 @@ class Epub3Builder(_epub_base.EpubBuilder):
properly escaped.
"""
metadata = {} # type: Dict
metadata['lang'] = self.esc(self.config.epub_language)
metadata['toc_locale'] = self.esc(self.guide_titles['toc'])
metadata['lang'] = html.escape(self.config.epub_language)
metadata['toc_locale'] = html.escape(self.guide_titles['toc'])
metadata['navlist'] = navlist
return metadata

View File

@@ -19,15 +19,18 @@ import glob
import locale
import os
import sys
import warnings
from fnmatch import fnmatch
from os import path
import sphinx.locale
from sphinx import __display_version__, package_dir
from sphinx.cmd.quickstart import EXTENSIONS
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.locale import __
from sphinx.util import rst
from sphinx.util.osutil import FileAvoidWrite, ensuredir
from sphinx.util.template import ReSTRenderer
if False:
# For type annotation
@@ -47,6 +50,8 @@ else:
INITPY = '__init__.py'
PY_SUFFIXES = {'.py', '.pyx'}
template_dir = path.join(package_dir, 'templates', 'apidoc')
def makename(package, module):
# type: (str, str) -> str
@@ -79,6 +84,8 @@ def write_file(name, text, opts):
def format_heading(level, text, escape=True):
# type: (int, str, bool) -> str
"""Create a heading of <level> [1, 2 or 3 supported]."""
warnings.warn('format_warning() is deprecated.',
RemovedInSphinx40Warning)
if escape:
text = rst.escape(text)
underlining = ['=', '-', '~', ][level - 1] * len(text)
@@ -88,100 +95,79 @@ def format_heading(level, text, escape=True):
def format_directive(module, package=None):
# type: (str, str) -> str
"""Create the automodule directive and add the options."""
warnings.warn('format_directive() is deprecated.',
RemovedInSphinx40Warning)
directive = '.. automodule:: %s\n' % makename(package, module)
for option in OPTIONS:
directive += ' :%s:\n' % option
return directive
def create_module_file(package, module, opts):
def create_module_file(package, basename, opts):
# type: (str, str, Any) -> None
"""Build the text of the file and write the file."""
if not opts.noheadings:
text = format_heading(1, '%s module' % module)
else:
text = ''
# text += format_heading(2, ':mod:`%s` Module' % module)
text += format_directive(module, package)
write_file(makename(package, module), text, opts)
qualname = makename(package, basename)
context = {
'show_headings': not opts.noheadings,
'basename': basename,
'qualname': qualname,
'automodule_options': OPTIONS,
}
text = ReSTRenderer(template_dir).render('module.rst', context)
write_file(qualname, text, opts)
def create_package_file(root, master_package, subroot, py_files, opts, subs, is_namespace, excludes=[]): # NOQA
# type: (str, str, str, List[str], Any, List[str], bool, List[str]) -> None
"""Build the text of the file and write the file."""
text = format_heading(1, ('%s package' if not is_namespace else "%s namespace")
% makename(master_package, subroot))
if opts.modulefirst and not is_namespace:
text += format_directive(subroot, master_package)
text += '\n'
# build a list of directories that are szvpackages (contain an INITPY file)
# and also checks the INITPY file is not empty, or there are other python
# source files in that folder.
# (depending on settings - but shall_skip() takes care of that)
subs = [sub for sub in subs if not
shall_skip(path.join(root, sub, INITPY), opts, excludes)]
# if there are some package directories, add a TOC for theses subpackages
if subs:
text += format_heading(2, 'Subpackages')
text += '.. toctree::\n\n'
for sub in subs:
text += ' %s.%s\n' % (makename(master_package, subroot), sub)
text += '\n'
submods = [path.splitext(sub)[0] for sub in py_files
if not shall_skip(path.join(root, sub), opts, excludes) and
sub != INITPY]
if submods:
text += format_heading(2, 'Submodules')
if opts.separatemodules:
text += '.. toctree::\n\n'
for submod in submods:
modfile = makename(master_package, makename(subroot, submod))
text += ' %s\n' % modfile
# generate separate file for this module
if not opts.noheadings:
filetext = format_heading(1, '%s module' % modfile)
else:
filetext = ''
filetext += format_directive(makename(subroot, submod),
master_package)
write_file(modfile, filetext, opts)
else:
for submod in submods:
modfile = makename(master_package, makename(subroot, submod))
if not opts.noheadings:
text += format_heading(2, '%s module' % modfile)
text += format_directive(makename(subroot, submod),
master_package)
text += '\n'
text += '\n'
if not opts.modulefirst and not is_namespace:
text += format_heading(2, 'Module contents')
text += format_directive(subroot, master_package)
# build a list of sub packages (directories containing an INITPY file)
subpackages = [sub for sub in subs if not
shall_skip(path.join(root, sub, INITPY), opts, excludes)]
subpackages = [makename(makename(master_package, subroot), pkgname)
for pkgname in subpackages]
# build a list of sub modules
submodules = [path.splitext(sub)[0] for sub in py_files
if not shall_skip(path.join(root, sub), opts, excludes) and
sub != INITPY]
submodules = [makename(master_package, makename(subroot, modname))
for modname in submodules]
context = {
'pkgname': makename(master_package, subroot),
'subpackages': subpackages,
'submodules': submodules,
'is_namespace': is_namespace,
'modulefirst': opts.modulefirst,
'separatemodules': opts.separatemodules,
'automodule_options': OPTIONS,
'show_headings': not opts.noheadings,
}
text = ReSTRenderer(template_dir).render('package.rst', context)
write_file(makename(master_package, subroot), text, opts)
if submodules and opts.separatemodules:
for submodule in submodules:
create_module_file(None, submodule, opts)
def create_modules_toc_file(modules, opts, name='modules'):
# type: (List[str], Any, str) -> None
"""Create the module's index."""
text = format_heading(1, '%s' % opts.header, escape=False)
text += '.. toctree::\n'
text += ' :maxdepth: %s\n\n' % opts.maxdepth
modules.sort()
prev_module = ''
for module in modules:
for module in modules[:]:
# look if the module is a subpackage and, if yes, ignore it
if module.startswith(prev_module + '.'):
continue
prev_module = module
text += ' %s\n' % module
modules.remove(module)
else:
prev_module = module
context = {
'header': opts.header,
'maxdepth': opts.maxdepth,
'docnames': modules,
}
text = ReSTRenderer(template_dir).render('toc.rst', context)
write_file(name, text, opts)

View File

@@ -32,6 +32,7 @@ if False:
# For type annotation
from typing import Any, Dict # NOQA
from pygments.formatter import Formatter # NOQA
from pygments.style import Style # NOQA
logger = logging.getLogger(__name__)
@@ -69,16 +70,8 @@ class PygmentsBridge:
def __init__(self, dest='html', stylename='sphinx', trim_doctest_flags=None):
# type: (str, str, bool) -> None
self.dest = dest
if stylename is None or stylename == 'sphinx':
style = SphinxStyle
elif stylename == 'none':
style = NoneStyle
elif '.' in stylename:
module, stylename = stylename.rsplit('.', 1)
style = getattr(__import__(module, None, None, ['__name__']),
stylename)
else:
style = get_style_by_name(stylename)
style = self.get_style(stylename)
self.formatter_args = {'style': style} # type: Dict[str, Any]
if dest == 'html':
self.formatter = self.html_formatter
@@ -91,6 +84,18 @@ class PygmentsBridge:
warnings.warn('trim_doctest_flags option for PygmentsBridge is now deprecated.',
RemovedInSphinx30Warning, stacklevel=2)
def get_style(self, stylename):
# type: (str) -> Style
if stylename is None or stylename == 'sphinx':
return SphinxStyle
elif stylename == 'none':
return NoneStyle
elif '.' in stylename:
module, stylename = stylename.rsplit('.', 1)
return getattr(__import__(module, None, None, ['__name__']), stylename)
else:
return get_style_by_name(stylename)
def get_formatter(self, **kwargs):
# type: (Any) -> Formatter
kwargs.update(self.formatter_args)
@@ -110,11 +115,8 @@ class PygmentsBridge:
return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \
source + '\\end{Verbatim}\n'
def highlight_block(self, source, lang, opts=None, location=None, force=False, **kwargs):
# type: (str, str, Any, Any, bool, Any) -> str
if not isinstance(source, str):
source = source.decode()
def get_lexer(self, source, lang, opts=None, location=None):
# type: (str, str, Any, Any) -> Lexer
# find out which lexer to use
if lang in ('py', 'python'):
if source.startswith('>>>'):
@@ -145,6 +147,15 @@ class PygmentsBridge:
else:
lexer.add_filter('raiseonerror')
return lexer
def highlight_block(self, source, lang, opts=None, location=None, force=False, **kwargs):
# type: (str, str, Any, Any, bool, Any) -> str
if not isinstance(source, str):
source = source.decode()
lexer = self.get_lexer(source, lang, opts, location)
# trim doctest options if wanted
if isinstance(lexer, PythonConsoleLexer) and self.trim_doctest_flags:
source = doctest.blankline_re.sub('', source)
@@ -165,6 +176,7 @@ class PygmentsBridge:
type='misc', subtype='highlighting_failure',
location=location)
hlsource = highlight(source, lexers['none'], formatter)
if self.dest == 'html':
return hlsource
else:

View File

@@ -16,6 +16,7 @@ from docutils.io import FileInput, NullOutput
from docutils.parsers.rst import Parser as RSTParser
from docutils.readers import standalone
from docutils.statemachine import StringList, string2lines
from docutils.transforms.references import DanglingReferences
from docutils.writers import UnfilteredWriter
from sphinx.deprecation import RemovedInSphinx30Warning
@@ -64,7 +65,15 @@ class SphinxBaseReader(standalone.Reader):
def get_transforms(self):
# type: () -> List[Type[Transform]]
return super().get_transforms() + self.transforms
transforms = super().get_transforms() + self.transforms
# remove transforms which is not needed for Sphinx
unused = [DanglingReferences]
for transform in unused:
if transform in transforms:
transforms.remove(transform)
return transforms
def new_document(self):
# type: () -> nodes.document

View File

@@ -0,0 +1,9 @@
{%- if show_headings %}
{{- [basename, "module"] | join(' ') | e | heading }}
{% endif -%}
.. automodule:: {{ qualname }}
{%- for option in automodule_options %}
:{{ option }}:
{%- endfor %}

View File

@@ -0,0 +1,52 @@
{%- macro automodule(modname, options) -%}
.. automodule:: {{ modname }}
{%- for option in options %}
:{{ option }}:
{%- endfor %}
{%- endmacro %}
{%- macro toctree(docnames) -%}
.. toctree::
{% for docname in docnames %}
{{ docname }}
{%- endfor %}
{%- endmacro %}
{%- if is_namespace %}
{{- [pkgname, "namespace"] | join(" ") | e | heading }}
{% else %}
{{- [pkgname, "package"] | join(" ") | e | heading }}
{% endif %}
{%- if modulefirst and not is_namespace %}
{{ automodule(pkgname, automodule_options) }}
{% endif %}
{%- if subpackages %}
Subpackages
-----------
{{ toctree(subpackages) }}
{% endif %}
{%- if submodules %}
Submodules
----------
{% if separatemodules %}
{{ toctree(submodules) }}
{%- else %}
{%- for submodule in submodules %}
{% if show_headings %}
{{- [submodule, "module"] | join(" ") | e | heading(2) }}
{% endif %}
{{ automodule(submodule, automodule_options) }}
{%- endfor %}
{% endif %}
{% endif %}
{%- if not modulefirst and not is_namespace %}
Module contents
---------------
{{ automodule(pkgname, automodule_options) }}
{% endif %}

View File

@@ -0,0 +1,8 @@
{{ header | heading }}
.. toctree::
:maxdepth: {{ maxdepth }}
{% for docname in docnames %}
{{ docname }}
{%- endfor %}

View File

@@ -1,5 +1,5 @@
\documentclass[12pt]{article}
\usepackage[utf8x]{inputenc}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}

View File

@@ -1,5 +1,5 @@
\documentclass[12pt]{article}
\usepackage[utf8x]{inputenc}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}

View File

@@ -1,5 +1,5 @@
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}
<%- if table.align == 'center' -%>
<%- if table.align in ('center', 'default') -%>
[c]
<%- elif table.align == 'left' -%>
[l]

View File

@@ -1,6 +1,6 @@
\begin{savenotes}\sphinxattablestart
<% if table.align -%>
<%- if table.align == 'center' -%>
<%- if table.align in ('center', 'default') -%>
\centering
<%- elif table.align == 'left' -%>
\raggedright

View File

@@ -1,6 +1,6 @@
\begin{savenotes}\sphinxattablestart
<% if table.align -%>
<%- if table.align == 'center' -%>
<%- if table.align in ('center', 'default') -%>
\centering
<%- elif table.align == 'left' -%>
\raggedright

View File

@@ -289,6 +289,12 @@ img.align-center, .figure.align-center, object.align-center {
margin-right: auto;
}
img.align-default, .figure.align-default {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left;
}
@@ -297,6 +303,10 @@ img.align-center, .figure.align-center, object.align-center {
text-align: center;
}
.align-default {
text-align: center;
}
.align-right {
text-align: right;
}
@@ -368,6 +378,11 @@ table.align-center {
margin-right: auto;
}
table.align-default {
margin-left: auto;
margin-right: auto;
}
table caption span.caption-number {
font-style: italic;
}

View File

@@ -293,7 +293,7 @@ class FigureAligner(SphinxTransform):
# type: (Any) -> None
matcher = NodeMatcher(nodes.table, nodes.figure)
for node in self.document.traverse(matcher): # type: nodes.Element
node.setdefault('align', 'center')
node.setdefault('align', 'default')
class FilterSystemMessages(SphinxTransform):

View File

@@ -9,7 +9,7 @@
"""
from docutils import nodes
from docutils.transforms.references import Substitutions
from docutils.transforms.references import DanglingReferences, Substitutions
from sphinx.transforms import SphinxTransform
@@ -31,6 +31,22 @@ class SubstitutionDefinitionsRemover(SphinxTransform):
node.parent.remove(node)
class SphinxDanglingReferences(DanglingReferences):
"""DanglingReferences transform which does not output info messages."""
def apply(self, **kwargs):
# type: (Any) -> None
try:
reporter = self.document.reporter
report_level = reporter.report_level
# suppress INFO level messages for a while
reporter.report_level = max(reporter.WARNING_LEVEL, reporter.report_level)
super().apply()
finally:
reporter.report_level = report_level
class SphinxDomains(SphinxTransform):
"""Collect objects to Sphinx domains for cross references."""
default_priority = 850
@@ -44,6 +60,7 @@ class SphinxDomains(SphinxTransform):
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
app.add_transform(SubstitutionDefinitionsRemover)
app.add_transform(SphinxDanglingReferences)
app.add_transform(SphinxDomains)
return {

View File

@@ -9,11 +9,14 @@
"""
import re
from collections import defaultdict
from contextlib import contextmanager
from unicodedata import east_asian_width
from docutils.parsers.rst import roles
from docutils.parsers.rst.languages import en as english
from docutils.utils import Reporter
from jinja2 import environmentfilter
from sphinx.locale import __
from sphinx.util import docutils
@@ -21,13 +24,20 @@ from sphinx.util import logging
if False:
# For type annotation
from typing import Generator # NOQA
from typing import Callable, Dict, Generator # NOQA
from docutils.statemachine import StringList # NOQA
from jinja2 import Environment # NOQA
logger = logging.getLogger(__name__)
docinfo_re = re.compile(':\\w+:.*?')
symbols_re = re.compile(r'([!-\-/:-@\[-`{-~])') # symbols without dot(0x2e)
SECTIONING_CHARS = ['=', '-', '~']
# width of characters
WIDECHARS = defaultdict(lambda: "WF") # type: Dict[str, str]
# WF: Wide + Full-width
WIDECHARS["ja"] = "WFA" # In Japanese, Ambiguous characters also have double width
def escape(text):
@@ -37,6 +47,29 @@ def escape(text):
return text
def textwidth(text, widechars='WF'):
# type: (str, str) -> int
"""Get width of text."""
def charwidth(char, widechars):
# type: (str, str) -> int
if east_asian_width(char) in widechars:
return 2
else:
return 1
return sum(charwidth(c, widechars) for c in text)
@environmentfilter
def heading(env, text, level=1):
# type: (Environment, str, int) -> str
"""Create a heading for *level*."""
assert level <= 3
width = textwidth(text, WIDECHARS[env.language]) # type: ignore
sectioning_char = SECTIONING_CHARS[level - 1]
return '%s\n%s' % (text, sectioning_char * width)
@contextmanager
def default_role(docname, name):
# type: (str, str) -> Generator

View File

@@ -15,7 +15,7 @@ from jinja2.sandbox import SandboxedEnvironment
from sphinx import package_dir
from sphinx.jinja2glue import SphinxFileSystemLoader
from sphinx.locale import get_translator
from sphinx.util import texescape
from sphinx.util import rst, texescape
if False:
# For type annotation
@@ -84,3 +84,17 @@ class LaTeXRenderer(SphinxRenderer):
self.env.variable_end_string = '%>'
self.env.block_start_string = '<%'
self.env.block_end_string = '%>'
class ReSTRenderer(SphinxRenderer):
def __init__(self, template_path=None, language=None):
# type: (str, str) -> None
super().__init__(template_path)
# add language to environment
self.env.extend(language=language)
# use texescape as escape filter
self.env.filters['e'] = rst.escape
self.env.filters['escape'] = rst.escape
self.env.filters['heading'] = rst.heading

View File

@@ -1565,6 +1565,7 @@ class LaTeXTranslator(SphinxTranslator):
(1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
(1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
(0, 'center'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'),
(0, 'default'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'),
# These 2 don't exactly do the right thing. The image should
# be floated alongside the paragraph. See
# https://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG

View File

@@ -282,7 +282,7 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
def depart_rubric(self, node):
# type: (nodes.Element) -> None
pass
self.body.append('\n')
def visit_seealso(self, node):
# type: (nodes.Element) -> None

View File

@@ -565,7 +565,7 @@ def test_numfig_disabled_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/"
@@ -582,21 +582,21 @@ def test_numfig_disabled_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True),
],
'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True),
],
'bar.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True),
],
'baz.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/"
@@ -633,9 +633,9 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 9 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 10 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 9 $', True),
@@ -657,13 +657,13 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
(".//li/p/code/span", '^Sect.{number}$', True),
],
'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1 $', True),
@@ -683,11 +683,11 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 4 $', True),
],
'bar.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 5 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 7 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 8 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 5 $', True),
@@ -703,7 +703,7 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 8 $', True),
],
'baz.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 6 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 6 $', True),
@@ -741,9 +741,9 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1 $', True),
@@ -765,13 +765,13 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True),
],
'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1.1 $', True),
@@ -791,11 +791,11 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 1.4 $', True),
],
'bar.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 2.1 $', True),
@@ -811,7 +811,7 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 2.4 $', True),
],
'baz.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 2.2 $', True),
@@ -846,9 +846,9 @@ def test_numfig_with_prefix_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Tab_1 $', True),
@@ -870,13 +870,13 @@ def test_numfig_with_prefix_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True),
],
'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Tab_1.1 $', True),
@@ -896,11 +896,11 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-1.4 $', True),
],
'bar.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Tab_2.1 $', True),
@@ -916,7 +916,7 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-2.4 $', True),
],
'baz.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Tab_2.2 $', True),
@@ -952,9 +952,9 @@ def test_numfig_with_secnum_depth_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1 $', True),
@@ -976,13 +976,13 @@ def test_numfig_with_secnum_depth_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True),
],
'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1.2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.2.1 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1.1 $', True),
@@ -1002,11 +1002,11 @@ def test_numfig_with_secnum_depth_warn(app, warning):
"span[@class='caption-number']", '^Listing 1.2.1 $', True),
],
'bar.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.2.1 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 2.1.1 $', True),
@@ -1022,7 +1022,7 @@ def test_numfig_with_secnum_depth_warn(app, warning):
"span[@class='caption-number']", '^Listing 2.2.1 $', True),
],
'baz.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1.2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 2.1.2 $', True),
@@ -1043,9 +1043,9 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1 $', True),
@@ -1065,13 +1065,13 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
(".//li/p/a/span", '^Section.2.1$', True),
(".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/p/a/span", '^Sect.1 Foo$', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1.1 $', True),
@@ -1089,11 +1089,11 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
"span[@class='caption-number']", '^Listing 1.3 $', True),
(".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 1.4 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 2.1 $', True),
@@ -1107,7 +1107,7 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
"span[@class='caption-number']", '^Listing 2.3 $', True),
(".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 2.4 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 2.2 $', True),
@@ -1126,11 +1126,11 @@ def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect):
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//div[@class='figure align-center']/p[@class='caption']"
(".//div[@class='figure align-default']/p[@class='caption']"
"/span[@class='caption-number']", "Fig. 1", True),
(".//div[@class='figure align-center']/p[@class='caption']"
(".//div[@class='figure align-default']/p[@class='caption']"
"/span[@class='caption-number']", "Fig. 2", True),
(".//div[@class='figure align-center']/p[@class='caption']"
(".//div[@class='figure align-default']/p[@class='caption']"
"/span[@class='caption-number']", "Fig. 3", True),
(".//div//span[@class='caption-number']", "No.1 ", True),
(".//div//span[@class='caption-number']", "No.2 ", True),

View File

@@ -59,3 +59,10 @@ def test_default_man_pages():
expected = [('index', 'stasi', 'STASI™ Documentation 1.0',
["Wolfgang Schäuble & G'Beckstein"], 1)]
assert default_man_pages(config) == expected
@pytest.mark.sphinx('man', testroot='markup-rubric')
def test_rubric(app, status, warning):
app.build()
content = (app.outdir / 'python.1').text()
assert 'This is a rubric\n' in content

View File

@@ -13,6 +13,7 @@ from collections import namedtuple
import pytest
from sphinx.ext.apidoc import main as apidoc_main
from sphinx.testing.path import path
@pytest.fixture()
@@ -398,3 +399,216 @@ def test_subpackage_in_toc(make_app, apidoc):
assert 'parent.child.foo' in parent_child
assert (outdir / 'parent.child.foo.rst').isfile()
def test_toc_file(tempdir):
outdir = path(tempdir)
(outdir / 'module').makedirs()
(outdir / 'example.py').write_text('')
(outdir / 'module' / 'example.py').write_text('')
apidoc_main(['-o', tempdir, tempdir])
assert (outdir / 'modules.rst').exists()
content = (outdir / 'modules.rst').text()
assert content == ("test_toc_file0\n"
"==============\n"
"\n"
".. toctree::\n"
" :maxdepth: 4\n"
"\n"
" example\n")
def test_module_file(tempdir):
outdir = path(tempdir)
(outdir / 'example.py').write_text('')
apidoc_main(['-o', tempdir, tempdir])
assert (outdir / 'example.rst').exists()
content = (outdir / 'example.rst').text()
assert content == ("example module\n"
"==============\n"
"\n"
".. automodule:: example\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n")
def test_module_file_noheadings(tempdir):
outdir = path(tempdir)
(outdir / 'example.py').write_text('')
apidoc_main(['--no-headings', '-o', tempdir, tempdir])
assert (outdir / 'example.rst').exists()
content = (outdir / 'example.rst').text()
assert content == (".. automodule:: example\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n")
def test_package_file(tempdir):
outdir = path(tempdir)
(outdir / 'testpkg').makedirs()
(outdir / 'testpkg' / '__init__.py').write_text('')
(outdir / 'testpkg' / 'example.py').write_text('')
(outdir / 'testpkg' / 'subpkg').makedirs()
(outdir / 'testpkg' / 'subpkg' / '__init__.py').write_text('')
apidoc_main(['-o', tempdir, tempdir / 'testpkg'])
assert (outdir / 'testpkg.rst').exists()
assert (outdir / 'testpkg.subpkg.rst').exists()
content = (outdir / 'testpkg.rst').text()
assert content == ("testpkg package\n"
"===============\n"
"\n"
"Subpackages\n"
"-----------\n"
"\n"
".. toctree::\n"
"\n"
" testpkg.subpkg\n"
"\n"
"Submodules\n"
"----------\n"
"\n"
"testpkg.example module\n"
"----------------------\n"
"\n"
".. automodule:: testpkg.example\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n"
"\n"
"\n"
"Module contents\n"
"---------------\n"
"\n"
".. automodule:: testpkg\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n")
content = (outdir / 'testpkg.subpkg.rst').text()
assert content == ("testpkg.subpkg package\n"
"======================\n"
"\n"
"Module contents\n"
"---------------\n"
"\n"
".. automodule:: testpkg.subpkg\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n")
def test_package_file_separate(tempdir):
outdir = path(tempdir)
(outdir / 'testpkg').makedirs()
(outdir / 'testpkg' / '__init__.py').write_text('')
(outdir / 'testpkg' / 'example.py').write_text('')
apidoc_main(['--separate', '-o', tempdir, tempdir / 'testpkg'])
assert (outdir / 'testpkg.rst').exists()
assert (outdir / 'testpkg.example.rst').exists()
content = (outdir / 'testpkg.rst').text()
assert content == ("testpkg package\n"
"===============\n"
"\n"
"Submodules\n"
"----------\n"
"\n"
".. toctree::\n"
"\n"
" testpkg.example\n"
"\n"
"Module contents\n"
"---------------\n"
"\n"
".. automodule:: testpkg\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n")
content = (outdir / 'testpkg.example.rst').text()
assert content == ("testpkg.example module\n"
"======================\n"
"\n"
".. automodule:: testpkg.example\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n")
def test_package_file_module_first(tempdir):
outdir = path(tempdir)
(outdir / 'testpkg').makedirs()
(outdir / 'testpkg' / '__init__.py').write_text('')
(outdir / 'testpkg' / 'example.py').write_text('')
apidoc_main(['--module-first', '-o', tempdir, tempdir])
content = (outdir / 'testpkg.rst').text()
assert content == ("testpkg package\n"
"===============\n"
"\n"
".. automodule:: testpkg\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n"
"\n"
"Submodules\n"
"----------\n"
"\n"
"testpkg.example module\n"
"----------------------\n"
"\n"
".. automodule:: testpkg.example\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n"
"\n")
def test_package_file_without_submodules(tempdir):
outdir = path(tempdir)
(outdir / 'testpkg').makedirs()
(outdir / 'testpkg' / '__init__.py').write_text('')
apidoc_main(['-o', tempdir, tempdir / 'testpkg'])
assert (outdir / 'testpkg.rst').exists()
content = (outdir / 'testpkg.rst').text()
assert content == ("testpkg package\n"
"===============\n"
"\n"
"Module contents\n"
"---------------\n"
"\n"
".. automodule:: testpkg\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n")
def test_namespace_package_file(tempdir):
outdir = path(tempdir)
(outdir / 'testpkg').makedirs()
(outdir / 'testpkg' / 'example.py').write_text('')
apidoc_main(['--implicit-namespace', '-o', tempdir, tempdir / 'testpkg'])
assert (outdir / 'testpkg.rst').exists()
content = (outdir / 'testpkg.rst').text()
assert content == ("testpkg namespace\n"
"=================\n"
"\n"
"Submodules\n"
"----------\n"
"\n"
"testpkg.example module\n"
"----------------------\n"
"\n"
".. automodule:: testpkg.example\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n"
"\n")

View File

@@ -21,7 +21,7 @@ def test_graphviz_png_html(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').text()
html = (r'<div class="figure align-center" .*?>\s*'
html = (r'<div class="figure align-default" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<p class="caption">'
r'<span class="caption-text">caption of graph</span>.*</p>\s*</div>')
assert re.search(html, content, re.S)
@@ -52,7 +52,7 @@ def test_graphviz_svg_html(app, status, warning):
content = (app.outdir / 'index.html').text()
html = (r'<div class=\"figure align-center\" .*?>\n'
html = (r'<div class=\"figure align-default\" .*?>\n'
r'<div class="graphviz"><object data=\".*\.svg\".*>\n'
r'\s*<p class=\"warning\">digraph foo {\n'
r'bar -&gt; baz\n'

View File

@@ -1,129 +0,0 @@
"""
test_inheritance
~~~~~~~~~~~~~~~~
Tests for :mod:`sphinx.ext.inheritance_diagram` module.
:copyright: Copyright 2015 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import os
import pytest
from sphinx.ext.inheritance_diagram import InheritanceDiagram
@pytest.mark.sphinx(buildername="html", testroot="inheritance")
@pytest.mark.usefixtures('if_graphviz_found')
def test_inheritance_diagram(app, status, warning):
# monkey-patch InheritaceDiagram.run() so we can get access to its
# results.
orig_run = InheritanceDiagram.run
graphs = {}
def new_run(self):
result = orig_run(self)
node = result[0]
source = os.path.basename(node.document.current_source).replace(".rst", "")
graphs[source] = node['graph']
return result
InheritanceDiagram.run = new_run
try:
app.builder.build_all()
finally:
InheritanceDiagram.run = orig_run
assert app.statuscode == 0
html_warnings = warning.getvalue()
assert html_warnings == ""
# note: it is better to split these asserts into separate test functions
# but I can't figure out how to build only a specific .rst file
# basic inheritance diagram showing all classes
for cls in graphs['basic_diagram'].class_info:
# use in b/c traversing order is different sometimes
assert cls in [
('dummy.test.A', 'dummy.test.A', [], None),
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', ['dummy.test.A'], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', ['dummy.test.A'], None)
]
# inheritance diagram using :parts: 1 option
for cls in graphs['diagram_w_parts'].class_info:
assert cls in [
('A', 'dummy.test.A', [], None),
('F', 'dummy.test.F', ['C'], None),
('C', 'dummy.test.C', ['A'], None),
('E', 'dummy.test.E', ['B'], None),
('D', 'dummy.test.D', ['B', 'C'], None),
('B', 'dummy.test.B', ['A'], None)
]
# inheritance diagram with 1 top class
# :top-classes: dummy.test.B
# rendering should be
# A
# \
# B C
# / \ / \
# E D F
#
for cls in graphs['diagram_w_1_top_class'].class_info:
assert cls in [
('dummy.test.A', 'dummy.test.A', [], None),
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', ['dummy.test.A'], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None)
]
# inheritance diagram with 2 top classes
# :top-classes: dummy.test.B, dummy.test.C
# Note: we're specifying separate classes, not the entire module here
# rendering should be
#
# B C
# / \ / \
# E D F
#
for cls in graphs['diagram_w_2_top_classes'].class_info:
assert cls in [
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', [], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None)
]
# inheritance diagram with 2 top classes and specifiying the entire module
# rendering should be
#
# A
# B C
# / \ / \
# E D F
#
# Note: dummy.test.A is included in the graph before its descendants are even processed
# b/c we've specified to load the entire module. The way InheritanceGraph works it is very
# hard to exclude parent classes once after they have been included in the graph.
# If you'd like to not show class A in the graph don't specify the entire module.
# this is a known issue.
for cls in graphs['diagram_module_w_2_top_classes'].class_info:
assert cls in [
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', [], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None),
('dummy.test.A', 'dummy.test.A', [], None),
]

View File

@@ -9,11 +9,128 @@
"""
import re
import os
import sys
import pytest
from sphinx.ext.inheritance_diagram import InheritanceException, import_classes
from sphinx.ext.inheritance_diagram import (
InheritanceDiagram, InheritanceException, import_classes
)
@pytest.mark.sphinx(buildername="html", testroot="inheritance")
@pytest.mark.usefixtures('if_graphviz_found')
def test_inheritance_diagram(app, status, warning):
# monkey-patch InheritaceDiagram.run() so we can get access to its
# results.
orig_run = InheritanceDiagram.run
graphs = {}
def new_run(self):
result = orig_run(self)
node = result[0]
source = os.path.basename(node.document.current_source).replace(".rst", "")
graphs[source] = node['graph']
return result
InheritanceDiagram.run = new_run
try:
app.builder.build_all()
finally:
InheritanceDiagram.run = orig_run
assert app.statuscode == 0
html_warnings = warning.getvalue()
assert html_warnings == ""
# note: it is better to split these asserts into separate test functions
# but I can't figure out how to build only a specific .rst file
# basic inheritance diagram showing all classes
for cls in graphs['basic_diagram'].class_info:
# use in b/c traversing order is different sometimes
assert cls in [
('dummy.test.A', 'dummy.test.A', [], None),
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', ['dummy.test.A'], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', ['dummy.test.A'], None)
]
# inheritance diagram using :parts: 1 option
for cls in graphs['diagram_w_parts'].class_info:
assert cls in [
('A', 'dummy.test.A', [], None),
('F', 'dummy.test.F', ['C'], None),
('C', 'dummy.test.C', ['A'], None),
('E', 'dummy.test.E', ['B'], None),
('D', 'dummy.test.D', ['B', 'C'], None),
('B', 'dummy.test.B', ['A'], None)
]
# inheritance diagram with 1 top class
# :top-classes: dummy.test.B
# rendering should be
# A
# \
# B C
# / \ / \
# E D F
#
for cls in graphs['diagram_w_1_top_class'].class_info:
assert cls in [
('dummy.test.A', 'dummy.test.A', [], None),
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', ['dummy.test.A'], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None)
]
# inheritance diagram with 2 top classes
# :top-classes: dummy.test.B, dummy.test.C
# Note: we're specifying separate classes, not the entire module here
# rendering should be
#
# B C
# / \ / \
# E D F
#
for cls in graphs['diagram_w_2_top_classes'].class_info:
assert cls in [
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', [], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None)
]
# inheritance diagram with 2 top classes and specifiying the entire module
# rendering should be
#
# A
# B C
# / \ / \
# E D F
#
# Note: dummy.test.A is included in the graph before its descendants are even processed
# b/c we've specified to load the entire module. The way InheritanceGraph works it is very
# hard to exclude parent classes once after they have been included in the graph.
# If you'd like to not show class A in the graph don't specify the entire module.
# this is a known issue.
for cls in graphs['diagram_module_w_2_top_classes'].class_info:
assert cls in [
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
('dummy.test.C', 'dummy.test.C', [], None),
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
('dummy.test.B', 'dummy.test.B', [], None),
('dummy.test.A', 'dummy.test.A', [], None),
]
@pytest.mark.sphinx('html', testroot='ext-inheritance_diagram')
@@ -23,7 +140,7 @@ def test_inheritance_diagram_png_html(app, status, warning):
content = (app.outdir / 'index.html').text()
pattern = ('<div class="figure align-center" id="id1">\n'
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'class="inheritance graphviz" /></div>\n<p class="caption">'
@@ -40,7 +157,7 @@ def test_inheritance_diagram_svg_html(app, status, warning):
content = (app.outdir / 'index.html').text()
pattern = ('<div class="figure align-center" id="id1">\n'
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<object data="_images/inheritance-\\w+.svg" '
'type="image/svg\\+xml" class="inheritance graphviz">\n'
@@ -80,7 +197,7 @@ def test_inheritance_diagram_latex_alias(app, status, warning):
content = (app.outdir / 'index.html').text()
pattern = ('<div class="figure align-center" id="id1">\n'
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'class="inheritance graphviz" /></div>\n<p class="caption">'

View File

@@ -9,8 +9,11 @@
"""
from docutils.statemachine import StringList
from jinja2 import Environment
from sphinx.util.rst import append_epilog, escape, prepend_prolog
from sphinx.util.rst import (
append_epilog, escape, heading, prepend_prolog, textwidth
)
def test_escape():
@@ -83,3 +86,34 @@ def test_prepend_prolog_without_CR(app):
('<generated>', 0, ''),
('dummy.rst', 0, 'hello Sphinx world'),
('dummy.rst', 1, 'Sphinx is a document generator')]
def test_textwidth():
assert textwidth('Hello') == 5
assert textwidth('русский язык') == 12
assert textwidth('русский язык', 'WFA') == 23 # Cyrillic are ambiguous chars
def test_heading():
env = Environment()
env.extend(language=None)
assert heading(env, 'Hello') == ('Hello\n'
'=====')
assert heading(env, 'Hello', 1) == ('Hello\n'
'=====')
assert heading(env, 'Hello', 2) == ('Hello\n'
'-----')
assert heading(env, 'Hello', 3) == ('Hello\n'
'~~~~~')
assert heading(env, 'русский язык', 1) == (
'русский язык\n'
'============'
)
# language=ja: ambiguous
env.language = 'ja'
assert heading(env, 'русский язык', 1) == (
'русский язык\n'
'======================='
)

View File

@@ -0,0 +1,37 @@
"""
test_util_template
~~~~~~~~~~~~~~~~~~
Tests sphinx.util.template functions.
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from sphinx.util.template import ReSTRenderer
def test_ReSTRenderer_escape():
r = ReSTRenderer()
template = '{{ "*hello*" | e }}'
assert r.render_string(template, {}) == r'\*hello\*'
def test_ReSTRenderer_heading():
r = ReSTRenderer()
template = '{{ "hello" | heading }}'
assert r.render_string(template, {}) == 'hello\n====='
template = '{{ "hello" | heading(1) }}'
assert r.render_string(template, {}) == 'hello\n====='
template = '{{ "русский язык" | heading(2) }}'
assert r.render_string(template, {}) == ('русский язык\n'
'------------')
# language: ja
r.env.language = 'ja'
template = '{{ "русский язык" | heading }}'
assert r.render_string(template, {}) == ('русский язык\n'
'=======================')

View File

@@ -15,7 +15,6 @@ deps =
du14: docutils==0.14
extras =
test
websupport
setenv =
PYTHONWARNINGS = all,ignore::ImportWarning:pkgutil,ignore::ImportWarning:importlib._bootstrap,ignore::ImportWarning:importlib._bootstrap_external,ignore::ImportWarning:pytest_cov.plugin,ignore::DeprecationWarning:site,ignore::DeprecationWarning:_pytest.assertion.rewrite,ignore::DeprecationWarning:_pytest.fixtures,ignore::DeprecationWarning:distutils
commands=
@@ -62,8 +61,8 @@ commands=
basepython = python3
description =
Build documentation.
deps =
sphinxcontrib-websupport
extras =
docs
commands =
python setup.py build_sphinx {posargs}