From edc7f30b9cc92f4d721eeef62ffa826f23a09a9f Mon Sep 17 00:00:00 2001 From: Dmitry Shachnev Date: Tue, 14 Mar 2017 23:43:04 +0300 Subject: [PATCH] Remove the custom smartypants code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- sphinx/builders/_epub_base.py | 14 +- sphinx/builders/html.py | 15 +- sphinx/environment/__init__.py | 5 + sphinx/util/smartypants.py | 312 ------------------------------ sphinx/writers/html.py | 94 --------- sphinx/writers/html5.py | 94 --------- sphinx/writers/latex.py | 5 - tests/test_api_translator.py | 9 - tests/test_build_epub.py | 8 +- tests/test_environment_toctree.py | 6 +- tests/test_markup.py | 11 +- tests/test_metadata.py | 4 +- 12 files changed, 38 insertions(+), 539 deletions(-) delete mode 100644 sphinx/util/smartypants.py diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index f7ebaa0e8..a63ecbc39 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -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): diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 7369ea23d..a0b3d8e87 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -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] diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index fd89a07e4..b0a1a2ce5 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -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 diff --git a/sphinx/util/smartypants.py b/sphinx/util/smartypants.py deleted file mode 100644 index 0146ba6e9..000000000 --- a/sphinx/util/smartypants.py +++ /dev/null @@ -1,312 +0,0 @@ -r""" -This is based on SmartyPants.py by `Chad Miller`_ , -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.: -#

He said, "'Quoted' words in a larger quote."

-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.: - #

He said, "'Quoted' words in a larger quote."

- 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.: - #

He said, "'Quoted' words in a larger quote."

- 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('. . .', "…") diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index 63aba3cd6..572b2825e 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -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 diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index 9deb35a88..8e3843c09 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -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 diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index fc2cbb272..c1e33374d 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -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): diff --git a/tests/test_api_translator.py b/tests/test_api_translator.py index 511fe1416..dadea50e5 100644 --- a/tests/test_api_translator.py +++ b/tests/test_api_translator.py @@ -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' diff --git a/tests/test_build_epub.py b/tests/test_build_epub.py index cf0e2857a..e5d86b0ed 100644 --- a/tests/test_build_epub.py +++ b/tests/test_build_epub.py @@ -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 diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py index 0f1dffa43..a2d54fb79 100644 --- a/tests/test_environment_toctree.py +++ b/tests/test_environment_toctree.py @@ -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])) diff --git a/tests/test_markup.py b/tests/test_markup.py index ecc4b6a11..18c9d0bbd 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -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"', - '

“John”

', - r'\sphinxquotedblleft{}John\sphinxquotedblright{}', + u'

“John”

', + u"“John”", ), ( # ... but not in literal text diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 77efff5ed..58f573b0a 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -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',