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 os import path
from copy import copy from copy import copy
from collections import defaultdict 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 six.moves import cPickle as pickle
from docutils.utils import Reporter, get_source_line, normalize_language_tag from docutils.utils import Reporter, get_source_line
from docutils.utils.smartquotes import smartchars
from docutils.frontend import OptionParser from docutils.frontend import OptionParser
from sphinx import addnodes, versioning 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.parallel import ParallelTasks, parallel_available, make_chunks
from sphinx.util.websupport import is_commentable from sphinx.util.websupport import is_commentable
from sphinx.errors import SphinxError, ExtensionError from sphinx.errors import SphinxError, ExtensionError
from sphinx.transforms import SphinxTransformer, SphinxSmartQuotes from sphinx.transforms import SphinxTransformer
from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.environment.adapters.indexentries import IndexEntries
from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.adapters.toctree import TocTree
@ -84,22 +82,6 @@ versioning_conditions = {
} # type: Dict[unicode, Union[bool, Callable]] } # 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): class NoUri(Exception):
"""Raised by get_relative_uri if there is no URI available.""" """Raised by get_relative_uri if there is no URI available."""
pass pass
@ -602,8 +584,7 @@ class BuildEnvironment(object):
# remove all inventory entries for that file # remove all inventory entries for that file
app.emit('env-purge-doc', self, docname) app.emit('env-purge-doc', self, docname)
self.clear_doc(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): def _read_parallel(self, docnames, app, nproc):
# type: (List[unicode], Sphinx, int) -> None # type: (List[unicode], Sphinx, int) -> None
@ -615,9 +596,8 @@ class BuildEnvironment(object):
def read_process(docs): def read_process(docs):
# type: (List[unicode]) -> unicode # type: (List[unicode]) -> unicode
self.app = app self.app = app
with sphinx_smartquotes_action(self): for docname in docs:
for docname in docs: self.read_doc(docname, app)
self.read_doc(docname, app)
# allow pickling self to send it back # allow pickling self to send it back
return BuildEnvironment.dumps(self) return BuildEnvironment.dumps(self)
@ -662,29 +642,10 @@ class BuildEnvironment(object):
self.config.trim_footnote_reference_space self.config.trim_footnote_reference_space
self.settings['gettext_compact'] = self.config.gettext_compact self.settings['gettext_compact'] = self.config.gettext_compact
language = self.config.language or 'en' self.settings['language_code'] = self.config.language or 'en'
self.settings['language_code'] = language
if 'smart_quotes' not in self.settings:
self.settings['smart_quotes'] = self.config.smartquotes
# some conditions exclude smart quotes, overriding smart_quotes # Allow to disable by 3rd party extension (workaround)
for valname, vallist in iteritems(self.config.smartquotes_excludes): self.settings.setdefault('smart_quotes', True)
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
def read_doc(self, docname, app=None): def read_doc(self, docname, app=None):
# type: (unicode, Sphinx) -> None # type: (unicode, Sphinx) -> None

View File

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

View File

@ -14,8 +14,9 @@ import re
from docutils import nodes from docutils import nodes
from docutils.transforms import Transform, Transformer from docutils.transforms import Transform, Transformer
from docutils.transforms.parts import ContentsFilter from docutils.transforms.parts import ContentsFilter
from docutils.utils import new_document
from docutils.transforms.universal import SmartQuotes 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 import addnodes
from sphinx.locale import _ from sphinx.locale import _
@ -335,12 +336,54 @@ class SphinxContentsFilter(ContentsFilter):
raise nodes.SkipNode raise nodes.SkipNode
class SphinxSmartQuotes(SmartQuotes): class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
""" """
Customized SmartQuotes to avoid transform for some extra node types. Customized SmartQuotes to avoid transform for some extra node types.
refs: sphinx.parsers.RSTParser 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): def get_tokens(self, txtnodes):
# A generator that yields ``(texttype, nodetext)`` tuples for a list # A generator that yields ``(texttype, nodetext)`` tuples for a list
# of "Text" nodes (interface to ``smartquotes.educate_tokens()``). # 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 :rtype: docutils.nodes.document
""" """
from sphinx.io import SphinxI18nReader from sphinx.io import SphinxI18nReader
reader = SphinxI18nReader() reader = SphinxI18nReader(app)
reader.set_lineno_for_reporter(source_line) reader.set_lineno_for_reporter(source_line)
parser = app.registry.create_source_parser(app, '') parser = app.registry.create_source_parser(app, '')
doc = reader.read( doc = reader.read(