From 1ea23e14df871ff97aa4082dddecfd11c4465cbe Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 24 Mar 2019 01:22:30 +0900 Subject: [PATCH 01/17] Fix #6165: autodoc: ``tab_width`` setting of docutils has been ignored --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 11 ++++++++--- sphinx/ext/autodoc/directive.py | 22 ++++++++++++++++++---- sphinx/ext/autosummary/__init__.py | 4 ++-- sphinx/util/docstrings.py | 6 +++--- tests/test_autodoc.py | 7 ++++++- 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 9cc8cab22..a8ea6c387 100644 --- a/CHANGES +++ b/CHANGES @@ -91,6 +91,7 @@ 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 +* #6165: autodoc: ``tab_width`` setting of docutils has been ignored Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 2a4df2159..412c336e3 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -442,7 +442,8 @@ class Documenter: docstring = getdoc(self.object, self.get_attr, self.env.config.autodoc_inherit_docstrings) if docstring: - return [prepare_docstring(docstring, ignore)] + tab_width = self.directive.state.document.settings.tab_width + return [prepare_docstring(docstring, ignore, tab_width)] return [] def process_doc(self, docstrings): @@ -936,7 +937,9 @@ class DocstringSignatureMixin: if base not in valid_names: continue # re-prepare docstring to ignore more leading indentation - self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:])) + tab_width = self.directive.state.document.settings.tab_width # type: ignore + self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:]), + tabsize=tab_width) result = args, retann # don't look any further break @@ -1179,7 +1182,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: docstrings = [initdocstring] else: docstrings.append(initdocstring) - return [prepare_docstring(docstring, ignore) for docstring in docstrings] + + tab_width = self.directive.state.document.settings.tab_width + return [prepare_docstring(docstring, ignore, tab_width) for docstring in docstrings] def add_content(self, more_content, no_docstring=False): # type: (Any, bool) -> None diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py index 42415433b..6b002b101 100644 --- a/sphinx/ext/autodoc/directive.py +++ b/sphinx/ext/autodoc/directive.py @@ -6,10 +6,14 @@ :license: BSD, see LICENSE for details. """ +import warnings + from docutils import nodes +from docutils.parsers.rst.states import Struct from docutils.statemachine import StringList from docutils.utils import assemble_option_dict +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.ext.autodoc import Options, get_documenters from sphinx.util import logging from sphinx.util.docutils import SphinxDirective, switch_source_input @@ -17,7 +21,7 @@ from sphinx.util.nodes import nested_parse_with_titles if False: # For type annotation - from typing import Callable, Dict, List, Set, Type # NOQA + from typing import Any, Callable, Dict, List, Set, Type # NOQA from docutils.parsers.rst.state import RSTState # NOQA from docutils.utils import Reporter # NOQA from sphinx.config import Config # NOQA @@ -50,8 +54,8 @@ class DummyOptionSpec(dict): class DocumenterBridge: """A parameters container for Documenters.""" - def __init__(self, env, reporter, options, lineno): - # type: (BuildEnvironment, Reporter, Options, int) -> None + def __init__(self, env, reporter, options, lineno, state=None): + # type: (BuildEnvironment, Reporter, Options, int, Any) -> None self.env = env self.reporter = reporter self.genopt = options @@ -59,6 +63,16 @@ class DocumenterBridge: self.filename_set = set() # type: Set[str] self.result = StringList() + if state: + self.state = state + else: + # create fake object for self.state.document.settings.tab_width + warnings.warn('DocumenterBridge requires a state object on instantiation.', + RemovedInSphinx40Warning) + settings = Struct(tab_width=8) + document = Struct(settings=settings) + self.state = Struct(document=document) + def warn(self, msg): # type: (str) -> None logger.warning(msg, location=(self.env.docname, self.lineno)) @@ -131,7 +145,7 @@ class AutodocDirective(SphinxDirective): return [] # generate the output - params = DocumenterBridge(self.env, reporter, documenter_options, lineno) + params = DocumenterBridge(self.env, reporter, documenter_options, lineno, self.state) documenter = doccls(params, self.arguments[0]) documenter.generate(more_content=self.content) if not params.result: diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 952bd9e2a..5840f0ccd 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -175,7 +175,7 @@ _app = None # type: Sphinx class FakeDirective(DocumenterBridge): def __init__(self): # type: () -> None - super().__init__({}, None, Options(), 0) # type: ignore + super().__init__({}, None, Options(), 0, None) # type: ignore def get_documenter(app, obj, parent): @@ -236,7 +236,7 @@ class Autosummary(SphinxDirective): def run(self): # type: () -> List[nodes.Node] self.bridge = DocumenterBridge(self.env, self.state.document.reporter, - Options(), self.lineno) + Options(), self.lineno, self.state) names = [x.strip().split()[0] for x in self.content if x.strip() and re.search(r'^[~a-zA-Z_]', x.strip()[0])] diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index 97dd60294..31943b2cb 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -15,8 +15,8 @@ if False: from typing import List # NOQA -def prepare_docstring(s, ignore=1): - # type: (str, int) -> List[str] +def prepare_docstring(s, ignore=1, tabsize=8): + # type: (str, int, int) -> List[str] """Convert a docstring into lines of parseable reST. Remove common leading indentation, where the indentation of a given number of lines (usually just one) is ignored. @@ -25,7 +25,7 @@ def prepare_docstring(s, ignore=1): ViewList (used as argument of nested_parse().) An empty line is added to act as a separator between this docstring and following content. """ - lines = s.expandtabs().splitlines() + lines = s.expandtabs(tabsize).splitlines() # Find minimum indentation of any non-blank lines after ignored lines. margin = sys.maxsize for line in lines[ignore:]: diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 5f616b791..75d59db14 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -11,6 +11,7 @@ import platform import sys +from unittest.mock import Mock from warnings import catch_warnings import pytest @@ -36,7 +37,9 @@ def do_autodoc(app, objtype, name, options=None): app.env.temp_data.setdefault('docname', 'index') # set dummy docname doccls = app.registry.documenters[objtype] docoptions = process_documenter_options(doccls, app.config, options) - bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1) + state = Mock() + state.document.settings.tab_width = 8 + bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1, state) documenter = doccls(bridge, name) documenter.generate() @@ -95,7 +98,9 @@ def setup_test(): genopt = options, result = ViewList(), filename_set = set(), + state = Mock(), ) + directive.state.document.settings.tab_width = 8 processed_docstrings = [] processed_signatures = [] From 107c20a11f1753a23496a57ea8f04178e59f6810 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 6 May 2019 19:44:36 +0900 Subject: [PATCH 02/17] Fix #4550: html: Centering tables by default using CSS --- CHANGES | 1 + sphinx/templates/latex/longtable.tex_t | 2 +- sphinx/templates/latex/tabular.tex_t | 2 +- sphinx/templates/latex/tabulary.tex_t | 2 +- sphinx/themes/basic/static/basic.css_t | 15 ++++ sphinx/transforms/__init__.py | 2 +- sphinx/writers/latex.py | 1 + tests/test_build_html.py | 114 ++++++++++++------------- tests/test_ext_graphviz.py | 4 +- tests/test_ext_inheritance_diagram.py | 6 +- 10 files changed, 83 insertions(+), 66 deletions(-) diff --git a/CHANGES b/CHANGES index 8a7c6647b..dd9b8f81e 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ 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 ---------- diff --git a/sphinx/templates/latex/longtable.tex_t b/sphinx/templates/latex/longtable.tex_t index ade1a54af..8fe5369df 100644 --- a/sphinx/templates/latex/longtable.tex_t +++ b/sphinx/templates/latex/longtable.tex_t @@ -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] diff --git a/sphinx/templates/latex/tabular.tex_t b/sphinx/templates/latex/tabular.tex_t index a4f56feb3..a0db7faff 100644 --- a/sphinx/templates/latex/tabular.tex_t +++ b/sphinx/templates/latex/tabular.tex_t @@ -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 diff --git a/sphinx/templates/latex/tabulary.tex_t b/sphinx/templates/latex/tabulary.tex_t index e3534725b..3236b798a 100644 --- a/sphinx/templates/latex/tabulary.tex_t +++ b/sphinx/templates/latex/tabulary.tex_t @@ -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 diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 90a14286f..91fd35755 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -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; } diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index 16849c46c..a4e6e52bf 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -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): diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c5be44b18..820d41b9f 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -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 diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 1dbf05a4a..1fd04cfdb 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -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), diff --git a/tests/test_ext_graphviz.py b/tests/test_ext_graphviz.py index 6a3096c23..ec905aa5f 100644 --- a/tests/test_ext_graphviz.py +++ b/tests/test_ext_graphviz.py @@ -21,7 +21,7 @@ def test_graphviz_png_html(app, status, warning): app.builder.build_all() content = (app.outdir / 'index.html').text() - html = (r'
\s*' + html = (r'
\s*' r'
\s*

