mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '1.6-release'
This commit is contained in:
28
CHANGES
28
CHANGES
@@ -16,7 +16,7 @@ Bugs fixed
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 1.6 beta2 (in development)
|
||||
Release 1.6 beta3 (in development)
|
||||
==================================
|
||||
|
||||
Incompatible changes
|
||||
@@ -34,6 +34,27 @@ Bugs fixed
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 1.6 beta2 (released Apr 29, 2017)
|
||||
=========================================
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
* #3345: Replace the custom smartypants code with docutils' smart_quotes
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
* #3662: ``builder.css_files`` is deprecated. Please use ``add_stylesheet()``
|
||||
API instead.
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #3661: sphinx-build crashes on parallel build
|
||||
* #3669: gettext builder fails with "ValueError: substring not found"
|
||||
* #3660: Sphinx always depends on sphinxcontrib-websupport and its dependencies
|
||||
|
||||
Release 1.6 beta1 (released Apr 24, 2017)
|
||||
=========================================
|
||||
|
||||
@@ -226,6 +247,11 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #3614: Sphinx crashes with requests-2.5.0
|
||||
* #3618: autodoc crashes with tupled arguments
|
||||
* #3664: No space after the bullet in items of a latex list produced by Sphinx
|
||||
* #3657: EPUB builder crashes if document startswith genindex exists
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
|
||||
1
Makefile
1
Makefile
@@ -64,6 +64,7 @@ clean-backupfiles:
|
||||
|
||||
clean-generated:
|
||||
find . -name '.DS_Store' -exec rm -f {} +
|
||||
rm -rf Sphinx.egg-info/
|
||||
rm -rf doc/_build/
|
||||
rm -f sphinx/pycode/*.pickle
|
||||
rm -f utils/*3.py*
|
||||
|
||||
2
doc/_templates/indexsidebar.html
vendored
2
doc/_templates/indexsidebar.html
vendored
@@ -3,7 +3,7 @@
|
||||
{%trans%}project{%endtrans%}</p>
|
||||
|
||||
<h3>Download</h3>
|
||||
{% if version.endswith('a0') %}
|
||||
{% if version.endswith('+') %}
|
||||
<p>{%trans%}This documentation is for version <b><a href="changes.html">{{ version }}</a></b>, which is
|
||||
not released yet.{%endtrans%}</p>
|
||||
<p>{%trans%}You can use it from the
|
||||
|
||||
@@ -16,7 +16,7 @@ exclude_patterns = ['_build']
|
||||
|
||||
project = 'Sphinx'
|
||||
copyright = '2007-2017, Georg Brandl and the Sphinx team'
|
||||
version = sphinx.__released__
|
||||
version = sphinx.__display_version__
|
||||
release = version
|
||||
show_authors = True
|
||||
|
||||
|
||||
@@ -771,6 +771,12 @@ that use Sphinx's HTMLWriter class.
|
||||
will be used to convert quotes and dashes to typographically correct
|
||||
entities. Default: ``True``.
|
||||
|
||||
.. deprecated:: 1.6
|
||||
Use the `smart_quotes option`_ in the Docutils configuration file
|
||||
(``docutils.conf``) instead.
|
||||
|
||||
.. _`smart_quotes option`: http://docutils.sourceforge.net/docs/user/config.html#smart-quotes
|
||||
|
||||
.. confval:: html_add_permalinks
|
||||
|
||||
Sphinx will add "permalinks" for each heading and description environment as
|
||||
|
||||
@@ -47,7 +47,9 @@ file :file:`blue.zip`, you can put it right in the directory containing
|
||||
html_theme_path = ["."]
|
||||
|
||||
The third form is a python package. If a theme you want to use is distributed
|
||||
as a python package, you can use it after installing::
|
||||
as a python package, you can use it after installing
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# installing theme package
|
||||
$ pip install sphinxjp.themes.dotted
|
||||
|
||||
4
setup.py
4
setup.py
@@ -51,7 +51,6 @@ requires = [
|
||||
'alabaster>=0.7,<0.8',
|
||||
'imagesize',
|
||||
'requests>=2.0.0',
|
||||
'sphinxcontrib-websupport',
|
||||
'typing',
|
||||
'setuptools',
|
||||
]
|
||||
@@ -60,6 +59,9 @@ extras_require = {
|
||||
':sys_platform=="win32"': [
|
||||
'colorama>=0.3.5',
|
||||
],
|
||||
'websupport': [
|
||||
'sphinxcontrib-websupport',
|
||||
],
|
||||
'test': [
|
||||
'pytest',
|
||||
'mock', # it would be better for 'test:python_version in 2.7'
|
||||
|
||||
@@ -25,6 +25,7 @@ except ImportError:
|
||||
Image = None
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.utils import smartquotes
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
@@ -32,7 +33,6 @@ from sphinx.util import logging
|
||||
from sphinx.util import status_iterator
|
||||
from sphinx.util.osutil import ensuredir, copyfile
|
||||
from sphinx.util.fileutil import copy_asset_file
|
||||
from sphinx.util.smartypants import sphinx_smarty_pants as ssp
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -94,6 +94,18 @@ Guide = namedtuple('Guide', ['type', 'title', 'uri'])
|
||||
NavPoint = namedtuple('NavPoint', ['navpoint', 'playorder', 'text', 'refuri', 'children'])
|
||||
|
||||
|
||||
def sphinx_smarty_pants(t, language='en'):
|
||||
# type: (unicode, str) -> unicode
|
||||
t = t.replace('"', '"')
|
||||
t = smartquotes.educateDashesOldSchool(t)
|
||||
t = smartquotes.educateQuotes(t, language)
|
||||
t = t.replace('"', '"')
|
||||
return t
|
||||
|
||||
|
||||
ssp = sphinx_smarty_pants
|
||||
|
||||
|
||||
# The epub publisher
|
||||
|
||||
class EpubBuilder(StandaloneHTMLBuilder):
|
||||
@@ -437,7 +449,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
|
||||
This method is overwritten for genindex pages in order to fix href link
|
||||
attributes.
|
||||
"""
|
||||
if pagename.startswith('genindex'):
|
||||
if pagename.startswith('genindex') and 'genindexentries' in addctx:
|
||||
if not self.use_index:
|
||||
return
|
||||
self.fix_genindex(addctx['genindexentries'])
|
||||
|
||||
@@ -194,14 +194,18 @@ ltz = LocalTimeZone()
|
||||
def should_write(filepath, new_content):
|
||||
if not path.exists(filepath):
|
||||
return True
|
||||
with open(filepath, 'r', encoding='utf-8') as oldpot: # type: ignore
|
||||
old_content = oldpot.read()
|
||||
old_header_index = old_content.index('"POT-Creation-Date:')
|
||||
new_header_index = new_content.index('"POT-Creation-Date:')
|
||||
old_body_index = old_content.index('"PO-Revision-Date:')
|
||||
new_body_index = new_content.index('"PO-Revision-Date:')
|
||||
return ((old_content[:old_header_index] != new_content[:new_header_index]) or
|
||||
(new_content[new_body_index:] != old_content[old_body_index:]))
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as oldpot: # type: ignore
|
||||
old_content = oldpot.read()
|
||||
old_header_index = old_content.index('"POT-Creation-Date:')
|
||||
new_header_index = new_content.index('"POT-Creation-Date:')
|
||||
old_body_index = old_content.index('"PO-Revision-Date:')
|
||||
new_body_index = new_content.index('"PO-Revision-Date:')
|
||||
return ((old_content[:old_header_index] != new_content[:new_header_index]) or
|
||||
(new_content[new_body_index:] != old_content[old_body_index:]))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import os
|
||||
import re
|
||||
import sys
|
||||
import codecs
|
||||
import warnings
|
||||
import posixpath
|
||||
from os import path
|
||||
from hashlib import md5
|
||||
@@ -38,6 +39,7 @@ from sphinx.util.docutils import is_html5_writer_available, __version_info__
|
||||
from sphinx.util.fileutil import copy_asset
|
||||
from sphinx.util.matching import patmatch, Matcher, DOTFILES
|
||||
from sphinx.config import string_classes
|
||||
from sphinx.deprecation import RemovedInSphinx20Warning
|
||||
from sphinx.locale import _, l_
|
||||
from sphinx.search import js_index
|
||||
from sphinx.theming import HTMLThemeFactory
|
||||
@@ -45,8 +47,7 @@ from sphinx.builders import Builder
|
||||
from sphinx.application import ENV_PICKLE_FILENAME
|
||||
from sphinx.highlighting import PygmentsBridge
|
||||
from sphinx.util.console import bold, darkgreen # type: ignore
|
||||
from sphinx.writers.html import HTMLWriter, HTMLTranslator, \
|
||||
SmartyPantsHTMLTranslator
|
||||
from sphinx.writers.html import HTMLWriter, HTMLTranslator
|
||||
from sphinx.environment.adapters.asset import ImageAdapter
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.environment.adapters.indexentries import IndexEntries
|
||||
@@ -59,7 +60,7 @@ if False:
|
||||
|
||||
# Experimental HTML5 Writer
|
||||
if is_html5_writer_available():
|
||||
from sphinx.writers.html5 import HTML5Translator, SmartyPantsHTML5Translator
|
||||
from sphinx.writers.html5 import HTML5Translator
|
||||
html5_ready = True
|
||||
else:
|
||||
html5_ready = False
|
||||
@@ -87,6 +88,38 @@ def get_stable_hash(obj):
|
||||
return md5(text_type(obj).encode('utf8')).hexdigest()
|
||||
|
||||
|
||||
class CSSContainer(list):
|
||||
"""The container of stylesheets.
|
||||
|
||||
To support the extensions which access the container directly, this wraps
|
||||
the entry with Stylesheet class.
|
||||
"""
|
||||
def append(self, obj):
|
||||
if isinstance(obj, Stylesheet):
|
||||
super(CSSContainer, self).append(obj)
|
||||
else:
|
||||
super(CSSContainer, self).append(Stylesheet(obj, None, 'stylesheet'))
|
||||
|
||||
def extend(self, other):
|
||||
warnings.warn('builder.css_files is deprecated. '
|
||||
'Please use app.add_stylesheet() instead.',
|
||||
RemovedInSphinx20Warning)
|
||||
for item in other:
|
||||
self.append(item)
|
||||
|
||||
def __iadd__(self, other):
|
||||
warnings.warn('builder.css_files is deprecated. '
|
||||
'Please use app.add_stylesheet() instead.',
|
||||
RemovedInSphinx20Warning)
|
||||
for item in other:
|
||||
self.append(item)
|
||||
|
||||
def __add__(self, other):
|
||||
ret = CSSContainer(self)
|
||||
ret += other
|
||||
return ret
|
||||
|
||||
|
||||
class Stylesheet(text_type):
|
||||
"""The metadata of stylesheet.
|
||||
|
||||
@@ -136,7 +169,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
script_files = ['_static/jquery.js', '_static/underscore.js',
|
||||
'_static/doctools.js'] # type: List[unicode]
|
||||
# Ditto for this one (Sphinx.add_stylesheet).
|
||||
css_files = [] # type: List[Dict[unicode, unicode]]
|
||||
css_files = CSSContainer() # type: List[Dict[unicode, unicode]]
|
||||
|
||||
imgpath = None # type: unicode
|
||||
domain_indices = [] # type: List[Tuple[unicode, Type[Index], List[Tuple[unicode, List[List[Union[unicode, int]]]]], bool]] # NOQA
|
||||
@@ -220,15 +253,9 @@ class StandaloneHTMLBuilder(Builder):
|
||||
@property
|
||||
def default_translator_class(self):
|
||||
if self.config.html_experimental_html5_writer and html5_ready:
|
||||
if self.config.html_use_smartypants:
|
||||
return SmartyPantsHTML5Translator
|
||||
else:
|
||||
return HTML5Translator
|
||||
return HTML5Translator
|
||||
else:
|
||||
if self.config.html_use_smartypants:
|
||||
return SmartyPantsHTMLTranslator
|
||||
else:
|
||||
return HTMLTranslator
|
||||
return HTMLTranslator
|
||||
|
||||
def get_outdated_docs(self):
|
||||
# type: () -> Iterator[unicode]
|
||||
@@ -1319,7 +1346,7 @@ def setup(app):
|
||||
app.add_config_value('html_static_path', [], 'html')
|
||||
app.add_config_value('html_extra_path', [], 'html')
|
||||
app.add_config_value('html_last_updated_fmt', None, 'html', string_classes)
|
||||
app.add_config_value('html_use_smartypants', True, 'html')
|
||||
app.add_config_value('html_use_smartypants', None, 'html')
|
||||
app.add_config_value('html_sidebars', {}, 'html')
|
||||
app.add_config_value('html_additional_pages', {}, 'html')
|
||||
app.add_config_value('html_domain_indices', True, 'html', [list])
|
||||
|
||||
@@ -20,12 +20,13 @@ import warnings
|
||||
from os import path
|
||||
from collections import defaultdict
|
||||
|
||||
from six import StringIO, itervalues, class_types, next
|
||||
from six import BytesIO, itervalues, class_types, next
|
||||
from six.moves import cPickle as pickle
|
||||
|
||||
from docutils.io import NullOutput
|
||||
from docutils.core import Publisher
|
||||
from docutils.utils import Reporter, get_source_line
|
||||
from docutils.utils.smartquotes import smartchars
|
||||
from docutils.parsers.rst import roles
|
||||
from docutils.parsers.rst.languages import en as english
|
||||
from docutils.frontend import OptionParser
|
||||
@@ -121,7 +122,7 @@ class BuildEnvironment(object):
|
||||
@classmethod
|
||||
def loads(cls, string, app=None):
|
||||
# type: (unicode, Sphinx) -> BuildEnvironment
|
||||
io = StringIO(string)
|
||||
io = BytesIO(string)
|
||||
return cls.load(io, app)
|
||||
|
||||
@classmethod
|
||||
@@ -156,7 +157,7 @@ class BuildEnvironment(object):
|
||||
@classmethod
|
||||
def dumps(cls, env):
|
||||
# type: (BuildEnvironment) -> unicode
|
||||
io = StringIO()
|
||||
io = BytesIO()
|
||||
cls.dump(env, io)
|
||||
return io.getvalue()
|
||||
|
||||
@@ -671,6 +672,16 @@ class BuildEnvironment(object):
|
||||
self.settings['trim_footnote_reference_space'] = \
|
||||
self.config.trim_footnote_reference_space
|
||||
self.settings['gettext_compact'] = self.config.gettext_compact
|
||||
language = (self.config.language or 'en').replace('_', '-')
|
||||
self.settings['language_code'] = language
|
||||
if self.config.html_use_smartypants is not None:
|
||||
warnings.warn("html_use_smartypants option is deprecated. Use the "
|
||||
"smart_quotes option in docutils.conf instead.",
|
||||
RemovedInSphinx17Warning)
|
||||
if language in smartchars.quotes:
|
||||
self.settings['smart_quotes'] = self.config.html_use_smartypants
|
||||
elif language in smartchars.quotes: # We enable smartypants by default
|
||||
self.settings['smart_quotes'] = True
|
||||
|
||||
docutilsconf = path.join(self.srcdir, 'docutils.conf')
|
||||
# read docutils.conf from source dir, not from current dir
|
||||
|
||||
@@ -473,10 +473,19 @@ def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
arg_fd = StringIO()
|
||||
arg_fd.write(format_arg_with_annotation(arg))
|
||||
if defaults and i >= defaults_start:
|
||||
arg_fd.write(' = ' if arg in annotations else '=')
|
||||
arg_fd.write(object_description(defaults[i - defaults_start])) # type: ignore
|
||||
if isinstance(arg, list):
|
||||
# support tupled arguments list (only for py2): def foo((x, y))
|
||||
arg_fd.write('(')
|
||||
arg_fd.write(format_arg_with_annotation(arg[0]))
|
||||
for param in arg[1:]:
|
||||
arg_fd.write(', ')
|
||||
arg_fd.write(format_arg_with_annotation(param))
|
||||
arg_fd.write(')')
|
||||
else:
|
||||
arg_fd.write(format_arg_with_annotation(arg))
|
||||
if defaults and i >= defaults_start:
|
||||
arg_fd.write(' = ' if arg in annotations else '=')
|
||||
arg_fd.write(object_description(defaults[i - defaults_start])) # type: ignore
|
||||
formatted.append(arg_fd.getvalue())
|
||||
|
||||
if varargs:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
%
|
||||
|
||||
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
|
||||
\ProvidesPackage{sphinx}[2017/03/26 v1.6 LaTeX package (Sphinx markup)]
|
||||
\ProvidesPackage{sphinx}[2017/04/25 v1.6 LaTeX package (Sphinx markup)]
|
||||
|
||||
% provides \ltx@ifundefined
|
||||
% (many packages load ltxcmds: graphicx does for pdftex and lualatex but
|
||||
@@ -1186,26 +1186,19 @@
|
||||
% {fulllineitems} is the main environment for object descriptions.
|
||||
%
|
||||
\newcommand{\py@itemnewline}[1]{%
|
||||
\kern\labelsep
|
||||
\@tempdima\linewidth
|
||||
\advance\@tempdima \leftmargin\makebox[\@tempdima][l]{#1}%
|
||||
\advance\@tempdima \labelwidth\makebox[\@tempdima][l]{#1}%
|
||||
\kern-\labelsep
|
||||
}
|
||||
|
||||
\newenvironment{fulllineitems}{
|
||||
\begin{list}{}{\labelwidth \leftmargin \labelsep \z@
|
||||
\newenvironment{fulllineitems}{%
|
||||
\begin{list}{}{\labelwidth \leftmargin
|
||||
\rightmargin \z@ \topsep -\parskip \partopsep \parskip
|
||||
\itemsep -\parsep
|
||||
\let\makelabel=\py@itemnewline}
|
||||
\let\makelabel=\py@itemnewline}%
|
||||
}{\end{list}}
|
||||
|
||||
% Redefine description environment so that it is usable inside fulllineitems.
|
||||
%
|
||||
% FIXME: use sphinxdescription, do not redefine description environment!
|
||||
\renewcommand{\description}{%
|
||||
\list{}{\labelwidth\z@
|
||||
\itemindent-\leftmargin
|
||||
\labelsep5pt\relax
|
||||
\let\makelabel=\descriptionlabel}}
|
||||
|
||||
% Signatures, possibly multi-line
|
||||
%
|
||||
\newlength{\py@argswidth}
|
||||
@@ -1395,10 +1388,6 @@
|
||||
\protected\def\sphinxstyleabbreviation {\textsc}
|
||||
\protected\def\sphinxstyleliteralintitle {\sphinxcode}
|
||||
|
||||
% LaTeX writer uses macros to hide double quotes from \sphinxcode's \@noligs
|
||||
\protected\def\sphinxquotedblleft{``}
|
||||
\protected\def\sphinxquotedblright{''}
|
||||
|
||||
% Tell TeX about pathological hyphenation cases:
|
||||
\hyphenation{Base-HTTP-Re-quest-Hand-ler}
|
||||
\endinput
|
||||
|
||||
@@ -34,6 +34,7 @@ from sphinx.util import logging
|
||||
from sphinx.util.console import strip_colors, colorize, bold, term_width_line # type: ignore
|
||||
from sphinx.util.fileutil import copy_asset_file
|
||||
from sphinx.util.osutil import fs_encoding
|
||||
from sphinx.util import smartypants # noqa
|
||||
|
||||
# import other utilities; partly for backwards compatibility, so don't
|
||||
# prune unused ones indiscriminately
|
||||
|
||||
@@ -36,6 +36,16 @@ except ImportError:
|
||||
# for requests < 2.4.0
|
||||
InsecureRequestWarning = None
|
||||
|
||||
try:
|
||||
from requests.packages.urllib3.exceptions import InsecurePlatformWarning
|
||||
except ImportError:
|
||||
try:
|
||||
# for Debian-jessie
|
||||
from urllib3.exceptions import InsecurePlatformWarning # type: ignore
|
||||
except ImportError:
|
||||
# for requests < 2.4.0
|
||||
InsecurePlatformWarning = None
|
||||
|
||||
# try to load requests[security] (but only if SSL is available)
|
||||
try:
|
||||
import ssl
|
||||
@@ -48,8 +58,8 @@ else:
|
||||
pkg_resources.VersionConflict):
|
||||
if not getattr(ssl, 'HAS_SNI', False):
|
||||
# don't complain on each url processed about the SSL issue
|
||||
requests.packages.urllib3.disable_warnings(
|
||||
requests.packages.urllib3.exceptions.InsecurePlatformWarning)
|
||||
if InsecurePlatformWarning:
|
||||
requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
|
||||
warnings.warn(
|
||||
'Some links may return broken results due to being unable to '
|
||||
'check the Server Name Indication (SNI) in the returned SSL cert '
|
||||
|
||||
@@ -1,312 +1,280 @@
|
||||
r"""
|
||||
This is based on SmartyPants.py by `Chad Miller`_ <smartypantspy@chad.org>,
|
||||
version 1.5_1.6.
|
||||
|
||||
Copyright and License
|
||||
=====================
|
||||
|
||||
SmartyPants_ license::
|
||||
|
||||
Copyright (c) 2003 John Gruber
|
||||
(http://daringfireball.net/)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the name "SmartyPants" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as
|
||||
is" and any express or implied warranties, including, but not limited
|
||||
to, the implied warranties of merchantability and fitness for a
|
||||
particular purpose are disclaimed. In no event shall the copyright
|
||||
owner or contributors be liable for any direct, indirect, incidental,
|
||||
special, exemplary, or consequential damages (including, but not
|
||||
limited to, procurement of substitute goods or services; loss of use,
|
||||
data, or profits; or business interruption) however caused and on any
|
||||
theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use
|
||||
of this software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
smartypants.py license::
|
||||
|
||||
smartypants.py is a derivative work of SmartyPants.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as
|
||||
is" and any express or implied warranties, including, but not limited
|
||||
to, the implied warranties of merchantability and fitness for a
|
||||
particular purpose are disclaimed. In no event shall the copyright
|
||||
owner or contributors be liable for any direct, indirect, incidental,
|
||||
special, exemplary, or consequential damages (including, but not
|
||||
limited to, procurement of substitute goods or services; loss of use,
|
||||
data, or profits; or business interruption) however caused and on any
|
||||
theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use
|
||||
of this software, even if advised of the possibility of such damage.
|
||||
|
||||
.. _Chad Miller: http://web.chad.org/
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sphinx.util.smartypants
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This code is copied from docutils’ docutils/utils/smartquotes.py
|
||||
version 1.7.1 (from 2017-03-19). It should be removed in the future.
|
||||
|
||||
:copyright: © 2010 Günter Milde,
|
||||
original `SmartyPants`_: © 2003 John Gruber
|
||||
smartypants.py: © 2004, 2007 Chad Miller
|
||||
:license: Released under the terms of the `2-Clause BSD license`_, in short:
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notices and this notice are preserved.
|
||||
This file is offered as-is, without any warranty.
|
||||
|
||||
.. _SmartyPants: http://daringfireball.net/projects/smartypants/
|
||||
.. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause
|
||||
|
||||
See the LICENSE file and the original docutils code for details.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import re
|
||||
from docutils.utils import smartquotes
|
||||
from sphinx.util.docutils import __version_info__ as docutils_version
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Tuple # NOQA
|
||||
if False: # For type annotation
|
||||
from typing import Iterable, Iterator, Tuple # NOQA
|
||||
|
||||
|
||||
def sphinx_smarty_pants(t):
|
||||
# type: (unicode) -> unicode
|
||||
t = t.replace('"', '"')
|
||||
t = educate_dashes_oldschool(t)
|
||||
t = educate_quotes(t) # type: ignore
|
||||
t = t.replace('"', '"')
|
||||
return t
|
||||
|
||||
|
||||
# Constants for quote education.
|
||||
punct_class = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]"""
|
||||
end_of_word_class = r"""[\s.,;:!?)]"""
|
||||
close_class = r"""[^\ \t\r\n\[\{\(\-]"""
|
||||
dec_dashes = r"""–|—"""
|
||||
|
||||
# Special case if the very first character is a quote
|
||||
# followed by punctuation at a non-word-break. Close the quotes by brute force:
|
||||
single_quote_start_re = re.compile(r"""^'(?=%s\\B)""" % (punct_class,))
|
||||
double_quote_start_re = re.compile(r"""^"(?=%s\\B)""" % (punct_class,))
|
||||
|
||||
# Special case for double sets of quotes, e.g.:
|
||||
# <p>He said, "'Quoted' words in a larger quote."</p>
|
||||
double_quote_sets_re = re.compile(r""""'(?=\w)""")
|
||||
single_quote_sets_re = re.compile(r"""'"(?=\w)""")
|
||||
|
||||
# Special case for decade abbreviations (the '80s):
|
||||
decade_abbr_re = re.compile(r"""\b'(?=\d{2}s)""")
|
||||
|
||||
# Get most opening double quotes:
|
||||
opening_double_quotes_regex = re.compile(r"""
|
||||
(
|
||||
\s | # a whitespace char, or
|
||||
  | # a non-breaking space entity, or
|
||||
-- | # dashes, or
|
||||
&[mn]dash; | # named dash entities
|
||||
%s | # or decimal entities
|
||||
&\#x201[34]; # or hex
|
||||
)
|
||||
" # the quote
|
||||
(?=\w) # followed by a word character
|
||||
""" % (dec_dashes,), re.VERBOSE)
|
||||
|
||||
# Double closing quotes:
|
||||
closing_double_quotes_regex = re.compile(r"""
|
||||
#(%s)? # character that indicates the quote should be closing
|
||||
"
|
||||
(?=%s)
|
||||
""" % (close_class, end_of_word_class), re.VERBOSE)
|
||||
|
||||
closing_double_quotes_regex_2 = re.compile(r"""
|
||||
(%s) # character that indicates the quote should be closing
|
||||
"
|
||||
""" % (close_class,), re.VERBOSE)
|
||||
|
||||
# Get most opening single quotes:
|
||||
opening_single_quotes_regex = re.compile(r"""
|
||||
(
|
||||
\s | # a whitespace char, or
|
||||
  | # a non-breaking space entity, or
|
||||
-- | # dashes, or
|
||||
&[mn]dash; | # named dash entities
|
||||
%s | # or decimal entities
|
||||
&\#x201[34]; # or hex
|
||||
)
|
||||
' # the quote
|
||||
(?=\w) # followed by a word character
|
||||
""" % (dec_dashes,), re.VERBOSE)
|
||||
|
||||
closing_single_quotes_regex = re.compile(r"""
|
||||
(%s)
|
||||
'
|
||||
(?!\s | s\b | \d)
|
||||
""" % (close_class,), re.VERBOSE)
|
||||
|
||||
closing_single_quotes_regex_2 = re.compile(r"""
|
||||
(%s)
|
||||
'
|
||||
(\s | s\b)
|
||||
""" % (close_class,), re.VERBOSE)
|
||||
|
||||
|
||||
def educate_quotes(s):
|
||||
# type: (str) -> str
|
||||
def educateQuotes(text, language='en'):
|
||||
# type: (unicode, unicode) -> unicode
|
||||
"""
|
||||
Parameter: String.
|
||||
|
||||
Returns: The string, with "educated" curly quote HTML entities.
|
||||
Parameter: - text string (unicode or bytes).
|
||||
- language (`BCP 47` language tag.)
|
||||
Returns: The `text`, with "educated" curly quote characters.
|
||||
|
||||
Example input: "Isn't this fun?"
|
||||
Example output: “Isn’t this fun?”
|
||||
Example output: “Isn’t this fun?“;
|
||||
"""
|
||||
|
||||
smart = smartquotes.smartchars(language)
|
||||
smart.apostrophe = u'’'
|
||||
|
||||
# oldtext = text
|
||||
punct_class = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]"""
|
||||
|
||||
# Special case if the very first character is a quote
|
||||
# followed by punctuation at a non-word-break. Close the quotes
|
||||
# by brute force:
|
||||
s = single_quote_start_re.sub("’", s)
|
||||
s = double_quote_start_re.sub("”", s)
|
||||
# followed by punctuation at a non-word-break.
|
||||
# Close the quotes by brute force:
|
||||
text = re.sub(r"""^'(?=%s\\B)""" % (punct_class,), smart.csquote, text)
|
||||
text = re.sub(r"""^"(?=%s\\B)""" % (punct_class,), smart.cpquote, text)
|
||||
|
||||
# Special case for double sets of quotes, e.g.:
|
||||
# <p>He said, "'Quoted' words in a larger quote."</p>
|
||||
s = double_quote_sets_re.sub("“‘", s)
|
||||
s = single_quote_sets_re.sub("‘“", s)
|
||||
text = re.sub(r""""'(?=\w)""", smart.opquote + smart.osquote, text)
|
||||
text = re.sub(r"""'"(?=\w)""", smart.osquote + smart.opquote, text)
|
||||
|
||||
# Special case for decade abbreviations (the '80s):
|
||||
s = decade_abbr_re.sub("’", s)
|
||||
text = re.sub(r"""\b'(?=\d{2}s)""", smart.csquote, text)
|
||||
|
||||
s = opening_single_quotes_regex.sub(r"\1‘", s)
|
||||
s = closing_single_quotes_regex.sub(r"\1’", s)
|
||||
s = closing_single_quotes_regex_2.sub(r"\1’\2", s)
|
||||
close_class = r"""[^\ \t\r\n\[\{\(\-]"""
|
||||
dec_dashes = r"""–|—"""
|
||||
|
||||
# Get most opening single quotes:
|
||||
opening_single_quotes_regex = re.compile(r"""
|
||||
(
|
||||
\s | # a whitespace char, or
|
||||
| # a non-breaking space entity, or
|
||||
-- | # dashes, or
|
||||
&[mn]dash; | # named dash entities
|
||||
%s | # or decimal entities
|
||||
&\#x201[34]; # or hex
|
||||
)
|
||||
' # the quote
|
||||
(?=\w) # followed by a word character
|
||||
""" % (dec_dashes,), re.VERBOSE | re.UNICODE)
|
||||
text = opening_single_quotes_regex.sub(r'\1' + smart.osquote, text)
|
||||
|
||||
# In many locales, single closing quotes are different from apostrophe:
|
||||
if smart.csquote != smart.apostrophe:
|
||||
apostrophe_regex = re.compile(r"(?<=(\w|\d))'(?=\w)", re.UNICODE)
|
||||
text = apostrophe_regex.sub(smart.apostrophe, text)
|
||||
|
||||
closing_single_quotes_regex = re.compile(r"""
|
||||
(%s)
|
||||
'
|
||||
(?!\s | # whitespace
|
||||
s\b |
|
||||
\d # digits ('80s)
|
||||
)
|
||||
""" % (close_class,), re.VERBOSE | re.UNICODE)
|
||||
text = closing_single_quotes_regex.sub(r'\1' + smart.csquote, text)
|
||||
|
||||
closing_single_quotes_regex = re.compile(r"""
|
||||
(%s)
|
||||
'
|
||||
(\s | s\b)
|
||||
""" % (close_class,), re.VERBOSE | re.UNICODE)
|
||||
text = closing_single_quotes_regex.sub(r'\1%s\2' % smart.csquote, text)
|
||||
|
||||
# Any remaining single quotes should be opening ones:
|
||||
s = s.replace("'", "‘")
|
||||
text = re.sub(r"""'""", smart.osquote, text)
|
||||
|
||||
s = opening_double_quotes_regex.sub(r"\1“", s)
|
||||
s = closing_double_quotes_regex.sub(r"”", s)
|
||||
s = closing_double_quotes_regex_2.sub(r"\1”", s)
|
||||
# Get most opening double quotes:
|
||||
opening_double_quotes_regex = re.compile(r"""
|
||||
(
|
||||
\s | # a whitespace char, or
|
||||
| # a non-breaking space entity, or
|
||||
-- | # dashes, or
|
||||
&[mn]dash; | # named dash entities
|
||||
%s | # or decimal entities
|
||||
&\#x201[34]; # or hex
|
||||
)
|
||||
" # the quote
|
||||
(?=\w) # followed by a word character
|
||||
""" % (dec_dashes,), re.VERBOSE)
|
||||
text = opening_double_quotes_regex.sub(r'\1' + smart.opquote, text)
|
||||
|
||||
# Double closing quotes:
|
||||
closing_double_quotes_regex = re.compile(r"""
|
||||
#(%s)? # character that indicates the quote should be closing
|
||||
"
|
||||
(?=\s)
|
||||
""" % (close_class,), re.VERBOSE)
|
||||
text = closing_double_quotes_regex.sub(smart.cpquote, text)
|
||||
|
||||
closing_double_quotes_regex = re.compile(r"""
|
||||
(%s) # character that indicates the quote should be closing
|
||||
"
|
||||
""" % (close_class,), re.VERBOSE)
|
||||
text = closing_double_quotes_regex.sub(r'\1' + smart.cpquote, text)
|
||||
|
||||
# Any remaining quotes should be opening ones.
|
||||
return s.replace('"', "“")
|
||||
text = re.sub(r'"', smart.opquote, text)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def educate_quotes_latex(s, dquotes=("``", "''")):
|
||||
# type: (str, Tuple[str, str]) -> unicode
|
||||
"""
|
||||
Parameter: String.
|
||||
|
||||
Returns: The string, with double quotes corrected to LaTeX quotes.
|
||||
|
||||
Example input: "Isn't this fun?"
|
||||
Example output: ``Isn't this fun?'';
|
||||
def educate_tokens(text_tokens, attr='1', language='en'):
|
||||
# type: (Iterable[Tuple[str, unicode]], unicode, unicode) -> Iterator
|
||||
"""Return iterator that "educates" the items of `text_tokens`.
|
||||
"""
|
||||
|
||||
# Special case if the very first character is a quote
|
||||
# followed by punctuation at a non-word-break. Close the quotes
|
||||
# by brute force:
|
||||
s = single_quote_start_re.sub("\x04", s)
|
||||
s = double_quote_start_re.sub("\x02", s)
|
||||
# Parse attributes:
|
||||
# 0 : do nothing
|
||||
# 1 : set all
|
||||
# 2 : set all, using old school en- and em- dash shortcuts
|
||||
# 3 : set all, using inverted old school en and em- dash shortcuts
|
||||
#
|
||||
# q : quotes
|
||||
# b : backtick quotes (``double'' only)
|
||||
# B : backtick quotes (``double'' and `single')
|
||||
# d : dashes
|
||||
# D : old school dashes
|
||||
# i : inverted old school dashes
|
||||
# e : ellipses
|
||||
# w : convert " entities to " for Dreamweaver users
|
||||
|
||||
# Special case for double sets of quotes, e.g.:
|
||||
# <p>He said, "'Quoted' words in a larger quote."</p>
|
||||
s = double_quote_sets_re.sub("\x01\x03", s)
|
||||
s = single_quote_sets_re.sub("\x03\x01", s)
|
||||
convert_quot = False # translate " entities into normal quotes?
|
||||
do_dashes = 0
|
||||
do_backticks = 0
|
||||
do_quotes = False
|
||||
do_ellipses = False
|
||||
do_stupefy = False
|
||||
|
||||
# Special case for decade abbreviations (the '80s):
|
||||
s = decade_abbr_re.sub("\x04", s)
|
||||
if attr == "0": # Do nothing.
|
||||
pass
|
||||
elif attr == "1": # Do everything, turn all options on.
|
||||
do_quotes = True
|
||||
do_backticks = 1
|
||||
do_dashes = 1
|
||||
do_ellipses = True
|
||||
elif attr == "2":
|
||||
# Do everything, turn all options on, use old school dash shorthand.
|
||||
do_quotes = True
|
||||
do_backticks = 1
|
||||
do_dashes = 2
|
||||
do_ellipses = True
|
||||
elif attr == "3":
|
||||
# Do everything, use inverted old school dash shorthand.
|
||||
do_quotes = True
|
||||
do_backticks = 1
|
||||
do_dashes = 3
|
||||
do_ellipses = True
|
||||
elif attr == "-1": # Special "stupefy" mode.
|
||||
do_stupefy = True
|
||||
else:
|
||||
if "q" in attr:
|
||||
do_quotes = True
|
||||
if "b" in attr:
|
||||
do_backticks = 1
|
||||
if "B" in attr:
|
||||
do_backticks = 2
|
||||
if "d" in attr:
|
||||
do_dashes = 1
|
||||
if "D" in attr:
|
||||
do_dashes = 2
|
||||
if "i" in attr:
|
||||
do_dashes = 3
|
||||
if "e" in attr:
|
||||
do_ellipses = True
|
||||
if "w" in attr:
|
||||
convert_quot = True
|
||||
|
||||
s = opening_single_quotes_regex.sub("\\1\x03", s)
|
||||
s = closing_single_quotes_regex.sub("\\1\x04", s)
|
||||
s = closing_single_quotes_regex_2.sub("\\1\x04\\2", s)
|
||||
prev_token_last_char = " "
|
||||
# Last character of the previous text token. Used as
|
||||
# context to curl leading quote characters correctly.
|
||||
|
||||
# Any remaining single quotes should be opening ones:
|
||||
s = s.replace("'", "\x03")
|
||||
for (ttype, text) in text_tokens:
|
||||
|
||||
s = opening_double_quotes_regex.sub("\\1\x01", s)
|
||||
s = closing_double_quotes_regex.sub("\x02", s)
|
||||
s = closing_double_quotes_regex_2.sub("\\1\x02", s)
|
||||
# skip HTML and/or XML tags as well as emtpy text tokens
|
||||
# without updating the last character
|
||||
if ttype == 'tag' or not text:
|
||||
yield text
|
||||
continue
|
||||
|
||||
# Any remaining quotes should be opening ones.
|
||||
s = s.replace('"', "\x01")
|
||||
# skip literal text (math, literal, raw, ...)
|
||||
if ttype == 'literal':
|
||||
prev_token_last_char = text[-1:]
|
||||
yield text
|
||||
continue
|
||||
|
||||
# Finally, replace all helpers with quotes.
|
||||
return s.replace("\x01", dquotes[0]).replace("\x02", dquotes[1]).\
|
||||
replace("\x03", "`").replace("\x04", "'")
|
||||
last_char = text[-1:] # Remember last char before processing.
|
||||
|
||||
text = smartquotes.processEscapes(text)
|
||||
|
||||
if convert_quot:
|
||||
text = re.sub('"', '"', text)
|
||||
|
||||
if do_dashes == 1:
|
||||
text = smartquotes.educateDashes(text)
|
||||
elif do_dashes == 2:
|
||||
text = smartquotes.educateDashesOldSchool(text)
|
||||
elif do_dashes == 3:
|
||||
text = smartquotes.educateDashesOldSchoolInverted(text)
|
||||
|
||||
if do_ellipses:
|
||||
text = smartquotes.educateEllipses(text)
|
||||
|
||||
# Note: backticks need to be processed before quotes.
|
||||
if do_backticks:
|
||||
text = smartquotes.educateBackticks(text, language)
|
||||
|
||||
if do_backticks == 2:
|
||||
text = smartquotes.educateSingleBackticks(text, language)
|
||||
|
||||
if do_quotes:
|
||||
# Replace plain quotes to prevent converstion to
|
||||
# 2-character sequence in French.
|
||||
context = prev_token_last_char.replace('"', ';').replace("'", ';')
|
||||
text = educateQuotes(context + text, language)[1:]
|
||||
|
||||
if do_stupefy:
|
||||
text = smartquotes.stupefyEntities(text, language)
|
||||
|
||||
# Remember last char as context for the next token
|
||||
prev_token_last_char = last_char
|
||||
|
||||
text = smartquotes.processEscapes(text, restore=True)
|
||||
|
||||
yield text
|
||||
|
||||
|
||||
def educate_backticks(s):
|
||||
# type: (unicode) -> unicode
|
||||
"""
|
||||
Parameter: String.
|
||||
Returns: The string, with ``backticks'' -style double quotes
|
||||
translated into HTML curly quote entities.
|
||||
Example input: ``Isn't this fun?''
|
||||
Example output: “Isn't this fun?”
|
||||
"""
|
||||
return s.replace("``", "“").replace("''", "”")
|
||||
if docutils_version < (0, 13, 2):
|
||||
# Monkey patch the old docutils versions to fix the issue mentioned
|
||||
# at https://sourceforge.net/p/docutils/bugs/313/
|
||||
smartquotes.educateQuotes = educateQuotes
|
||||
|
||||
# And the one mentioned at https://sourceforge.net/p/docutils/bugs/317/
|
||||
smartquotes.educate_tokens = educate_tokens
|
||||
|
||||
def educate_single_backticks(s):
|
||||
# type: (unicode) -> unicode
|
||||
"""
|
||||
Parameter: String.
|
||||
Returns: The string, with `backticks' -style single quotes
|
||||
translated into HTML curly quote entities.
|
||||
|
||||
Example input: `Isn't this fun?'
|
||||
Example output: ‘Isn’t this fun?’
|
||||
"""
|
||||
return s.replace('`', "‘").replace("'", "’")
|
||||
|
||||
|
||||
def educate_dashes_oldschool(s):
|
||||
# type: (unicode) -> unicode
|
||||
"""
|
||||
Parameter: String.
|
||||
|
||||
Returns: The string, with each instance of "--" translated to
|
||||
an en-dash HTML entity, and each "---" translated to
|
||||
an em-dash HTML entity.
|
||||
"""
|
||||
return s.replace('---', "—").replace('--', "–")
|
||||
|
||||
|
||||
def educate_dashes_oldschool_inverted(s):
|
||||
# type: (unicode) -> unicode
|
||||
"""
|
||||
Parameter: String.
|
||||
|
||||
Returns: The string, with each instance of "--" translated to
|
||||
an em-dash HTML entity, and each "---" translated to
|
||||
an en-dash HTML entity. Two reasons why: First, unlike the
|
||||
en- and em-dash syntax supported by
|
||||
educate_dashes_oldschool(), it's compatible with existing
|
||||
entries written before SmartyPants 1.1, back when "--" was
|
||||
only used for em-dashes. Second, em-dashes are more
|
||||
common than en-dashes, and so it sort of makes sense that
|
||||
the shortcut should be shorter to type. (Thanks to Aaron
|
||||
Swartz for the idea.)
|
||||
"""
|
||||
return s.replace('---', "–").replace('--', "—")
|
||||
|
||||
|
||||
def educate_ellipses(s):
|
||||
# type: (unicode) -> unicode
|
||||
"""
|
||||
Parameter: String.
|
||||
Returns: The string, with each instance of "..." translated to
|
||||
an ellipsis HTML entity.
|
||||
|
||||
Example input: Huh...?
|
||||
Example output: Huh…?
|
||||
"""
|
||||
return s.replace('...', "…").replace('. . .', "…")
|
||||
# Fix the issue with French quotes mentioned at
|
||||
# https://sourceforge.net/p/docutils/mailman/message/35760696/
|
||||
quotes = smartquotes.smartchars.quotes
|
||||
quotes['fr'] = (u'«\u00a0', u'\u00a0»', u'“', u'”')
|
||||
quotes['fr-ch'] = u'«»‹›'
|
||||
|
||||
@@ -12,11 +12,16 @@
|
||||
import warnings
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx20Warning
|
||||
from sphinxcontrib.websupport import WebSupport # NOQA
|
||||
from sphinxcontrib.websupport import errors # NOQA
|
||||
from sphinxcontrib.websupport.search import BaseSearch, SEARCH_ADAPTERS # NOQA
|
||||
from sphinxcontrib.websupport.storage import StorageBackend # NOQA
|
||||
|
||||
warnings.warn('sphinx.websupport module is now provided as sphinxcontrib.webuspport. '
|
||||
'sphinx.websupport will be removed in Sphinx-2.0. Please use it instaed',
|
||||
RemovedInSphinx20Warning)
|
||||
try:
|
||||
from sphinxcontrib.websupport import WebSupport # NOQA
|
||||
from sphinxcontrib.websupport import errors # NOQA
|
||||
from sphinxcontrib.websupport.search import BaseSearch, SEARCH_ADAPTERS # NOQA
|
||||
from sphinxcontrib.websupport.storage import StorageBackend # NOQA
|
||||
|
||||
warnings.warn('sphinx.websupport module is now provided as sphinxcontrib-webuspport. '
|
||||
'sphinx.websupport will be removed in Sphinx-2.0. Please use it instaed',
|
||||
RemovedInSphinx20Warning)
|
||||
except ImportError:
|
||||
warnings.warn('Since Sphinx-1.6, sphinx.websupport module is now separated to '
|
||||
'sphinxcontrib-webuspport package. Please add it into your dependency list.')
|
||||
|
||||
@@ -22,7 +22,6 @@ from sphinx import addnodes
|
||||
from sphinx.locale import admonitionlabels, _
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.images import get_image_size
|
||||
from sphinx.util.smartypants import sphinx_smarty_pants
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -74,7 +73,6 @@ class HTMLTranslator(BaseTranslator):
|
||||
# type: (StandaloneHTMLBuilder, Any, Any) -> None
|
||||
BaseTranslator.__init__(self, *args, **kwds)
|
||||
self.highlighter = builder.highlighter
|
||||
self.no_smarty = 0
|
||||
self.builder = builder
|
||||
self.highlightlang = self.highlightlang_base = \
|
||||
builder.config.highlight_language
|
||||
@@ -685,10 +683,6 @@ class HTMLTranslator(BaseTranslator):
|
||||
BaseTranslator.visit_option_group(self, node)
|
||||
self.context[-2] = self.context[-2].replace(' ', ' ')
|
||||
|
||||
def bulk_text_processor(self, text):
|
||||
# type: (unicode) -> unicode
|
||||
return text
|
||||
|
||||
# overwritten
|
||||
def visit_Text(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
@@ -710,8 +704,6 @@ class HTMLTranslator(BaseTranslator):
|
||||
else:
|
||||
if self.in_mailto and self.settings.cloak_email_addresses:
|
||||
encoded = self.cloak_email(encoded)
|
||||
else:
|
||||
encoded = self.bulk_text_processor(encoded)
|
||||
self.body.append(encoded)
|
||||
|
||||
def visit_note(self, node):
|
||||
@@ -786,7 +778,6 @@ class HTMLTranslator(BaseTranslator):
|
||||
# type: (nodes.Node) -> None
|
||||
self.depart_admonition(node)
|
||||
|
||||
# these are only handled specially in the SmartyPantsHTMLTranslator
|
||||
def visit_literal_emphasis(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
return self.visit_emphasis(node)
|
||||
@@ -875,94 +866,3 @@ class HTMLTranslator(BaseTranslator):
|
||||
def unknown_visit(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
|
||||
|
||||
|
||||
class SmartyPantsHTMLTranslator(HTMLTranslator):
|
||||
"""
|
||||
Handle ordinary text via smartypants, converting quotes and dashes
|
||||
to the correct entities.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
# type: (Any, Any) -> None
|
||||
self.no_smarty = 0
|
||||
HTMLTranslator.__init__(self, *args, **kwds)
|
||||
|
||||
def visit_literal(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
try:
|
||||
# this raises SkipNode
|
||||
HTMLTranslator.visit_literal(self, node)
|
||||
finally:
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_literal_block(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
try:
|
||||
HTMLTranslator.visit_literal_block(self, node)
|
||||
except nodes.SkipNode:
|
||||
# HTMLTranslator raises SkipNode for simple literal blocks,
|
||||
# but not for parsed literal blocks
|
||||
self.no_smarty -= 1
|
||||
raise
|
||||
|
||||
def depart_literal_block(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
HTMLTranslator.depart_literal_block(self, node)
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_literal_emphasis(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
self.visit_emphasis(node)
|
||||
|
||||
def depart_literal_emphasis(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.depart_emphasis(node)
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_literal_strong(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
self.visit_strong(node)
|
||||
|
||||
def depart_literal_strong(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.depart_strong(node)
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_desc_signature(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
HTMLTranslator.visit_desc_signature(self, node)
|
||||
|
||||
def depart_desc_signature(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty -= 1
|
||||
HTMLTranslator.depart_desc_signature(self, node)
|
||||
|
||||
def visit_productionlist(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
try:
|
||||
HTMLTranslator.visit_productionlist(self, node)
|
||||
finally:
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_option(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
HTMLTranslator.visit_option(self, node)
|
||||
|
||||
def depart_option(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty -= 1
|
||||
HTMLTranslator.depart_option(self, node)
|
||||
|
||||
def bulk_text_processor(self, text):
|
||||
# type: (unicode) -> unicode
|
||||
if self.no_smarty <= 0:
|
||||
return sphinx_smarty_pants(text)
|
||||
return text
|
||||
|
||||
@@ -21,7 +21,6 @@ from sphinx import addnodes
|
||||
from sphinx.locale import admonitionlabels, _
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.images import get_image_size
|
||||
from sphinx.util.smartypants import sphinx_smarty_pants
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -44,7 +43,6 @@ class HTML5Translator(BaseTranslator):
|
||||
# type: (StandaloneHTMLBuilder, Any, Any) -> None
|
||||
BaseTranslator.__init__(self, *args, **kwds)
|
||||
self.highlighter = builder.highlighter
|
||||
self.no_smarty = 0
|
||||
self.builder = builder
|
||||
self.highlightlang = self.highlightlang_base = \
|
||||
builder.config.highlight_language
|
||||
@@ -628,10 +626,6 @@ class HTML5Translator(BaseTranslator):
|
||||
# type: (nodes.Node) -> None
|
||||
self.body.append('</td>')
|
||||
|
||||
def bulk_text_processor(self, text):
|
||||
# type: (unicode) -> unicode
|
||||
return text
|
||||
|
||||
# overwritten
|
||||
def visit_Text(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
@@ -653,8 +647,6 @@ class HTML5Translator(BaseTranslator):
|
||||
else:
|
||||
if self.in_mailto and self.settings.cloak_email_addresses:
|
||||
encoded = self.cloak_email(encoded)
|
||||
else:
|
||||
encoded = self.bulk_text_processor(encoded)
|
||||
self.body.append(encoded)
|
||||
|
||||
def visit_note(self, node):
|
||||
@@ -729,7 +721,6 @@ class HTML5Translator(BaseTranslator):
|
||||
# type: (nodes.Node) -> None
|
||||
self.depart_admonition(node)
|
||||
|
||||
# these are only handled specially in the SmartyPantsHTML5Translator
|
||||
def visit_literal_emphasis(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
return self.visit_emphasis(node)
|
||||
@@ -830,94 +821,3 @@ class HTML5Translator(BaseTranslator):
|
||||
def unknown_visit(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
|
||||
|
||||
|
||||
class SmartyPantsHTML5Translator(HTML5Translator):
|
||||
"""
|
||||
Handle ordinary text via smartypants, converting quotes and dashes
|
||||
to the correct entities.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
# type: (Any, Any) -> None
|
||||
self.no_smarty = 0
|
||||
HTML5Translator.__init__(self, *args, **kwds)
|
||||
|
||||
def visit_literal(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
try:
|
||||
# this raises SkipNode
|
||||
HTML5Translator.visit_literal(self, node)
|
||||
finally:
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_literal_block(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
try:
|
||||
HTML5Translator.visit_literal_block(self, node)
|
||||
except nodes.SkipNode:
|
||||
# HTML5Translator raises SkipNode for simple literal blocks,
|
||||
# but not for parsed literal blocks
|
||||
self.no_smarty -= 1
|
||||
raise
|
||||
|
||||
def depart_literal_block(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
HTML5Translator.depart_literal_block(self, node)
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_literal_emphasis(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
self.visit_emphasis(node)
|
||||
|
||||
def depart_literal_emphasis(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.depart_emphasis(node)
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_literal_strong(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
self.visit_strong(node)
|
||||
|
||||
def depart_literal_strong(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.depart_strong(node)
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_desc_signature(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
HTML5Translator.visit_desc_signature(self, node)
|
||||
|
||||
def depart_desc_signature(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty -= 1
|
||||
HTML5Translator.depart_desc_signature(self, node)
|
||||
|
||||
def visit_productionlist(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
try:
|
||||
HTML5Translator.visit_productionlist(self, node)
|
||||
finally:
|
||||
self.no_smarty -= 1
|
||||
|
||||
def visit_option(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty += 1
|
||||
HTML5Translator.visit_option(self, node)
|
||||
|
||||
def depart_option(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
self.no_smarty -= 1
|
||||
HTML5Translator.depart_option(self, node)
|
||||
|
||||
def bulk_text_processor(self, text):
|
||||
# type: (unicode) -> unicode
|
||||
if self.no_smarty <= 0:
|
||||
return sphinx_smarty_pants(text)
|
||||
return text
|
||||
|
||||
@@ -30,7 +30,6 @@ from sphinx.util.i18n import format_date
|
||||
from sphinx.util.nodes import clean_astext, traverse_parent
|
||||
from sphinx.util.template import LaTeXRenderer
|
||||
from sphinx.util.texescape import tex_escape_map, tex_replace_map
|
||||
from sphinx.util.smartypants import educate_quotes_latex
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -2533,10 +2532,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
||||
def visit_Text(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
text = self.encode(node.astext())
|
||||
if not self.no_contractions and not self.in_parsed_literal:
|
||||
text = educate_quotes_latex(text, # type: ignore
|
||||
dquotes=("\\sphinxquotedblleft{}",
|
||||
"\\sphinxquotedblright{}"))
|
||||
self.body.append(text)
|
||||
|
||||
def depart_Text(self, node):
|
||||
|
||||
@@ -426,6 +426,26 @@ More domains:
|
||||
.. default-role::
|
||||
|
||||
|
||||
Smart quotes
|
||||
------------
|
||||
|
||||
* Smart "quotes" in English 'text'.
|
||||
* Smart --- long and -- short dashes.
|
||||
* Ellipsis...
|
||||
* No smartypants in literal blocks: ``foo--"bar"...``.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. LaTeX does not like Cyrillic letters in this test, so it is HTML only.
|
||||
|
||||
.. rst-class:: language-ru
|
||||
|
||||
Этот "абзац" должен использовать 'русские' кавычки.
|
||||
|
||||
.. rst-class:: language-fr
|
||||
|
||||
Il dit : "C'est 'super' !"
|
||||
|
||||
.. rubric:: Footnotes
|
||||
|
||||
.. [#] Like footnotes.
|
||||
|
||||
@@ -24,6 +24,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(testroot, os.path.pardir)))
|
||||
# filter warnings of test dependencies
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning, module='site') # virtualenv
|
||||
warnings.filterwarnings('ignore', category=ImportWarning, module='backports')
|
||||
warnings.filterwarnings('ignore', category=ImportWarning, module='pkgutil')
|
||||
warnings.filterwarnings('ignore', category=ImportWarning, module='pytest_cov')
|
||||
warnings.filterwarnings('ignore', category=PendingDeprecationWarning, module=r'_pytest\..*')
|
||||
|
||||
|
||||
@@ -28,15 +28,6 @@ def test_html_translator(app, status, warning):
|
||||
# no set_translator()
|
||||
translator_class = app.builder.get_translator_class()
|
||||
assert translator_class
|
||||
assert translator_class.__name__ == 'SmartyPantsHTMLTranslator'
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', confoverrides={
|
||||
'html_use_smartypants': False})
|
||||
def test_html_with_smartypants(app, status, warning):
|
||||
# no set_translator(), html_use_smartypants=False
|
||||
translator_class = app.builder.get_translator_class()
|
||||
assert translator_class
|
||||
assert translator_class.__name__ == 'HTMLTranslator'
|
||||
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ def test_nested_toc(app):
|
||||
app.build()
|
||||
|
||||
# toc.ncx
|
||||
toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').text())
|
||||
toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').bytes())
|
||||
assert toc.find("./ncx:docTitle/ncx:text").text == 'Python documentation'
|
||||
|
||||
# toc.ncx / navPoint
|
||||
@@ -175,7 +175,7 @@ def test_nested_toc(app):
|
||||
navpoints = toc.findall("./ncx:navMap/ncx:navPoint")
|
||||
assert len(navpoints) == 4
|
||||
assert navinfo(navpoints[0]) == ('navPoint1', '1', 'index.xhtml',
|
||||
"Welcome to Sphinx Tests's documentation!")
|
||||
u"Welcome to Sphinx Tests’s documentation!")
|
||||
assert navpoints[0].findall("./ncx:navPoint") == []
|
||||
|
||||
# toc.ncx / nested navPoints
|
||||
@@ -192,11 +192,11 @@ def test_nested_toc(app):
|
||||
anchor = elem.find("./xhtml:a")
|
||||
return (anchor.get('href'), anchor.text)
|
||||
|
||||
nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').text())
|
||||
nav = EPUBElementTree.fromstring((app.outdir / 'nav.xhtml').bytes())
|
||||
toc = nav.findall("./xhtml:body/xhtml:nav/xhtml:ol/xhtml:li")
|
||||
assert len(toc) == 4
|
||||
assert navinfo(toc[0]) == ('index.xhtml',
|
||||
"Welcome to Sphinx Tests's documentation!")
|
||||
u"Welcome to Sphinx Tests’s documentation!")
|
||||
assert toc[0].findall("./xhtml:ol") == []
|
||||
|
||||
# nav.xhtml / nested toc
|
||||
|
||||
@@ -297,6 +297,13 @@ def test_static_output(app):
|
||||
# tests for ``any`` role
|
||||
(".//a[@href='#with']/span", 'headings'),
|
||||
(".//a[@href='objects.html#func_without_body']/code/span", 'objects'),
|
||||
# tests for smartypants
|
||||
(".//li", u'Smart “quotes” in English ‘text’.'),
|
||||
(".//li", u'Smart — long and – short dashes.'),
|
||||
(".//li", u'Ellipsis…'),
|
||||
(".//li//code//span[@class='pre']", 'foo--"bar"...'),
|
||||
(".//p", u'Этот «абзац» должен использовать „русские“ кавычки.'),
|
||||
(".//p", u'Il dit : « C’est “super” ! »'),
|
||||
],
|
||||
'objects.html': [
|
||||
(".//dt[@id='mod.Cls.meth1']", ''),
|
||||
|
||||
@@ -36,7 +36,7 @@ def test_process_doc(app):
|
||||
list_item)])
|
||||
|
||||
assert_node(toctree[0][0],
|
||||
[compact_paragraph, reference, "Welcome to Sphinx Tests's documentation!"])
|
||||
[compact_paragraph, reference, u"Welcome to Sphinx Tests’s documentation!"])
|
||||
assert_node(toctree[0][0][0], reference, anchorname='')
|
||||
assert_node(toctree[0][1][0], addnodes.toctree,
|
||||
caption="Table of Contents", glob=False, hidden=False,
|
||||
@@ -150,7 +150,7 @@ def test_get_toc_for(app):
|
||||
addnodes.toctree)])],
|
||||
[list_item, compact_paragraph])]) # [2][0]
|
||||
assert_node(toctree[0][0],
|
||||
[compact_paragraph, reference, "Welcome to Sphinx Tests's documentation!"])
|
||||
[compact_paragraph, reference, u"Welcome to Sphinx Tests’s documentation!"])
|
||||
assert_node(toctree[0][1][2],
|
||||
([compact_paragraph, reference, "subsection"],
|
||||
[bullet_list, list_item, compact_paragraph, reference, "subsubsection"]))
|
||||
@@ -177,7 +177,7 @@ def test_get_toc_for_only(app):
|
||||
addnodes.toctree)])],
|
||||
[list_item, compact_paragraph])]) # [2][0]
|
||||
assert_node(toctree[0][0],
|
||||
[compact_paragraph, reference, "Welcome to Sphinx Tests's documentation!"])
|
||||
[compact_paragraph, reference, u"Welcome to Sphinx Tests’s documentation!"])
|
||||
assert_node(toctree[0][1][1],
|
||||
([compact_paragraph, reference, "Section for HTML"],
|
||||
[bullet_list, addnodes.toctree]))
|
||||
|
||||
@@ -14,11 +14,12 @@ import pickle
|
||||
|
||||
from docutils import frontend, utils, nodes
|
||||
from docutils.parsers.rst import Parser as RstParser
|
||||
from docutils.transforms.universal import SmartQuotes
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.util import texescape
|
||||
from sphinx.util.docutils import sphinx_domains
|
||||
from sphinx.writers.html import HTMLWriter, SmartyPantsHTMLTranslator
|
||||
from sphinx.writers.html import HTMLWriter, HTMLTranslator
|
||||
from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator
|
||||
import pytest
|
||||
|
||||
@@ -31,6 +32,7 @@ def settings(app):
|
||||
optparser = frontend.OptionParser(
|
||||
components=(RstParser, HTMLWriter, LaTeXWriter))
|
||||
settings = optparser.get_default_values()
|
||||
settings.smart_quotes = True
|
||||
settings.env = app.builder.env
|
||||
settings.env.temp_data['docname'] = 'dummy'
|
||||
domain_context = sphinx_domains(settings.env)
|
||||
@@ -46,6 +48,7 @@ def parse(settings):
|
||||
document['file'] = 'dummy'
|
||||
parser = RstParser()
|
||||
parser.parse(rst, document)
|
||||
SmartQuotes(document, startnode=None).apply()
|
||||
for msg in document.traverse(nodes.system_message):
|
||||
if msg['level'] == 1:
|
||||
msg.replace_self([])
|
||||
@@ -62,7 +65,7 @@ class ForgivingTranslator:
|
||||
pass
|
||||
|
||||
|
||||
class ForgivingHTMLTranslator(SmartyPantsHTMLTranslator, ForgivingTranslator):
|
||||
class ForgivingHTMLTranslator(HTMLTranslator, ForgivingTranslator):
|
||||
pass
|
||||
|
||||
|
||||
@@ -178,8 +181,8 @@ def get_verifier(verify, verify_re):
|
||||
# verify smarty-pants quotes
|
||||
'verify',
|
||||
'"John"',
|
||||
'<p>“John”</p>',
|
||||
r'\sphinxquotedblleft{}John\sphinxquotedblright{}',
|
||||
u'<p>“John”</p>',
|
||||
u"“John”",
|
||||
),
|
||||
(
|
||||
# ... but not in literal text
|
||||
|
||||
@@ -34,14 +34,14 @@ def test_docinfo(app, status, warning):
|
||||
'field name': u'This is a generic bibliographic field.',
|
||||
'field name 2': (u'Generic bibliographic fields may contain multiple '
|
||||
u'body elements.\n\nLike this.'),
|
||||
'status': u'This is a "work in progress"',
|
||||
'status': u'This is a “work in progress”',
|
||||
'version': u'1',
|
||||
'copyright': (u'This document has been placed in the public domain. '
|
||||
u'You\nmay do with it as you wish. You may copy, modify,'
|
||||
u'\nredistribute, reattribute, sell, buy, rent, lease,\n'
|
||||
u'destroy, or improve it, quote it at length, excerpt,\n'
|
||||
u'incorporate, collate, fold, staple, or mutilate it, or '
|
||||
u'do\nanything else to it that your or anyone else\'s '
|
||||
u'do\nanything else to it that your or anyone else’s '
|
||||
u'heart\ndesires.'),
|
||||
'contact': u'goodger@python.org',
|
||||
'date': u'2006-05-21',
|
||||
|
||||
@@ -4,7 +4,12 @@ Release checklist
|
||||
* open https://travis-ci.org/sphinx-doc/sphinx/branches and check stable branch is green
|
||||
* Check `git status`
|
||||
* Run `make style-check`
|
||||
* if final major release ...
|
||||
* Update sphinx/locale/sphinx.pot if first major release (beta1)
|
||||
|
||||
* Run `pytho nsetup.py extract_messages`
|
||||
* Run `(cd sphinx/locale; tx push -s)`
|
||||
|
||||
* Update sphinx/locale/<lang>/ files if final major release ...
|
||||
|
||||
* Run `(cd sphinx/locale; tx pull -a -f)`
|
||||
* Run `python setup.py compile_catalog`
|
||||
|
||||
Reference in New Issue
Block a user