mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Remove the custom smartypants code
Instead rely on docutils’ ‘smart_quotes’ option which is available since docutils 0.10. This adds support for internationalization: our code supported only English quotes, while docutils code supports 27 different languages. Closes #498, #580, #3345, #3472.
This commit is contained in:
parent
710ddb3880
commit
edc7f30b9c
@ -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):
|
||||
|
@ -45,8 +45,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 +58,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
|
||||
@ -220,15 +219,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]
|
||||
|
@ -26,6 +26,7 @@ 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
|
||||
@ -671,6 +672,10 @@ 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 language in smartchars.quotes:
|
||||
self.settings['smart_quotes'] = self.config.html_use_smartypants
|
||||
|
||||
docutilsconf = path.join(self.srcdir, 'docutils.conf')
|
||||
# read docutils.conf from source dir, not from current dir
|
||||
|
@ -1,312 +0,0 @@
|
||||
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/
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import 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
|
||||
"""
|
||||
Parameter: String.
|
||||
|
||||
Returns: The string, with "educated" curly quote HTML entities.
|
||||
|
||||
Example input: "Isn't this fun?"
|
||||
Example output: “Isn’t this fun?”
|
||||
"""
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# Special case for decade abbreviations (the '80s):
|
||||
s = decade_abbr_re.sub("’", s)
|
||||
|
||||
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)
|
||||
|
||||
# Any remaining single quotes should be opening ones:
|
||||
s = s.replace("'", "‘")
|
||||
|
||||
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)
|
||||
|
||||
# Any remaining quotes should be opening ones.
|
||||
return s.replace('"', "“")
|
||||
|
||||
|
||||
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?'';
|
||||
"""
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# Special case for decade abbreviations (the '80s):
|
||||
s = decade_abbr_re.sub("\x04", s)
|
||||
|
||||
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)
|
||||
|
||||
# Any remaining single quotes should be opening ones:
|
||||
s = s.replace("'", "\x03")
|
||||
|
||||
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)
|
||||
|
||||
# Any remaining quotes should be opening ones.
|
||||
s = s.replace('"', "\x01")
|
||||
|
||||
# Finally, replace all helpers with quotes.
|
||||
return s.replace("\x01", dquotes[0]).replace("\x02", dquotes[1]).\
|
||||
replace("\x03", "`").replace("\x04", "'")
|
||||
|
||||
|
||||
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("''", "”")
|
||||
|
||||
|
||||
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('. . .', "…")
|
@ -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
|
||||
@ -786,7 +784,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 +872,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
|
||||
@ -729,7 +727,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 +827,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):
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user