diff options
-rw-r--r-- | CHANGES | 30 | ||||
-rw-r--r-- | doc/_themes/sphinx13/layout.html | 2 | ||||
-rw-r--r-- | doc/extdev/deprecated.rst | 10 | ||||
-rw-r--r-- | doc/faq.rst | 5 | ||||
-rw-r--r-- | doc/usage/extensions/duration.rst | 11 | ||||
-rw-r--r-- | doc/usage/extensions/index.rst | 1 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | sphinx/builders/html.py | 2 | ||||
-rw-r--r-- | sphinx/errors.py | 5 | ||||
-rw-r--r-- | sphinx/ext/duration.py | 96 | ||||
-rw-r--r-- | sphinx/io.py | 27 | ||||
-rw-r--r-- | sphinx/pycode/parser.py | 2 | ||||
-rw-r--r-- | sphinx/themes/basic/domainindex.html | 2 | ||||
-rw-r--r-- | sphinx/themes/basic/layout.html | 2 | ||||
-rw-r--r-- | sphinx/themes/basic/search.html | 6 | ||||
-rw-r--r-- | sphinx/themes/basic/searchbox.html | 2 | ||||
-rw-r--r-- | sphinx/themes/bizstyle/layout.html | 4 | ||||
-rw-r--r-- | sphinx/themes/classic/layout.html | 2 | ||||
-rw-r--r-- | sphinx/themes/scrolls/layout.html | 2 | ||||
-rw-r--r-- | sphinx/transforms/i18n.py | 5 | ||||
-rw-r--r-- | sphinx/util/__init__.py | 14 | ||||
-rw-r--r-- | tests/test_build_html.py | 4 | ||||
-rw-r--r-- | tests/test_ext_duration.py | 21 | ||||
-rw-r--r-- | tests/test_ext_math.py | 2 | ||||
-rw-r--r-- | tox.ini | 2 |
25 files changed, 220 insertions, 41 deletions
@@ -42,6 +42,36 @@ Incompatible changes Deprecated ---------- +* ``sphinx.io.FiletypeNotFoundError`` +* ``sphinx.io.get_filetype()`` + +Features added +-------------- + +* #6910: inheritance_diagram: Make the background of diagrams transparent +* #6446: duration: Add ``sphinx.ext.durations`` to inspect which documents slow + down the build + +Bugs fixed +---------- + +* #6925: html: Remove redundant type="text/javascript" from <script> elements + +Testing +-------- + +Release 2.3.1 (in development) +============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + Features added -------------- diff --git a/doc/_themes/sphinx13/layout.html b/doc/_themes/sphinx13/layout.html index df0b82bb8..8d1b04a14 100644 --- a/doc/_themes/sphinx13/layout.html +++ b/doc/_themes/sphinx13/layout.html @@ -30,7 +30,7 @@ .related { display: none; } {% endif %} </style> - <script type="text/javascript"> + <script> // intelligent scrolling of the sidebar content $(window).scroll(function() { var sb = $('.sphinxsidebarwrapper'); diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 482cb50bb..9fdaa2f96 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -26,6 +26,16 @@ The following is a list of deprecated interfaces. - (will be) Removed - Alternatives + * - ``sphinx.io.FiletypeNotFoundError`` + - 2.4 + - 4.0 + - ``sphinx.errors.FiletypeNotFoundError`` + + * - ``sphinx.io.get_filetype()`` + - 2.4 + - 4.0 + - ``sphinx.util.get_filetype()`` + * - ``sphinx.builders.gettext.POHEADER`` - 2.3 - 4.0 diff --git a/doc/faq.rst b/doc/faq.rst index ba16ceb9a..28d14d79e 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -89,7 +89,7 @@ Google Analytics {%- block extrahead %} {{ super() }} - <script type="text/javascript"> + <script> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'XXX account number XXX']); _gaq.push(['_trackPageview']); @@ -101,7 +101,7 @@ Google Analytics <div class="footer">This page uses <a href="https://analytics.google.com/"> Google Analytics</a> to collect statistics. You can disable it by blocking the JavaScript coming from www.google-analytics.com. - <script type="text/javascript"> + <script> (function() { var ga = document.createElement('script'); ga.src = ('https:' == document.location.protocol ? @@ -132,7 +132,6 @@ Google Search (function() { var cx = '......'; var gcse = document.createElement('script'); - gcse.type = 'text/javascript'; gcse.async = true; gcse.src = 'https://cse.google.com/cse.js?cx=' + cx; var s = document.getElementsByTagName('script')[0]; diff --git a/doc/usage/extensions/duration.rst b/doc/usage/extensions/duration.rst new file mode 100644 index 000000000..c57549984 --- /dev/null +++ b/doc/usage/extensions/duration.rst @@ -0,0 +1,11 @@ +:mod:`sphinx.ext.duration` -- Measure durations of Sphinx processing +==================================================================== + +.. module:: sphinx.ext.duration + :synopsis: Measure durations of Sphinx processing + +.. versionadded:: 2.3 + +This extension measures durations of Sphinx processing and show its +result at end of the build. It is useful for inspecting what document +is slowly built. diff --git a/doc/usage/extensions/index.rst b/doc/usage/extensions/index.rst index ae9639ee8..6aab8adb2 100644 --- a/doc/usage/extensions/index.rst +++ b/doc/usage/extensions/index.rst @@ -23,6 +23,7 @@ These extensions are built in and can be activated by respective entries in the autosummary coverage doctest + duration extlinks githubpages graphviz @@ -47,7 +47,7 @@ extras_require = { 'html5lib', 'flake8>=3.5.0', 'flake8-import-order', - 'mypy>=0.750', + 'mypy>=0.761', 'docutils-stubs', ], } diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 50c38cdb2..48b21fa4e 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -117,7 +117,6 @@ class JavaScript(str): self = str.__new__(cls, filename) # type: ignore self.filename = filename self.attributes = attributes - self.attributes.setdefault('type', 'text/javascript') return self @@ -1098,7 +1097,6 @@ def setup_js_tag_helper(app: Sphinx, pagename: str, templatexname: str, attrs.append('src="%s"' % pathto(js.filename, resource=True)) else: # str value (old styled) - attrs.append('type="text/javascript"') attrs.append('src="%s"' % pathto(js, resource=True)) return '<script %s>%s</script>' % (' '.join(attrs), body) diff --git a/sphinx/errors.py b/sphinx/errors.py index 7f8b1fbd5..64036721f 100644 --- a/sphinx/errors.py +++ b/sphinx/errors.py @@ -126,3 +126,8 @@ class PycodeError(Exception): class NoUri(Exception): """Raised by builder.get_relative_uri() if there is no URI available.""" pass + + +class FiletypeNotFoundError(Exception): + "Raised by get_filetype() if a filename matches no source suffix." + pass diff --git a/sphinx/ext/duration.py b/sphinx/ext/duration.py new file mode 100644 index 000000000..1286e49ec --- /dev/null +++ b/sphinx/ext/duration.py @@ -0,0 +1,96 @@ +""" + sphinx.ext.duration + ~~~~~~~~~~~~~~~~~~~ + + Measure durations of Sphinx processing. + + :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from datetime import datetime, timedelta +from itertools import islice +from operator import itemgetter +from typing import cast +from typing import Dict, List + +from docutils import nodes + +from sphinx.application import Sphinx +from sphinx.domains import Domain +from sphinx.locale import __ +from sphinx.util import logging + +logger = logging.getLogger(__name__) + + +class DurationDomain(Domain): + """A domain for durations of Sphinx processing.""" + name = 'duration' + + @property + def reading_durations(self) -> Dict[str, timedelta]: + return self.data.setdefault('reading_durations', {}) + + def note_reading_duration(self, duration: timedelta): + self.reading_durations[self.env.docname] = duration + + def clear(self) -> None: + self.reading_durations.clear() + + def clear_doc(self, docname: str) -> None: + self.reading_durations.pop(docname, None) + + def merge_domaindata(self, docnames: List[str], otherdata: Dict[str, timedelta]) -> None: + for docname, duration in otherdata.items(): + if docname in docnames: + self.reading_durations[docname] = duration + + +def on_builder_inited(app: Sphinx) -> None: + """Initialize DurationDomain on bootstrap. + + This clears results of last build. + """ + domain = cast(DurationDomain, app.env.get_domain('duration')) + domain.clear() + + +def on_source_read(app: Sphinx, docname: str, content: List[str]) -> None: + """Start to measure reading duration.""" + app.env.temp_data['started_at'] = datetime.now() + + +def on_doctree_read(app: Sphinx, doctree: nodes.document) -> None: + """Record a reading duration.""" + started_at = app.env.temp_data.get('started_at') + duration = datetime.now() - started_at + domain = cast(DurationDomain, app.env.get_domain('duration')) + domain.note_reading_duration(duration) + + +def on_build_finished(app: Sphinx, error): + """Display duration ranking on current build.""" + domain = cast(DurationDomain, app.env.get_domain('duration')) + durations = sorted(domain.reading_durations.items(), key=itemgetter(1), reverse=True) + if not durations: + return + + logger.info('') + logger.info(__('====================== slowest reading durations =======================')) + for docname, d in islice(durations, 5): + logger.info('%d.%03d %s', d.seconds, d.microseconds / 1000, docname) + + +def setup(app): + app.add_domain(DurationDomain) + app.connect('builder-inited', on_builder_inited) + app.connect('source-read', on_source_read) + app.connect('doctree-read', on_doctree_read) + app.connect('build-finished', on_build_finished) + + return { + 'version': 'builtin', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/sphinx/io.py b/sphinx/io.py index f3e72733e..624a04246 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -18,7 +18,8 @@ from docutils.readers import standalone from docutils.transforms.references import DanglingReferences from docutils.writers import UnfilteredWriter -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.errors import FiletypeNotFoundError +from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.transforms import ( AutoIndexUpgrader, DoctreeReadEvent, FigureAligner, SphinxTransformer ) @@ -26,7 +27,7 @@ from sphinx.transforms.i18n import ( PreserveTranslatableMessages, Locale, RemoveTranslatableInline, ) from sphinx.transforms.references import SphinxDomains -from sphinx.util import logging +from sphinx.util import logging, get_filetype from sphinx.util import UnicodeDecodeErrorHandler from sphinx.util.docutils import LoggingReporter from sphinx.versioning import UIDTransform @@ -192,20 +193,6 @@ class SphinxFileInput(FileInput): super().__init__(*args, **kwargs) -class FiletypeNotFoundError(Exception): - pass - - -def get_filetype(source_suffix, filename): - # type: (Dict[str, str], str) -> str - for suffix, filetype in source_suffix.items(): - if filename.endswith(suffix): - # If default filetype (None), considered as restructuredtext. - return filetype or 'restructuredtext' - else: - raise FiletypeNotFoundError - - def read_doc(app, env, filename): # type: (Sphinx, BuildEnvironment, str) -> nodes.document """Parse a document and convert to doctree.""" @@ -249,3 +236,11 @@ def read_doc(app, env, filename): pub.publish() return pub.document + + +deprecated_alias('sphinx.io', + { + 'FiletypeNotFoundError': FiletypeNotFoundError, + 'get_filetype': get_filetype, + }, + RemovedInSphinx40Warning) diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 4233089ff..cbae86c9e 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -129,7 +129,7 @@ class TokenProcessor: def __init__(self, buffers: List[str]) -> None: lines = iter(buffers) self.buffers = buffers - self.tokens = tokenize.generate_tokens(lambda: next(lines)) # type: ignore + self.tokens = tokenize.generate_tokens(lambda: next(lines)) self.current = None # type: Token self.previous = None # type: Token diff --git a/sphinx/themes/basic/domainindex.html b/sphinx/themes/basic/domainindex.html index 35c64ccb5..3cf1bc4e8 100644 --- a/sphinx/themes/basic/domainindex.html +++ b/sphinx/themes/basic/domainindex.html @@ -12,7 +12,7 @@ {% block extrahead %} {{ super() }} {% if not embedded and collapse_index %} - <script type="text/javascript"> + <script> DOCUMENTATION_OPTIONS.COLLAPSE_INDEX = true; </script> {% endif %} diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index 987aeeb6e..2c9e24930 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -87,7 +87,7 @@ {%- endmacro %} {%- macro script() %} - <script type="text/javascript" id="documentation_options" data-url_root="{{ pathto('', 1) }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script> + <script id="documentation_options" data-url_root="{{ pathto('', 1) }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script> {%- for js in script_files %} {{ js_tag(js) }} {%- endfor %} diff --git a/sphinx/themes/basic/search.html b/sphinx/themes/basic/search.html index a9b9960d2..3cda0feaa 100644 --- a/sphinx/themes/basic/search.html +++ b/sphinx/themes/basic/search.html @@ -11,16 +11,16 @@ {% set title = _('Search') %} {%- block scripts %} {{ super() }} - <script type="text/javascript" src="{{ pathto('_static/searchtools.js', 1) }}"></script> + <script src="{{ pathto('_static/searchtools.js', 1) }}"></script> {%- endblock %} {% block extrahead %} - <script type="text/javascript" src="{{ pathto('searchindex.js', 1) }}" defer></script> + <script src="{{ pathto('searchindex.js', 1) }}" defer></script> {{ super() }} {% endblock %} {% block body %} <h1 id="search-documentation">{{ _('Search') }}</h1> <div id="fallback" class="admonition warning"> - <script type="text/javascript">$('#fallback').hide();</script> + <script>$('#fallback').hide();</script> <p> {% trans %}Please activate JavaScript to enable the search functionality.{% endtrans %} diff --git a/sphinx/themes/basic/searchbox.html b/sphinx/themes/basic/searchbox.html index 6679ca6b5..8658f9759 100644 --- a/sphinx/themes/basic/searchbox.html +++ b/sphinx/themes/basic/searchbox.html @@ -17,5 +17,5 @@ </form> </div> </div> -<script type="text/javascript">$('#searchbox').show(0);</script> +<script>$('#searchbox').show(0);</script> {%- endif %} diff --git a/sphinx/themes/bizstyle/layout.html b/sphinx/themes/bizstyle/layout.html index d1deafde4..126bb59e9 100644 --- a/sphinx/themes/bizstyle/layout.html +++ b/sphinx/themes/bizstyle/layout.html @@ -11,7 +11,7 @@ {%- block scripts %} {{ super() }} - <script type="text/javascript" src="{{ pathto('_static/bizstyle.js', 1) }}"></script> + <script src="{{ pathto('_static/bizstyle.js', 1) }}"></script> {%- endblock %} {# put the sidebar before the body #} @@ -26,6 +26,6 @@ {%- block extrahead %} <meta name="viewport" content="width=device-width,initial-scale=1.0"> <!--[if lt IE 9]> - <script type="text/javascript" src="_static/css3-mediaqueries.js"></script> + <script src="_static/css3-mediaqueries.js"></script> <![endif]--> {%- endblock %} diff --git a/sphinx/themes/classic/layout.html b/sphinx/themes/classic/layout.html index a67a931c8..9f22787c5 100644 --- a/sphinx/themes/classic/layout.html +++ b/sphinx/themes/classic/layout.html @@ -12,6 +12,6 @@ {%- block scripts %} {{ super() }} {% if theme_collapsiblesidebar|tobool %} - <script type="text/javascript" src="{{ pathto('_static/sidebar.js', 1) }}"></script> + <script src="{{ pathto('_static/sidebar.js', 1) }}"></script> {% endif %} {%- endblock %} diff --git a/sphinx/themes/scrolls/layout.html b/sphinx/themes/scrolls/layout.html index 08f8970fc..11c571749 100644 --- a/sphinx/themes/scrolls/layout.html +++ b/sphinx/themes/scrolls/layout.html @@ -15,7 +15,7 @@ {%- endblock %} {%- block scripts %} {{ super() }} - <script type="text/javascript" src="{{ pathto('_static/theme_extras.js', 1) }}"></script> + <script src="{{ pathto('_static/theme_extras.js', 1) }}"></script> {%- endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index 6512fd43d..d1c125a7e 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -22,7 +22,7 @@ from sphinx.config import Config from sphinx.domains.std import make_glossary_term, split_term_classifiers from sphinx.locale import __, init as init_locale from sphinx.transforms import SphinxTransform -from sphinx.util import split_index_msg, logging +from sphinx.util import split_index_msg, logging, get_filetype from sphinx.util.i18n import docname_to_domain from sphinx.util.nodes import ( LITERAL_TYPE_NODES, IMAGE_TYPE_NODES, NodeMatcher, @@ -61,7 +61,8 @@ def publish_msgstr(app: "Sphinx", source: str, source_path: str, source_line: in from sphinx.io import SphinxI18nReader reader = SphinxI18nReader() reader.setup(app) - parser = app.registry.create_source_parser(app, 'restructuredtext') + filetype = get_filetype(config.source_suffix, source_path) + parser = app.registry.create_source_parser(app, filetype) doc = reader.read( source=StringInput(source=source, source_path="%s:%s:<translated>" % (source_path, source_line)), diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index d44290b7b..2032b94ac 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -29,7 +29,9 @@ from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, S from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode from sphinx.deprecation import RemovedInSphinx40Warning -from sphinx.errors import PycodeError, SphinxParallelError, ExtensionError +from sphinx.errors import ( + PycodeError, SphinxParallelError, ExtensionError, FiletypeNotFoundError +) from sphinx.locale import __ from sphinx.util import logging from sphinx.util.console import strip_colors, colorize, bold, term_width_line # type: ignore @@ -117,6 +119,16 @@ def get_matching_docs(dirname: str, suffixes: List[str], break +def get_filetype(source_suffix, filename): + # type: (Dict[str, str], str) -> str + for suffix, filetype in source_suffix.items(): + if filename.endswith(suffix): + # If default filetype (None), considered as restructuredtext. + return filetype or 'restructuredtext' + else: + raise FiletypeNotFoundError + + class FilenameUniqDict(dict): """ A dictionary that automatically generates unique names for its keys, diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 66164dd1c..b83bd5637 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -1216,8 +1216,8 @@ def test_html_assets(app): 'href="https://example.com/custom.css" />' in content) # html_js_files - assert '<script type="text/javascript" src="_static/js/custom.js"></script>' in content - assert ('<script async="async" type="text/javascript" src="https://example.com/script.js">' + assert '<script src="_static/js/custom.js"></script>' in content + assert ('<script async="async" src="https://example.com/script.js">' '</script>' in content) diff --git a/tests/test_ext_duration.py b/tests/test_ext_duration.py new file mode 100644 index 000000000..65ea1eef7 --- /dev/null +++ b/tests/test_ext_duration.py @@ -0,0 +1,21 @@ +""" + test_ext_duration + ~~~~~~~~~~~~~~~~~ + + Test sphinx.ext.duration extension. + + :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import pytest + + +@pytest.mark.sphinx('dummy', testroot='basic', + confoverrides={'extensions': ['sphinx.ext.duration']}) +def test_githubpages(app, status, warning): + app.build() + + assert 'slowest reading durations' in status.getvalue() + assert re.search('\\d+\\.\\d{3} index\n', status.getvalue()) diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index 7fbfd1477..32a50512a 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -70,7 +70,7 @@ def test_mathjax_options(app, status, warning): app.builder.build_all() content = (app.outdir / 'index.html').text() - assert ('<script async="async" integrity="sha384-0123456789" type="text/javascript" ' + assert ('<script async="async" integrity="sha384-0123456789" ' 'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?' 'config=TeX-AMS-MML_HTMLorMML"></script>' in content) @@ -15,7 +15,7 @@ deps = du13: docutils==0.13.1 du14: docutils==0.14 du15: docutils==0.15 - du16: docutils==0.16b0.dev0 + du16: docutils==0.16rc1 extras = test setenv = |