Merge pull request #4410 from tk0miya/refactor_smartquotes2

Refactor smartquotes
This commit is contained in:
Takeshi KOMIYA 2018-01-15 01:30:45 +09:00 committed by GitHub
commit fb501dbea7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 60 deletions

View File

@ -19,13 +19,11 @@ import warnings
from os import path
from copy import copy
from collections import defaultdict
from contextlib import contextmanager
from six import BytesIO, itervalues, class_types, next, iteritems
from six import BytesIO, itervalues, class_types, next
from six.moves import cPickle as pickle
from docutils.utils import Reporter, get_source_line, normalize_language_tag
from docutils.utils.smartquotes import smartchars
from docutils.utils import Reporter, get_source_line
from docutils.frontend import OptionParser
from sphinx import addnodes, versioning
@ -41,7 +39,7 @@ from sphinx.util.matching import compile_matchers
from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks
from sphinx.util.websupport import is_commentable
from sphinx.errors import SphinxError, ExtensionError
from sphinx.transforms import SphinxTransformer, SphinxSmartQuotes
from sphinx.transforms import SphinxTransformer
from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.environment.adapters.indexentries import IndexEntries
from sphinx.environment.adapters.toctree import TocTree
@ -84,22 +82,6 @@ versioning_conditions = {
} # type: Dict[unicode, Union[bool, Callable]]
@contextmanager
def sphinx_smartquotes_action(env):
# type: (BuildEnvironment) -> Generator
if not hasattr(SphinxSmartQuotes, 'smartquotes_action'):
# less than docutils-0.14
yield
else:
# docutils-0.14 or above
try:
original = SphinxSmartQuotes.smartquotes_action
SphinxSmartQuotes.smartquotes_action = env.config.smartquotes_action
yield
finally:
SphinxSmartQuotes.smartquotes_action = original
class NoUri(Exception):
"""Raised by get_relative_uri if there is no URI available."""
pass
@ -602,8 +584,7 @@ class BuildEnvironment(object):
# remove all inventory entries for that file
app.emit('env-purge-doc', self, docname)
self.clear_doc(docname)
with sphinx_smartquotes_action(self):
self.read_doc(docname, app)
self.read_doc(docname, app)
def _read_parallel(self, docnames, app, nproc):
# type: (List[unicode], Sphinx, int) -> None
@ -615,9 +596,8 @@ class BuildEnvironment(object):
def read_process(docs):
# type: (List[unicode]) -> unicode
self.app = app
with sphinx_smartquotes_action(self):
for docname in docs:
self.read_doc(docname, app)
for docname in docs:
self.read_doc(docname, app)
# allow pickling self to send it back
return BuildEnvironment.dumps(self)
@ -662,29 +642,10 @@ class BuildEnvironment(object):
self.config.trim_footnote_reference_space
self.settings['gettext_compact'] = self.config.gettext_compact
language = self.config.language or 'en'
self.settings['language_code'] = language
if 'smart_quotes' not in self.settings:
self.settings['smart_quotes'] = self.config.smartquotes
self.settings['language_code'] = self.config.language or 'en'
# some conditions exclude smart quotes, overriding smart_quotes
for valname, vallist in iteritems(self.config.smartquotes_excludes):
if valname == 'builders':
# this will work only for checking first build target
if self.app.builder.name in vallist:
self.settings['smart_quotes'] = False
break
elif valname == 'languages':
if self.config.language in vallist:
self.settings['smart_quotes'] = False
break
# confirm selected language supports smart_quotes or not
for tag in normalize_language_tag(language):
if tag in smartchars.quotes:
break
else:
self.settings['smart_quotes'] = False
# Allow to disable by 3rd party extension (workaround)
self.settings.setdefault('smart_quotes', True)
def read_doc(self, docname, app=None):
# type: (unicode, Sphinx) -> None

View File

