Merge branch '2.0'

This commit is contained in:
Takeshi KOMIYA 2019-12-21 12:21:49 +09:00
commit d47f38e22d
25 changed files with 220 additions and 41 deletions

30
CHANGES
View File

@ -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
--------------

View File

@ -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');

View File

@ -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

View File

@ -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];

View File

@ -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.

View File

@ -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

View File

@ -47,7 +47,7 @@ extras_require = {
'html5lib',
'flake8>=3.5.0',
'flake8-import-order',
'mypy>=0.750',
'mypy>=0.761',
'docutils-stubs',
],
}

View File

@ -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)

View File

@ -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

96
sphinx/ext/duration.py Normal file
View File

@ -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,
}

View File

@ -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)

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -17,5 +17,5 @@
</form>
</div>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
<script>$('#searchbox').show(0);</script>
{%- endif %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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)),

View File

@ -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,

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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 =