' r'caption of graph.*

\s*
') 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'
\n' + html = (r'
\n' r'
\n' r'\s*

digraph foo {\n' r'bar -> baz\n' diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py index 30ad625aa..71ff5a139 100644 --- a/tests/test_ext_inheritance_diagram.py +++ b/tests/test_ext_inheritance_diagram.py @@ -23,7 +23,7 @@ def test_inheritance_diagram_png_html(app, status, warning): content = (app.outdir / 'index.html').text() - pattern = ('

\n' + pattern = ('
\n' '
' 'Inheritance diagram of test.Foo
\n

' @@ -40,7 +40,7 @@ def test_inheritance_diagram_svg_html(app, status, warning): content = (app.outdir / 'index.html').text() - pattern = ('

\n' + pattern = ('
\n' '
' '\n' @@ -80,7 +80,7 @@ def test_inheritance_diagram_latex_alias(app, status, warning): content = (app.outdir / 'index.html').text() - pattern = ('
\n' + pattern = ('
\n' '
' 'Inheritance diagram of test.Foo
\n

' From a38ce3bfc7d3a74f59a88ea96acec4b07e0b40cf Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 12 May 2019 15:21:03 +0900 Subject: [PATCH 03/17] test: declare pytest markers To stop PytestUnknownMarkWarning, this declares markers on setup.cfg https://docs.pytest.org/en/latest/mark.html --- setup.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.cfg b/setup.cfg index 2db007339..c91a31879 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,6 +55,11 @@ strict_optional = False filterwarnings = all ignore::DeprecationWarning:docutils.io +markers = + sphinx + apidoc + setup_command + test_params [coverage:run] branch = True From a142a654fccb4a7c9f5704f55d8aacfe866efd94 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 16 Apr 2019 20:31:43 +0900 Subject: [PATCH 04/17] Add :property: option to py:method directive --- CHANGES | 8 ++++++-- doc/usage/restructuredtext/domains.rst | 6 +++++- sphinx/domains/python.py | 20 ++++++++++++++------ tests/test_domain_py.py | 15 ++++++++++++++- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index c337b2087..55e0b5c91 100644 --- a/CHANGES +++ b/CHANGES @@ -84,8 +84,12 @@ Features added imported members on autosummary * #6271: ``make clean`` is catastrophically broken if building into '.' * #4777: py domain: Add ``:async:`` option to :rst:dir:`py:function` directive -* py domain: Add ``:async:``, ``:classmethod:`` and ``:staticmethod:`` options - to :rst:dir:`py:method` directive +* py domain: Add new options to :rst:dir:`py:method` directive + + - ``:async:`` + - ``:classmethod:`` + - ``:property:`` + - ``:staticmethod:`` Bugs fixed ---------- diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 10fbf6f6f..7d616c8c7 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -229,9 +229,13 @@ The following directives are provided for module and class contents: The ``classmethod`` option and ``staticmethod`` option can be given (with no value) to indicate the method is a class method (or a static method). + The ``property`` option can be given (with no value) to indicate the method + is a property. + .. versionchanged:: 2.1 - ``:async:``, ``:classmethod:`` and ``:staticmethod:`` options added. + ``:async:``, ``:classmethod:``, ``:property:`` and ``:staticmethod:`` + options added. .. rst:directive:: .. py:staticmethod:: name(parameters) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index c1ef3f990..c4971ba60 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -587,22 +587,28 @@ class PyMethod(PyObject): option_spec.update({ 'async': directives.flag, 'classmethod': directives.flag, + 'property': directives.flag, 'staticmethod': directives.flag, }) def needs_arglist(self): # type: () -> bool - return True + if 'property' in self.options: + return False + else: + return True def get_signature_prefix(self, sig): # type: (str) -> str prefix = [] if 'async' in self.options: prefix.append('async') - if 'staticmethod' in self.options: - prefix.append('static') if 'classmethod' in self.options: prefix.append('classmethod') + if 'property' in self.options: + prefix.append('property') + if 'staticmethod' in self.options: + prefix.append('static') if prefix: return ' '.join(prefix) + ' ' @@ -622,10 +628,12 @@ class PyMethod(PyObject): else: return '%s()' % name - if 'staticmethod' in self.options: - return _('%s() (%s static method)') % (methname, clsname) - elif 'classmethod' in self.options: + if 'classmethod' in self.options: return _('%s() (%s class method)') % (methname, clsname) + elif 'property' in self.options: + return _('%s() (%s property)') % (methname, clsname) + elif 'staticmethod' in self.options: + return _('%s() (%s static method)') % (methname, clsname) else: return _('%s() (%s method)') % (methname, clsname) diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index d3c685388..fac8a838f 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -333,7 +333,9 @@ def test_pymethod_options(app): " .. py:method:: meth3\n" " :staticmethod:\n" " .. py:method:: meth4\n" - " :async:\n") + " :async:\n" + " .. py:method:: meth5\n" + " :property:\n") domain = app.env.get_domain('py') doctree = restructuredtext.parse(app, text) assert_node(doctree, (addnodes.index, @@ -346,6 +348,8 @@ def test_pymethod_options(app): addnodes.index, desc, addnodes.index, + desc, + addnodes.index, desc)])])) # method @@ -387,6 +391,15 @@ def test_pymethod_options(app): assert 'Class.meth4' in domain.objects assert domain.objects['Class.meth4'] == ('index', 'method') + # :property: + assert_node(doctree[1][1][8], addnodes.index, + entries=[('single', 'meth5() (Class property)', 'Class.meth5', '', None)]) + assert_node(doctree[1][1][9], ([desc_signature, ([desc_annotation, "property "], + [desc_name, "meth5"])], + [desc_content, ()])) + assert 'Class.meth5' in domain.objects + assert domain.objects['Class.meth5'] == ('index', 'method') + def test_pyclassmethod(app): text = (".. py:class:: Class\n" From c59f2d9545b3c17f3a5b7364e2bc19525543a49a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 16 Apr 2019 21:59:50 +0900 Subject: [PATCH 05/17] Add sphinx.util.inspect:isproperty() --- sphinx/util/inspect.py | 6 ++++++ tests/test_util_inspect.py | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index a05110496..ddc7ee5ef 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -224,6 +224,12 @@ def iscoroutinefunction(obj): return False +def isproperty(obj): + # type: (Any) -> bool + """Check if the object is property.""" + return isinstance(obj, property) + + def safe_getattr(obj, name, *defargs): # type: (Any, str, str) -> object """A getattr() that turns all exceptions into AttributeErrors.""" diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index c298e2c64..c80b2b7c8 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -475,3 +475,14 @@ def test_isattributedescriptor(app): assert inspect.isattributedescriptor(types.FrameType.f_locals) is True # GetSetDescriptorType # NOQA assert inspect.isattributedescriptor(datetime.timedelta.days) is True # MemberDescriptorType # NOQA assert inspect.isattributedescriptor(testinstancemethod) is False # instancemethod (C-API) # NOQA + + +def test_isproperty(app): + from target.functions import func + from target.methods import Base + + assert inspect.isproperty(Base.prop) is True # property of class + assert inspect.isproperty(Base().prop) is False # property of instance + assert inspect.isproperty(Base.meth) is False # method of class + assert inspect.isproperty(Base().meth) is False # method of instance + assert inspect.isproperty(func) is False # function From b708f7b82f4074274f07a5b5f7886cc618a3e15a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 16 Apr 2019 22:00:41 +0900 Subject: [PATCH 06/17] autodoc: Add PropertyDocumenter to detect properties --- sphinx/ext/autodoc/__init__.py | 32 ++++++++++++++++++++++++++++++ sphinx/ext/autosummary/generate.py | 13 ++++++++---- tests/test_autodoc.py | 25 +++++++++++++---------- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 2a4df2159..9f9dd2fde 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1415,6 +1415,37 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): super().add_content(more_content, no_docstring) +class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore + """ + Specialized Documenter subclass for properties. + """ + objtype = 'property' + directivetype = 'method' + member_order = 60 + + # before AttributeDocumenter + priority = AttributeDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + # type: (Any, str, bool, Any) -> bool + return inspect.isproperty(member) and isinstance(parent, ClassDocumenter) + + def document_members(self, all_members=False): + # type: (bool) -> None + pass + + def get_real_modname(self): + # type: () -> str + return self.get_attr(self.parent or self.object, '__module__', None) \ + or self.modname + + def add_directive_header(self, sig): + # type: (str) -> None + super().add_directive_header(sig) + self.add_line(' :property:', self.get_sourcename()) + + class InstanceAttributeDocumenter(AttributeDocumenter): """ Specialized Documenter subclass for attributes that cannot be imported @@ -1506,6 +1537,7 @@ def setup(app): app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) + app.add_autodocumenter(PropertyDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter) app.add_config_value('autoclass_content', 'class', True) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 1e9dbedc8..c8972b499 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -40,7 +40,7 @@ from sphinx.util.rst import escape as rst_escape if False: # For type annotation - from typing import Any, Callable, Dict, List, Tuple, Type, Union # NOQA + from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union # NOQA from sphinx.builders import Builder # NOQA from sphinx.ext.autodoc import Documenter # NOQA @@ -170,7 +170,12 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', template = template_env.get_template('autosummary/base.rst') def get_members(obj, typ, include_public=[], imported=True): - # type: (Any, str, List[str], bool) -> Tuple[List[str], List[str]] + # type: (Any, Union[str, Set[str]], List[str], bool) -> Tuple[List[str], List[str]] # NOQA + if isinstance(typ, str): + types = {typ} + else: + types = typ + items = [] # type: List[str] for name in dir(obj): try: @@ -178,7 +183,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', except AttributeError: continue documenter = get_documenter(app, value, obj) - if documenter.objtype == typ: + if documenter.objtype in types: if imported or getattr(value, '__module__', None) == obj.__name__: # skip imported members if expected items.append(name) @@ -203,7 +208,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', ns['methods'], ns['all_methods'] = \ get_members(obj, 'method', ['__init__']) ns['attributes'], ns['all_attributes'] = \ - get_members(obj, 'attribute') + get_members(obj, {'attribute', 'property'}) parts = name.split('.') if doc.objtype in ('method', 'attribute'): diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 5f616b791..563ab8fcf 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -756,7 +756,7 @@ def test_autodoc_undoc_members(app): ' .. py:attribute:: Class.mdocattr', ' .. py:method:: Class.meth()', ' .. py:method:: Class.moore(a, e, f) -> happiness', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:attribute:: Class.skipattr', ' .. py:method:: Class.skipmeth()', @@ -777,6 +777,7 @@ def test_autodoc_inherited_members(app): ' .. py:method:: Class.inheritedstaticmeth(cls)', ' .. py:method:: Class.meth()', ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.prop', ' .. py:method:: Class.skipmeth()' ] @@ -836,7 +837,7 @@ def test_autodoc_special_members(app): ' .. py:attribute:: Class.mdocattr', ' .. py:method:: Class.meth()', ' .. py:method:: Class.moore(a, e, f) -> happiness', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:attribute:: Class.skipattr', ' .. py:method:: Class.skipmeth()', @@ -1028,7 +1029,7 @@ def test_autodoc_member_order(app): ' .. py:method:: Class.excludemeth()', ' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.attr', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:attribute:: Class.docattr', ' .. py:attribute:: Class.udocattr', ' .. py:attribute:: Class.mdocattr', @@ -1062,7 +1063,7 @@ def test_autodoc_member_order(app): ' .. py:attribute:: Class.inst_attr_inline', ' .. py:attribute:: Class.inst_attr_string', ' .. py:attribute:: Class.mdocattr', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.udocattr' ] @@ -1085,7 +1086,7 @@ def test_autodoc_member_order(app): ' .. py:attribute:: Class.mdocattr', ' .. py:method:: Class.meth()', ' .. py:method:: Class.moore(a, e, f) -> happiness', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:attribute:: Class.skipattr', ' .. py:method:: Class.skipmeth()', @@ -1152,14 +1153,16 @@ def test_autodoc_docstring_signature(app): ' indented line', ' ', ' ', - ' .. py:attribute:: DocstringSig.prop1', + ' .. py:method:: DocstringSig.prop1', ' :module: target', + ' :property:', ' ', ' First line of docstring', ' ', ' ', - ' .. py:attribute:: DocstringSig.prop2', + ' .. py:method:: DocstringSig.prop2', ' :module: target', + ' :property:', ' ', ' First line of docstring', ' Second line of docstring', @@ -1194,15 +1197,17 @@ def test_autodoc_docstring_signature(app): ' indented line', ' ', ' ', - ' .. py:attribute:: DocstringSig.prop1', + ' .. py:method:: DocstringSig.prop1', ' :module: target', + ' :property:', ' ', ' DocstringSig.prop1(self)', ' First line of docstring', ' ', ' ', - ' .. py:attribute:: DocstringSig.prop2', + ' .. py:method:: DocstringSig.prop2', ' :module: target', + ' :property:', ' ', ' First line of docstring', ' Second line of docstring', @@ -1717,7 +1722,7 @@ def test_autodoc_default_options_with_values(app): ' .. py:method:: Class.skipmeth()', ' .. py:method:: Class.excludemeth()', ' .. py:attribute:: Class.attr', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:attribute:: Class.docattr', ' .. py:attribute:: Class.udocattr', ' .. py:attribute:: Class.mdocattr', From e0747684b187f9ff4e37fd8894c388f6c250d233 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 14 Feb 2019 23:43:09 +0900 Subject: [PATCH 07/17] refactor: Add PygmentsBridge.get_style() and .get_lexer() --- sphinx/highlighting.py | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index c53149ce6..dd602708a 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -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: From ab668396fc449ecba7788338ea4357fc99dde158 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 12 May 2019 14:52:03 +0900 Subject: [PATCH 08/17] Fix #6351: "Hyperlink target is not referenced" message is shown even if referenced --- CHANGES | 2 ++ sphinx/io.py | 11 ++++++++++- sphinx/transforms/references.py | 19 ++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index c337b2087..aa94dd076 100644 --- a/CHANGES +++ b/CHANGES @@ -96,6 +96,8 @@ Bugs fixed * 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 Testing -------- diff --git a/sphinx/io.py b/sphinx/io.py index b5b57d065..5f4ec3351 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -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 diff --git a/sphinx/transforms/references.py b/sphinx/transforms/references.py index de512f437..9cdc28c78 100644 --- a/sphinx/transforms/references.py +++ b/sphinx/transforms/references.py @@ -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 { From e08e7b05d3a0fc681570c680f4c1b027a9dec231 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Mon, 22 Apr 2019 13:55:32 +0300 Subject: [PATCH 09/17] Add a label to search input for accessability purposes. --- sphinx/themes/basic/searchbox.html | 4 ++-- tests/test_build_html.py | 6 +++--- tests/test_theming.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sphinx/themes/basic/searchbox.html b/sphinx/themes/basic/searchbox.html index 2ed7fa137..6679ca6b5 100644 --- a/sphinx/themes/basic/searchbox.html +++ b/sphinx/themes/basic/searchbox.html @@ -9,10 +9,10 @@ #} {%- if pagename != "search" and builder != "singlehtml" %}