@ -19,6 +19,7 @@ from docutils.writers import UnfilteredWriter
from six import text_type
from typing import Any, Union # NOQA
from sphinx.transforms import SphinxTransformer
from sphinx.transforms import (
ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences,
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds,
@ -56,6 +57,11 @@ class SphinxBaseReader(standalone.Reader):
This replaces reporter by Sphinx's on generating document.
"""
def __init__(self, app, *args, **kwargs):
# type: (Sphinx, Any, Any) -> None
self.env = app.env
standalone.Reader.__init__(self, *args, **kwargs)
def get_transforms(self):
# type: () -> List[Transform]
return standalone.Reader.get_transforms(self) + self.transforms
@ -66,9 +72,16 @@ class SphinxBaseReader(standalone.Reader):
for logging.
"""
document = standalone.Reader.new_document(self)
# substitute transformer
document.transformer = SphinxTransformer(document)
document.transformer.set_environment(self.env)
# substitute reporter
reporter = document.reporter
document.reporter = LoggingReporter.from_reporter(reporter)
document.reporter.set_source(self.source)
return document
@ -80,20 +93,14 @@ class SphinxStandaloneReader(SphinxBaseReader):
Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets,
HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds,
RemoveTranslatableInline, PreserveTranslatableMessages, FilterSystemMessages,
RefOnlyBulletListTransform, UnreferencedFootnotesDetector, ManpageLink
RefOnlyBulletListTransform, UnreferencedFootnotesDetector, SphinxSmartQuotes,
ManpageLink
] # type: List[Transform]
def __init__(self, app, *args, **kwargs):
# type: (Sphinx, Any, Any) -> None
self.transforms = self.transforms + app.registry.get_transforms()
self.smart_quotes = app.env.settings['smart_quotes']
SphinxBaseReader.__init__(self, *args, **kwargs) # type: ignore
def get_transforms(self):
transforms = SphinxBaseReader.get_transforms(self)
if self.smart_quotes:
transforms.append(SphinxSmartQuotes)
return transforms
SphinxBaseReader.__init__(self, app, *args, **kwargs)
class SphinxI18nReader(SphinxBaseReader):

View File

@ -14,8 +14,9 @@ import re
from docutils import nodes
from docutils.transforms import Transform, Transformer
from docutils.transforms.parts import ContentsFilter
from docutils.utils import new_document
from docutils.transforms.universal import SmartQuotes
from docutils.utils import new_document, normalize_language_tag
from docutils.utils.smartquotes import smartchars
from sphinx import addnodes
from sphinx.locale import _
@ -335,12 +336,54 @@ class SphinxContentsFilter(ContentsFilter):
raise nodes.SkipNode
class SphinxSmartQuotes(SmartQuotes):
class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
"""
Customized SmartQuotes to avoid transform for some extra node types.
refs: sphinx.parsers.RSTParser
"""
def apply(self):
# type: () -> None
if not self.is_available():
return
SmartQuotes.apply(self)
def is_available(self):
# type: () -> bool
builders = self.config.smartquotes_excludes.get('builders', [])
languages = self.config.smartquotes_excludes.get('languages', [])
if self.document.settings.smart_quotes is False:
# disabled by 3rd party extension (workaround)
return False
elif self.config.smartquotes is False:
# disabled by confval smartquotes
return False
elif self.app.builder.name in builders:
# disabled by confval smartquotes_excludes['builders']
return False
elif self.config.language in languages:
# disabled by confval smartquotes_excludes['languages']
return False
# confirm selected language supports smart_quotes or not
language = self.env.settings['language_code']
for tag in normalize_language_tag(language):
if tag in smartchars.quotes:
return True
else:
return False
@property
def smartquotes_action(self):
# type: () -> unicode
"""A smartquotes_action setting for SmartQuotes.
Users can change this setting through :confval:`smartquotes_action`.
"""
return self.config.smartquotes_action
def get_tokens(self, txtnodes):
# A generator that yields ``(texttype, nodetext)`` tuples for a list
# of "Text" nodes (interface to ``smartquotes.educate_tokens()``).

View File

@ -50,7 +50,7 @@ def publish_msgstr(app, source, source_path, source_line, config, settings):
:rtype: docutils.nodes.document
"""
from sphinx.io import SphinxI18nReader
reader = SphinxI18nReader()
reader = SphinxI18nReader(app)
reader.set_lineno_for_reporter(source_line)
parser = app.registry.create_source_parser(app, '')
doc = reader.read(