From fc36f9024688bb8abeb7d2e0d8cdbb716466d4cf Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 18 Jun 2017 22:58:21 +0900 Subject: [PATCH 01/18] fix --- sphinx/addnodes.py | 9 +++++++-- sphinx/transforms/__init__.py | 5 +++-- sphinx/util/nodes.py | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index e1168a8ff..e06b25111 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -49,6 +49,11 @@ class translatable(object): raise NotImplementedError +class not_smartquotable(object): + """A node which does not support smart-quotes.""" + support_smartquotes = False + + class toctree(nodes.General, nodes.Element, translatable): """Node for inserting a "TOC tree".""" @@ -266,13 +271,13 @@ class download_reference(nodes.reference): """Node for download references, similar to pending_xref.""" -class literal_emphasis(nodes.emphasis): +class literal_emphasis(nodes.emphasis, not_smartquotable): """Node that behaves like `emphasis`, but further text processors are not applied (e.g. smartypants for HTML output). """ -class literal_strong(nodes.strong): +class literal_strong(nodes.strong, not_smartquotable): """Node that behaves like `strong`, but further text processors are not applied (e.g. smartypants for HTML output). """ diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index 0a7db678f..bae58a985 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -19,7 +19,7 @@ from sphinx import addnodes from sphinx.locale import _ from sphinx.util import logging from sphinx.util.i18n import format_date -from sphinx.util.nodes import apply_source_workaround +from sphinx.util.nodes import apply_source_workaround, is_smartquotable if False: # For type annotation @@ -343,6 +343,7 @@ class SphinxSmartQuotes(SmartQuotes): texttype = {True: 'literal', # "literal" text is not changed: False: 'plain'} for txtnode in txtnodes: + smartquotable = is_smartquotable(txtnode.parent) nodetype = texttype[isinstance(txtnode.parent, (nodes.literal, addnodes.literal_emphasis, @@ -360,4 +361,4 @@ class SphinxSmartQuotes(SmartQuotes): nodes.image, nodes.raw, nodes.problematic))] - yield (nodetype, txtnode.astext()) + yield (texttype[smartquotable], txtnode.astext()) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index f8157ef6d..914919351 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -364,6 +364,22 @@ def process_only_nodes(doctree, tags): node.replace_self(nodes.comment()) +SMARTQUOTABLE_NODES = ( + addnodes.not_smartquotable, +) + + +def is_smartquotable(node): + # type: (nodes.Node) -> bool + """Check the node is smart-quotable or not.""" + if isinstance(node, SMARTQUOTABLE_NODES): + return False + elif getattr(node, 'support_smartquotes', None) is False: + return False + else: + return True + + # monkey-patch Element.copy to copy the rawsource and line def _new_copy(self): From 5331ab2ef9d06373df94e412cdf4708cd3d31463 Mon Sep 17 00:00:00 2001 From: jfbu Date: Sun, 18 Jun 2017 19:36:49 +0200 Subject: [PATCH 02/18] Fix #3851 and fix #3706 about box drawing characters for PDF output Do not tex-escape them, but provide suitable definitions for pdflatex only (and platex). Define the 4 box drawing characters needed by unix tree command with charset utf8. Also define the U+2572 as it was previously escaped for all engines. No definitions for xelatex/lualatex as it is then only a matter of using a font providing them (a priori, only the mono font needs definition, as the characters will be used in literal blocks). --- sphinx/texinputs/sphinx.sty | 35 +++++++++++++++++++++++++++++++++++ sphinx/util/texescape.py | 3 --- sphinx/writers/latex.py | 17 +++++++++++++++-- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 6a3c48cfc..067452441 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -1478,6 +1478,41 @@ % figure legend comes after caption and may contain arbitrary body elements \newenvironment{sphinxlegend}{\par\small}{\par} +% Declare Unicode characters used by linux tree command to pdflatex utf8/utf8x +\def\spx@bd#1#2{% + \leavevmode + \begingroup + \ifx\spx@bd@height \@undefined\def\spx@bd@height{\baselineskip}\fi + \ifx\spx@bd@width \@undefined\setbox0\hbox{0}\def\spx@bd@width{\wd0 }\fi + \ifx\spx@bd@thickness\@undefined\def\spx@bd@thickness{.6\p@}\fi + \ifx\spx@bd@raise \@undefined\def\spx@bd@lower{\dp\strutbox}\fi + \lower\spx@bd@lower#1{#2}% + \endgroup +}% +\@namedef{sphinx@u2500}% BOX DRAWINGS LIGHT HORIZONTAL + {\spx@bd{\vbox to\spx@bd@height} + {\vss\hrule\@height\spx@bd@thickness + \@width\spx@bd@width\vss}}% +\@namedef{sphinx@u2502}% BOX DRAWINGS LIGHT VERTICAL + {\spx@bd{\hb@xt@\spx@bd@width} + {\hss\vrule\@height\spx@bd@height + \@width \spx@bd@thickness\hss}}% +\@namedef{sphinx@u2514}% BOX DRAWINGS LIGHT UP AND RIGHT + {\spx@bd{\hb@xt@\spx@bd@width} + {\hss\raise.5\spx@bd@height + \hb@xt@\z@{\hss\vrule\@height.5\spx@bd@height + \@width \spx@bd@thickness\hss}% + \vbox to\spx@bd@height{\vss\hrule\@height\spx@bd@thickness + \@width.5\spx@bd@width\vss}}}% +\@namedef{sphinx@u251C}% BOX DRAWINGS LIGHT VERTICAL AND RIGHT + {\spx@bd{\hb@xt@\spx@bd@width} + {\hss + \hb@xt@\z@{\hss\vrule\@height\spx@bd@height + \@width \spx@bd@thickness\hss}% + \vbox to\spx@bd@height{\vss\hrule\@height\spx@bd@thickness + \@width.5\spx@bd@width\vss}}}% +\protected\def\sphinxunichar#1{\@nameuse{sphinx@u#1}}% + % Tell TeX about pathological hyphenation cases: \hyphenation{Base-HTTP-Re-quest-Hand-ler} \endinput diff --git a/sphinx/util/texescape.py b/sphinx/util/texescape.py index d9102c417..07f5390c4 100644 --- a/sphinx/util/texescape.py +++ b/sphinx/util/texescape.py @@ -40,12 +40,9 @@ tex_replacements = [ # used to separate -- in options ('', r'{}'), # map some special Unicode characters to similar ASCII ones - ('─', r'-'), ('⎽', r'\_'), - ('╲', r'\textbackslash{}'), ('–', r'\textendash{}'), ('|', r'\textbar{}'), - ('│', r'\textbar{}'), ('ℯ', r'e'), ('ⅈ', r'i'), ('⁰', r'\(\sp{\text{0}}\)'), diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 92383a642..2aff959cf 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -105,9 +105,22 @@ ADDITIONAL_SETTINGS = { 'pdflatex': { 'inputenc': '\\usepackage[utf8]{inputenc}', 'utf8extra': ('\\ifdefined\\DeclareUnicodeCharacter\n' - ' \\ifdefined\\DeclareUnicodeCharacterAsOptional\\else\n' + ' \\ifdefined\\DeclareUnicodeCharacterAsOptional\n' + ' \\DeclareUnicodeCharacter{"00A0}{\\nobreakspace}\n' + ' \\DeclareUnicodeCharacter{"2500}{\\sphinxunichar{2500}}\n' + ' \\DeclareUnicodeCharacter{"2502}{\\sphinxunichar{2502}}\n' + ' \\DeclareUnicodeCharacter{"2514}{\\sphinxunichar{2514}}\n' + ' \\DeclareUnicodeCharacter{"251C}{\\sphinxunichar{251C}}\n' + ' \\DeclareUnicodeCharacter{"2572}{\\textbackslash}\n' + ' \\else\n' ' \\DeclareUnicodeCharacter{00A0}{\\nobreakspace}\n' - '\\fi\\fi'), + ' \\DeclareUnicodeCharacter{2500}{\\sphinxunichar{2500}}\n' + ' \\DeclareUnicodeCharacter{2502}{\\sphinxunichar{2502}}\n' + ' \\DeclareUnicodeCharacter{2514}{\\sphinxunichar{2514}}\n' + ' \\DeclareUnicodeCharacter{251C}{\\sphinxunichar{251C}}\n' + ' \\DeclareUnicodeCharacter{2572}{\\textbackslash}\n' + ' \\fi\n' + '\\fi'), }, 'xelatex': { 'latex_engine': 'xelatex', From 9db496f8009459702e8470ab8c93590671cb5517 Mon Sep 17 00:00:00 2001 From: jfbu Date: Sun, 18 Jun 2017 20:22:48 +0200 Subject: [PATCH 03/18] Fix typo in latex macro name --- sphinx/texinputs/sphinx.sty | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 067452441..25120b8a9 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -1485,7 +1485,7 @@ \ifx\spx@bd@height \@undefined\def\spx@bd@height{\baselineskip}\fi \ifx\spx@bd@width \@undefined\setbox0\hbox{0}\def\spx@bd@width{\wd0 }\fi \ifx\spx@bd@thickness\@undefined\def\spx@bd@thickness{.6\p@}\fi - \ifx\spx@bd@raise \@undefined\def\spx@bd@lower{\dp\strutbox}\fi + \ifx\spx@bd@lower \@undefined\def\spx@bd@lower{\dp\strutbox}\fi \lower\spx@bd@lower#1{#2}% \endgroup }% From 837ee21c9dc7c36a291235291c552a12a5193aac Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 18 Jun 2017 23:56:18 +0900 Subject: [PATCH 04/18] Refactor smart-quotes detector --- sphinx/addnodes.py | 18 +++++++++--------- sphinx/transforms/__init__.py | 19 +------------------ sphinx/util/nodes.py | 10 ++++++++-- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index e06b25111..6cb47d63d 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -96,7 +96,7 @@ class desc_signature(nodes.Part, nodes.Inline, nodes.TextElement): """ -class desc_signature_line(nodes.Part, nodes.Inline, nodes.TextElement): +class desc_signature_line(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for a line in a multi-line object signatures. It should only be used in a ``desc_signature`` with ``is_multiline`` set. @@ -106,7 +106,7 @@ class desc_signature_line(nodes.Part, nodes.Inline, nodes.TextElement): # nodes to use within a desc_signature or desc_signature_line -class desc_addname(nodes.Part, nodes.Inline, nodes.TextElement): +class desc_addname(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for additional name parts (module name, class name).""" @@ -114,7 +114,7 @@ class desc_addname(nodes.Part, nodes.Inline, nodes.TextElement): desc_classname = desc_addname -class desc_type(nodes.Part, nodes.Inline, nodes.TextElement): +class desc_type(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for return types or object type names.""" @@ -125,20 +125,20 @@ class desc_returns(desc_type): return ' -> ' + nodes.TextElement.astext(self) -class desc_name(nodes.Part, nodes.Inline, nodes.TextElement): +class desc_name(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for the main object name.""" -class desc_parameterlist(nodes.Part, nodes.Inline, nodes.TextElement): +class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for a general parameter list.""" child_text_separator = ', ' -class desc_parameter(nodes.Part, nodes.Inline, nodes.TextElement): +class desc_parameter(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for a single parameter.""" -class desc_optional(nodes.Part, nodes.Inline, nodes.TextElement): +class desc_optional(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for marking optional parts of the parameter list.""" child_text_separator = ', ' @@ -147,7 +147,7 @@ class desc_optional(nodes.Part, nodes.Inline, nodes.TextElement): return '[' + nodes.TextElement.astext(self) + ']' -class desc_annotation(nodes.Part, nodes.Inline, nodes.TextElement): +class desc_annotation(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for signature annotations (not Python 3-style annotations).""" @@ -179,7 +179,7 @@ class productionlist(nodes.Admonition, nodes.Element): """ -class production(nodes.Part, nodes.Inline, nodes.TextElement): +class production(nodes.Part, nodes.Inline, nodes.FixedTextElement): """Node for a single grammar production rule.""" diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index bae58a985..0c70a6b9e 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -343,22 +343,5 @@ class SphinxSmartQuotes(SmartQuotes): texttype = {True: 'literal', # "literal" text is not changed: False: 'plain'} for txtnode in txtnodes: - smartquotable = is_smartquotable(txtnode.parent) - nodetype = texttype[isinstance(txtnode.parent, - (nodes.literal, - addnodes.literal_emphasis, - addnodes.literal_strong, - addnodes.desc_addname, - addnodes.desc_annotation, - addnodes.desc_name, - addnodes.desc_optional, - addnodes.desc_parameter, - addnodes.desc_parameterlist, - addnodes.desc_signature_line, - addnodes.desc_type, - addnodes.production, - nodes.math, - nodes.image, - nodes.raw, - nodes.problematic))] + smartquotable = not is_smartquotable(txtnode) yield (texttype[smartquotable], txtnode.astext()) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 914919351..5d9ac78c6 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -364,7 +364,13 @@ def process_only_nodes(doctree, tags): node.replace_self(nodes.comment()) -SMARTQUOTABLE_NODES = ( +NON_SMARTQUOTABLE_PARENT_NODES = ( + nodes.FixedTextElement, + nodes.literal, + nodes.math, + nodes.image, + nodes.raw, + nodes.problematic, addnodes.not_smartquotable, ) @@ -372,7 +378,7 @@ SMARTQUOTABLE_NODES = ( def is_smartquotable(node): # type: (nodes.Node) -> bool """Check the node is smart-quotable or not.""" - if isinstance(node, SMARTQUOTABLE_NODES): + if isinstance(node.parent, NON_SMARTQUOTABLE_PARENT_NODES): return False elif getattr(node, 'support_smartquotes', None) is False: return False From 41bb5f7fd26a5d4e8c24812a47aa50f46e88eb70 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 25 Jun 2017 17:35:42 +0900 Subject: [PATCH 05/18] Fix #3833: command line messages are translated unintentionally --- CHANGES | 2 + setup.cfg | 2 +- sphinx/application.py | 56 +++++++++---------- sphinx/config.py | 17 +++--- sphinx/directives/code.py | 22 ++++---- sphinx/environment/__init__.py | 10 ++-- sphinx/events.py | 6 +- sphinx/ext/graphviz.py | 28 +++++----- sphinx/ext/imgconverter.py | 14 ++--- sphinx/ext/mathbase.py | 4 +- sphinx/extension.py | 12 ++-- sphinx/locale/__init__.py | 10 ++++ sphinx/registry.py | 52 ++++++++--------- sphinx/theming.py | 28 +++++----- sphinx/transforms/post_transforms/__init__.py | 10 ++-- sphinx/util/docutils.py | 6 +- 16 files changed, 146 insertions(+), 133 deletions(-) diff --git a/CHANGES b/CHANGES index 9ad92038d..639f7a43a 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,8 @@ Bugs fixed * #3873: Failure of deprecation warning mechanism of ``sphinx.util.compat.Directive`` * #3874: Bogus warnings for "citation not referenced" for cross-file citations +* #3833: command line messages are translated unintentionally with ``language`` + setting. Testing -------- diff --git a/setup.cfg b/setup.cfg index 560bde328..e0312ce00 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,7 +9,7 @@ upload = upload --sign --identity=36580288 [extract_messages] mapping_file = babel.cfg output_file = sphinx/locale/sphinx.pot -keywords = _ l_ lazy_gettext +keywords = _ __ l_ lazy_gettext [update_catalog] input_file = sphinx/locale/sphinx.pot diff --git a/sphinx/application.py b/sphinx/application.py index c06e86995..27dfeb2e4 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -34,7 +34,7 @@ from sphinx.environment import BuildEnvironment from sphinx.events import EventManager from sphinx.extension import verify_required_extensions from sphinx.io import SphinxStandaloneReader -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.registry import SphinxComponentRegistry from sphinx.util import pycompat # noqa: F401 from sphinx.util import import_object @@ -179,8 +179,8 @@ class Sphinx(object): # check the Sphinx version if requested if self.config.needs_sphinx and self.config.needs_sphinx > sphinx.__display_version__: raise VersionRequirementError( - _('This project needs at least Sphinx v%s and therefore cannot ' - 'be built with this version.') % self.config.needs_sphinx) + __('This project needs at least Sphinx v%s and therefore cannot ' + 'be built with this version.') % self.config.needs_sphinx) # set confdir to srcdir if -C given (!= no confdir); a few pieces # of code expect a confdir to be set @@ -206,9 +206,9 @@ class Sphinx(object): self.config.setup(self) else: raise ConfigError( - _("'setup' as currently defined in conf.py isn't a Python callable. " - "Please modify its definition to make it a callable function. This is " - "needed for conf.py to behave as a Sphinx extension.") + __("'setup' as currently defined in conf.py isn't a Python callable. " + "Please modify its definition to make it a callable function. This is " + "needed for conf.py to behave as a Sphinx extension.") ) # now that we know all config values, collect them from conf.py @@ -220,7 +220,7 @@ class Sphinx(object): # check primary_domain if requested primary_domain = self.config.primary_domain if primary_domain and not self.registry.has_domain(primary_domain): - logger.warning(_('primary_domain %r not found, ignored.'), primary_domain) + logger.warning(__('primary_domain %r not found, ignored.'), primary_domain) # create the builder self.builder = self.create_builder(buildername) @@ -257,7 +257,7 @@ class Sphinx(object): if self.config.language is not None: if has_translation or self.config.language == 'en': # "en" never needs to be translated - logger.info(_('done')) + logger.info(__('done')) else: logger.info('not available for built-in messages') @@ -278,19 +278,19 @@ class Sphinx(object): self.env.domains[domain.name] = domain else: try: - logger.info(bold(_('loading pickled environment... ')), nonl=True) + logger.info(bold(__('loading pickled environment... ')), nonl=True) filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME) self.env = BuildEnvironment.frompickle(filename, self) self.env.domains = {} for domain in self.registry.create_domains(self.env): # this can raise if the data version doesn't fit self.env.domains[domain.name] = domain - logger.info(_('done')) + logger.info(__('done')) except Exception as err: if isinstance(err, IOError) and err.errno == ENOENT: - logger.info(_('not yet created')) + logger.info(__('not yet created')) else: - logger.info(_('failed: %s'), err) + logger.info(__('failed: %s'), err) self._init_env(freshenv=True) def preload_builder(self, name): @@ -300,7 +300,7 @@ class Sphinx(object): def create_builder(self, name): # type: (unicode) -> Builder if name is None: - logger.info(_('No builder selected, using default: html')) + logger.info(__('No builder selected, using default: html')) name = 'html' return self.registry.create_builder(self, name) @@ -339,13 +339,13 @@ class Sphinx(object): self.builder.build_update() status = (self.statuscode == 0 and - _('succeeded') or _('finished with problems')) + __('succeeded') or __('finished with problems')) if self._warncount: - logger.info(bold(_('build %s, %s warning%s.') % + logger.info(bold(__('build %s, %s warning%s.') % (status, self._warncount, self._warncount != 1 and 's' or ''))) else: - logger.info(bold(_('build %s.') % status)) + logger.info(bold(__('build %s.') % status)) except Exception as err: # delete the saved env to force a fresh build next time envfile = path.join(self.doctreedir, ENV_PICKLE_FILENAME) @@ -504,7 +504,7 @@ class Sphinx(object): logger.debug('[app] adding config value: %r', (name, default, rebuild) + ((types,) if types else ())) # type: ignore if name in self.config: - raise ExtensionError(_('Config value %r already present') % name) + raise ExtensionError(__('Config value %r already present') % name) if rebuild in (False, True): rebuild = rebuild and 'env' or '' self.config.add(name, default, rebuild, types) @@ -516,7 +516,7 @@ class Sphinx(object): def set_translator(self, name, translator_class): # type: (unicode, Type[nodes.NodeVisitor]) -> None - logger.info(bold(_('Change of translator for the %s builder.') % name)) + logger.info(bold(__('Change of translator for the %s builder.') % name)) self.registry.add_translator(name, translator_class) def add_node(self, node, **kwds): @@ -524,8 +524,8 @@ class Sphinx(object): logger.debug('[app] adding node: %r', (node, kwds)) if not kwds.pop('override', False) and \ hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__): - logger.warning(_('while setting up extension %s: node class %r is ' - 'already registered, its visitors will be overridden'), + logger.warning(__('while setting up extension %s: node class %r is ' + 'already registered, its visitors will be overridden'), self._setting_up_extension, node.__name__, type='app', subtype='add_node') nodes._add_node_class_names([node.__name__]) @@ -533,8 +533,8 @@ class Sphinx(object): try: visit, depart = val except ValueError: - raise ExtensionError(_('Value for key %r must be a ' - '(visit, depart) function tuple') % key) + raise ExtensionError(__('Value for key %r must be a ' + '(visit, depart) function tuple') % key) translator = self.registry.translators.get(key) translators = [] if translator is not None: @@ -580,8 +580,8 @@ class Sphinx(object): logger.debug('[app] adding directive: %r', (name, obj, content, arguments, options)) if name in directives._directives: - logger.warning(_('while setting up extension %s: directive %r is ' - 'already registered, it will be overridden'), + logger.warning(__('while setting up extension %s: directive %r is ' + 'already registered, it will be overridden'), self._setting_up_extension[-1], name, type='app', subtype='add_directive') directive = directive_helper(obj, content, arguments, **options) @@ -591,8 +591,8 @@ class Sphinx(object): # type: (unicode, Any) -> None logger.debug('[app] adding role: %r', (name, role)) if name in roles._roles: - logger.warning(_('while setting up extension %s: role %r is ' - 'already registered, it will be overridden'), + logger.warning(__('while setting up extension %s: role %r is ' + 'already registered, it will be overridden'), self._setting_up_extension[-1], name, type='app', subtype='add_role') roles.register_local_role(name, role) @@ -603,8 +603,8 @@ class Sphinx(object): # register_canonical_role logger.debug('[app] adding generic role: %r', (name, nodeclass)) if name in roles._roles: - logger.warning(_('while setting up extension %s: role %r is ' - 'already registered, it will be overridden'), + logger.warning(__('while setting up extension %s: role %r is ' + 'already registered, it will be overridden'), self._setting_up_extension[-1], name, type='app', subtype='add_generic_role') role = roles.GenericRole(name, nodeclass) diff --git a/sphinx/config.py b/sphinx/config.py index 451aa8b0d..02ee529a3 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -16,7 +16,7 @@ from six import PY2, PY3, iteritems, string_types, binary_type, text_type, integ from typing import Any, NamedTuple, Union from sphinx.errors import ConfigError -from sphinx.locale import l_, _ +from sphinx.locale import l_, __ from sphinx.util import logging from sphinx.util.i18n import format_date from sphinx.util.osutil import cd @@ -233,8 +233,8 @@ class Config(object): else: defvalue = self.values[name][0] if isinstance(defvalue, dict): - raise ValueError(_('cannot override dictionary config setting %r, ' - 'ignoring (use %r to set individual elements)') % + raise ValueError(__('cannot override dictionary config setting %r, ' + 'ignoring (use %r to set individual elements)') % (name, name + '.key=value')) elif isinstance(defvalue, list): return value.split(',') @@ -242,13 +242,13 @@ class Config(object): try: return int(value) except ValueError: - raise ValueError(_('invalid number %r for config value %r, ignoring') % + raise ValueError(__('invalid number %r for config value %r, ignoring') % (value, name)) elif hasattr(defvalue, '__call__'): return value elif defvalue is not None and not isinstance(defvalue, string_types): - raise ValueError(_('cannot override config setting %r with unsupported ' - 'type, ignoring') % name) + raise ValueError(__('cannot override config setting %r with unsupported ' + 'type, ignoring') % name) else: return value @@ -277,7 +277,8 @@ class Config(object): config.setdefault(realvalname, {})[key] = value continue elif valname not in self.values: - logger.warning(_('unknown config value %r in override, ignoring'), valname) + logger.warning(__('unknown config value %r in override, ignoring'), + valname) continue if isinstance(value, string_types): config[valname] = self.convert_overrides(valname, value) @@ -296,7 +297,7 @@ class Config(object): if name.startswith('_'): raise AttributeError(name) if name not in self.values: - raise AttributeError(_('No such config value: %s') % name) + raise AttributeError(__('No such config value: %s') % name) default = self.values[name][0] if hasattr(default, '__call__'): return default(self) diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index e372762ce..41a593aa3 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -16,7 +16,7 @@ from docutils.parsers.rst import Directive, directives from docutils.statemachine import ViewList from sphinx import addnodes -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.util import logging from sphinx.util import parselinenos from sphinx.util.nodes import set_source_info @@ -63,7 +63,7 @@ def dedent_lines(lines, dedent, location=None): return lines if any(s[:dedent].strip() for s in lines): - logger.warning(_('Over dedent has detected'), location=location) + logger.warning(__('Over dedent has detected'), location=location) new_lines = [] for line in lines: @@ -83,7 +83,7 @@ def container_wrapper(directive, literal_node, caption): directive.state.nested_parse(ViewList([caption], source=''), directive.content_offset, parsed) if isinstance(parsed[0], nodes.system_message): - msg = _('Invalid caption: %s' % parsed[0].astext()) + msg = __('Invalid caption: %s' % parsed[0].astext()) raise ValueError(msg) caption_node = nodes.caption(parsed[0].rawsource, '', *parsed[0].children) @@ -198,7 +198,7 @@ class LiteralIncludeReader(object): # type: () -> None for option1, option2 in self.INVALID_OPTIONS_PAIR: if option1 in self.options and option2 in self.options: - raise ValueError(_('Cannot use both "%s" and "%s" options') % + raise ValueError(__('Cannot use both "%s" and "%s" options') % (option1, option2)) def read_file(self, filename, location=None): @@ -211,10 +211,10 @@ class LiteralIncludeReader(object): return text.splitlines(True) except (IOError, OSError): - raise IOError(_('Include file %r not found or reading it failed') % filename) + raise IOError(__('Include file %r not found or reading it failed') % filename) except UnicodeError: - raise UnicodeError(_('Encoding %r used for reading included file %r seems to ' - 'be wrong, try giving an :encoding: option') % + raise UnicodeError(__('Encoding %r used for reading included file %r seems to ' + 'be wrong, try giving an :encoding: option') % (self.encoding, filename)) def read(self, location=None): @@ -251,7 +251,7 @@ class LiteralIncludeReader(object): analyzer = ModuleAnalyzer.for_file(self.filename, '') tags = analyzer.find_tags() if pyobject not in tags: - raise ValueError(_('Object named %r not found in include file %r') % + raise ValueError(__('Object named %r not found in include file %r') % (pyobject, self.filename)) else: start = tags[pyobject][1] @@ -277,12 +277,12 @@ class LiteralIncludeReader(object): if all(first + i == n for i, n in enumerate(linelist)): self.lineno_start += linelist[0] else: - raise ValueError(_('Cannot use "lineno-match" with a disjoint ' - 'set of "lines"')) + raise ValueError(__('Cannot use "lineno-match" with a disjoint ' + 'set of "lines"')) lines = [lines[n] for n in linelist if n < len(lines)] if lines == []: - raise ValueError(_('Line spec %r: no lines pulled from include file %r') % + raise ValueError(__('Line spec %r: no lines pulled from include file %r') % (linespec, self.filename)) return lines diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index bca779cc4..70fdbaeca 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -44,7 +44,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.locale import _ +from sphinx.locale import __ from sphinx.transforms import SphinxTransformer from sphinx.versioning import add_uids, merge_doctrees from sphinx.deprecation import RemovedInSphinx17Warning, RemovedInSphinx20Warning @@ -565,10 +565,10 @@ class BuildEnvironment(object): if parallel_available and len(docnames) > 5 and self.app.parallel > 1: for ext in itervalues(self.app.extensions): if ext.parallel_read_safe is None: - logger.warning(_('the %s extension does not declare if it is safe ' - 'for parallel reading, assuming it isn\'t - please ' - 'ask the extension author to check and make it ' - 'explicit'), ext.name) + logger.warning(__('the %s extension does not declare if it is safe ' + 'for parallel reading, assuming it isn\'t - please ' + 'ask the extension author to check and make it ' + 'explicit'), ext.name) logger.warning('doing serial read') break elif ext.parallel_read_safe is False: diff --git a/sphinx/events.py b/sphinx/events.py index 23353dfdb..99decfff5 100644 --- a/sphinx/events.py +++ b/sphinx/events.py @@ -17,7 +17,7 @@ from collections import OrderedDict, defaultdict from six import itervalues from sphinx.errors import ExtensionError -from sphinx.locale import _ +from sphinx.locale import __ if False: # For type annotation @@ -54,13 +54,13 @@ class EventManager(object): def add(self, name): # type: (unicode) -> None if name in self.events: - raise ExtensionError(_('Event %r already present') % name) + raise ExtensionError(__('Event %r already present') % name) self.events[name] = '' def connect(self, name, callback): # type: (unicode, Callable) -> int if name not in self.events: - raise ExtensionError(_('Unknown event name: %s') % name) + raise ExtensionError(__('Unknown event name: %s') % name) listener_id = self.next_listener_id self.next_listener_id += 1 diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 83d89adf2..a1565a1a7 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -25,7 +25,7 @@ from docutils.statemachine import ViewList import sphinx from sphinx.errors import SphinxError -from sphinx.locale import _ +from sphinx.locale import _, __ from sphinx.util import logging from sphinx.util.i18n import search_image_for_language from sphinx.util.osutil import ensuredir, ENOENT, EPIPE, EINVAL @@ -93,8 +93,8 @@ class Graphviz(Directive): document = self.state.document if self.content: return [document.reporter.warning( - _('Graphviz directive cannot have both content and ' - 'a filename argument'), line=self.lineno)] + __('Graphviz directive cannot have both content and ' + 'a filename argument'), line=self.lineno)] env = self.state.document.settings.env argument = search_image_for_language(self.arguments[0], env) rel_filename, filename = env.relfn2path(argument) @@ -104,13 +104,13 @@ class Graphviz(Directive): dotcode = fp.read() except (IOError, OSError): return [document.reporter.warning( - _('External Graphviz file %r not found or reading ' - 'it failed') % filename, line=self.lineno)] + __('External Graphviz file %r not found or reading ' + 'it failed') % filename, line=self.lineno)] else: dotcode = '\n'.join(self.content) if not dotcode.strip(): return [self.state_machine.reporter.warning( - _('Ignoring "graphviz" directive without content.'), + __('Ignoring "graphviz" directive without content.'), line=self.lineno)] node = graphviz() node['code'] = dotcode @@ -201,8 +201,8 @@ def render_dot(self, code, options, format, prefix='graphviz'): except OSError as err: if err.errno != ENOENT: # No such file or directory raise - logger.warning(_('dot command %r cannot be run (needed for graphviz ' - 'output), check the graphviz_dot setting'), graphviz_dot) + logger.warning(__('dot command %r cannot be run (needed for graphviz ' + 'output), check the graphviz_dot setting'), graphviz_dot) if not hasattr(self.builder, '_graphviz_warned_dot'): self.builder._graphviz_warned_dot = {} self.builder._graphviz_warned_dot[graphviz_dot] = True @@ -219,11 +219,11 @@ def render_dot(self, code, options, format, prefix='graphviz'): stdout, stderr = p.stdout.read(), p.stderr.read() p.wait() if p.returncode != 0: - raise GraphvizError(_('dot exited with error:\n[stderr]\n%s\n' - '[stdout]\n%s') % (stderr, stdout)) + raise GraphvizError(__('dot exited with error:\n[stderr]\n%s\n' + '[stdout]\n%s') % (stderr, stdout)) if not path.isfile(outfn): - raise GraphvizError(_('dot did not produce an output file:\n[stderr]\n%s\n' - '[stdout]\n%s') % (stderr, stdout)) + raise GraphvizError(__('dot did not produce an output file:\n[stderr]\n%s\n' + '[stdout]\n%s') % (stderr, stdout)) return relfn, outfn @@ -233,8 +233,8 @@ def render_dot_html(self, node, code, options, prefix='graphviz', format = self.builder.config.graphviz_output_format try: if format not in ('png', 'svg'): - raise GraphvizError(_("graphviz_output_format must be one of 'png', " - "'svg', but is %r") % format) + raise GraphvizError(__("graphviz_output_format must be one of 'png', " + "'svg', but is %r") % format) fname, outfn = render_dot(self, code, options, format, prefix) except GraphvizError as exc: logger.warning('dot code %r: ' % code + str(exc)) diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index d69a4411b..d2894b2a3 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -11,7 +11,7 @@ import subprocess from sphinx.errors import ExtensionError -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.transforms.post_transforms.images import ImageConverter from sphinx.util import logging from sphinx.util.osutil import ENOENT, EPIPE, EINVAL @@ -43,8 +43,8 @@ class ImagemagickConverter(ImageConverter): else: return False except (OSError, IOError): - logger.warning(_('convert command %r cannot be run.' - 'check the image_converter setting'), + logger.warning(__('convert command %r cannot be run.' + 'check the image_converter setting'), self.config.image_converter) return False @@ -60,8 +60,8 @@ class ImagemagickConverter(ImageConverter): except OSError as err: if err.errno != ENOENT: # No such file or directory raise - logger.warning(_('convert command %r cannot be run.' - 'check the image_converter setting'), + logger.warning(__('convert command %r cannot be run.' + 'check the image_converter setting'), self.config.image_converter) return False @@ -73,8 +73,8 @@ class ImagemagickConverter(ImageConverter): stdout, stderr = p.stdout.read(), p.stderr.read() p.wait() if p.returncode != 0: - raise ExtensionError(_('convert exited with error:\n' - '[stderr]\n%s\n[stdout]\n%s') % + raise ExtensionError(__('convert exited with error:\n' + '[stderr]\n%s\n[stdout]\n%s') % (stderr, stdout)) return True diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index 9ca81d738..baa4df176 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -13,7 +13,7 @@ from docutils import nodes, utils from docutils.parsers.rst import Directive, directives from sphinx.roles import XRefRole -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.domains import Domain from sphinx.util.nodes import make_refnode, set_source_info @@ -103,7 +103,7 @@ class MathDomain(Domain): equations = self.data['objects'] if labelid in equations: path = env.doc2path(equations[labelid][0]) - msg = _('duplicate label of equation %s, other instance in %s') % (labelid, path) + msg = __('duplicate label of equation %s, other instance in %s') % (labelid, path) raise UserWarning(msg) else: eqno = self.get_next_equation_number(docname) diff --git a/sphinx/extension.py b/sphinx/extension.py index c3b3b901e..0520bf564 100644 --- a/sphinx/extension.py +++ b/sphinx/extension.py @@ -12,7 +12,7 @@ from six import iteritems from sphinx.errors import VersionRequirementError -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.util import logging if False: @@ -50,12 +50,12 @@ def verify_required_extensions(app, requirements): for extname, reqversion in iteritems(requirements): extension = app.extensions.get(extname) if extension is None: - logger.warning(_('The %s extension is required by needs_extensions settings,' - 'but it is not loaded.'), extname) + logger.warning(__('The %s extension is required by needs_extensions settings,' + 'but it is not loaded.'), extname) continue if extension.version == 'unknown version' or reqversion > extension.version: - raise VersionRequirementError(_('This project needs the extension %s at least in ' - 'version %s and therefore cannot be built with ' - 'the loaded version (%s).') % + raise VersionRequirementError(__('This project needs the extension %s at least in ' + 'version %s and therefore cannot be built with ' + 'the loaded version (%s).') % (extname, reqversion, extension.version)) diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 7c0f9b4f9..6c77eeb8b 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -242,6 +242,16 @@ else: return message +def __(message): + # type: (unicode) -> unicode + """A dummy wrapper to i18n'ize exceptions and command line messages. + + In future, the messages are translated using LC_MESSAGES or any other + locale settings. + """ + return message + + def init(locale_dirs, language, catalog='sphinx'): # type: (List, unicode, unicode) -> Tuple[Any, bool] """Look for message catalogs in `locale_dirs` and *ensure* that there is at diff --git a/sphinx/registry.py b/sphinx/registry.py index 2efd5e08a..0861575db 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -19,7 +19,7 @@ from sphinx.errors import ExtensionError, SphinxError, VersionRequirementError from sphinx.extension import Extension from sphinx.domains import ObjType from sphinx.domains.std import GenericObject, Target -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.roles import XRefRole from sphinx.util import logging from sphinx.util.docutils import directive_helper @@ -53,9 +53,9 @@ class SphinxComponentRegistry(object): def add_builder(self, builder): # type: (Type[Builder]) -> None if not hasattr(builder, 'name'): - raise ExtensionError(_('Builder class %s has no "name" attribute') % builder) + raise ExtensionError(__('Builder class %s has no "name" attribute') % builder) if builder.name in self.builders: - raise ExtensionError(_('Builder %r already exists (in module %s)') % + raise ExtensionError(__('Builder %r already exists (in module %s)') % (builder.name, self.builders[builder.name].__module__)) self.builders[builder.name] = builder @@ -69,22 +69,22 @@ class SphinxComponentRegistry(object): try: entry_point = next(entry_points) except StopIteration: - raise SphinxError(_('Builder name %s not registered or available' - ' through entry point') % name) + raise SphinxError(__('Builder name %s not registered or available' + ' through entry point') % name) self.load_extension(app, entry_point.module_name) def create_builder(self, app, name): # type: (Sphinx, unicode) -> Builder if name not in self.builders: - raise SphinxError(_('Builder name %s not registered') % name) + raise SphinxError(__('Builder name %s not registered') % name) return self.builders[name](app) def add_domain(self, domain): # type: (Type[Domain]) -> None if domain.name in self.domains: - raise ExtensionError(_('domain %s already registered') % domain.name) + raise ExtensionError(__('domain %s already registered') % domain.name) self.domains[domain.name] = domain def has_domain(self, domain): @@ -99,30 +99,30 @@ class SphinxComponentRegistry(object): def override_domain(self, domain): # type: (Type[Domain]) -> None if domain.name not in self.domains: - raise ExtensionError(_('domain %s not yet registered') % domain.name) + raise ExtensionError(__('domain %s not yet registered') % domain.name) if not issubclass(domain, self.domains[domain.name]): - raise ExtensionError(_('new domain not a subclass of registered %s ' - 'domain') % domain.name) + raise ExtensionError(__('new domain not a subclass of registered %s ' + 'domain') % domain.name) self.domains[domain.name] = domain def add_directive_to_domain(self, domain, name, obj, has_content=None, argument_spec=None, **option_spec): # type: (unicode, unicode, Any, bool, Any, Any) -> None if domain not in self.domains: - raise ExtensionError(_('domain %s not yet registered') % domain) + raise ExtensionError(__('domain %s not yet registered') % domain) directive = directive_helper(obj, has_content, argument_spec, **option_spec) self.domains[domain].directives[name] = directive def add_role_to_domain(self, domain, name, role): # type: (unicode, unicode, Any) -> None if domain not in self.domains: - raise ExtensionError(_('domain %s not yet registered') % domain) + raise ExtensionError(__('domain %s not yet registered') % domain) self.domains[domain].roles[name] = role def add_index_to_domain(self, domain, index): # type: (unicode, Type[Index]) -> None if domain not in self.domains: - raise ExtensionError(_('domain %s not yet registered') % domain) + raise ExtensionError(__('domain %s not yet registered') % domain) self.domains[domain].indices.append(index) def add_object_type(self, directivename, rolename, indextemplate='', @@ -157,7 +157,7 @@ class SphinxComponentRegistry(object): def add_source_parser(self, suffix, parser): # type: (unicode, Parser) -> None if suffix in self.source_parsers: - raise ExtensionError(_('source_parser for %r is already registered') % suffix) + raise ExtensionError(__('source_parser for %r is already registered') % suffix) self.source_parsers[suffix] = parser def get_source_parsers(self): @@ -184,8 +184,8 @@ class SphinxComponentRegistry(object): if extname in app.extensions: # alread loaded return if extname in EXTENSION_BLACKLIST: - logger.warning(_('the extension %r was already merged with Sphinx since ' - 'version %s; this extension is ignored.'), + logger.warning(__('the extension %r was already merged with Sphinx since ' + 'version %s; this extension is ignored.'), extname, EXTENSION_BLACKLIST[extname]) return @@ -195,12 +195,12 @@ class SphinxComponentRegistry(object): try: mod = __import__(extname, None, None, ['setup']) except ImportError as err: - logger.verbose(_('Original exception:\n') + traceback.format_exc()) - raise ExtensionError(_('Could not import extension %s') % extname, err) + logger.verbose(__('Original exception:\n') + traceback.format_exc()) + raise ExtensionError(__('Could not import extension %s') % extname, err) if not hasattr(mod, 'setup'): - logger.warning(_('extension %r has no setup() function; is it really ' - 'a Sphinx extension module?'), extname) + logger.warning(__('extension %r has no setup() function; is it really ' + 'a Sphinx extension module?'), extname) metadata = {} # type: Dict[unicode, Any] else: try: @@ -208,9 +208,9 @@ class SphinxComponentRegistry(object): except VersionRequirementError as err: # add the extension name to the version required raise VersionRequirementError( - _('The %s extension used by this project needs at least ' - 'Sphinx v%s; it therefore cannot be built with this ' - 'version.') % (extname, err) + __('The %s extension used by this project needs at least ' + 'Sphinx v%s; it therefore cannot be built with this ' + 'version.') % (extname, err) ) if metadata is None: @@ -218,9 +218,9 @@ class SphinxComponentRegistry(object): if extname == 'rst2pdf.pdfbuilder': metadata['parallel_read_safe'] = True elif not isinstance(metadata, dict): - logger.warning(_('extension %r returned an unsupported object from ' - 'its setup() function; it should return None or a ' - 'metadata dictionary'), extname) + logger.warning(__('extension %r returned an unsupported object from ' + 'its setup() function; it should return None or a ' + 'metadata dictionary'), extname) app.extensions[extname] = Extension(extname, mod, **metadata) app._setting_up_extension.pop() diff --git a/sphinx/theming.py b/sphinx/theming.py index 7a11f4bbf..1cd07b8dc 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -23,7 +23,7 @@ from six.moves import configparser from sphinx import package_dir from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.errors import ThemeError -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.util import logging from sphinx.util.osutil import ensuredir @@ -80,15 +80,15 @@ class Theme(object): try: inherit = self.config.get('theme', 'inherit') except configparser.NoSectionError: - raise ThemeError(_('theme %r doesn\'t have "theme" setting') % name) + raise ThemeError(__('theme %r doesn\'t have "theme" setting') % name) except configparser.NoOptionError: - raise ThemeError(_('theme %r doesn\'t have "inherit" setting') % name) + raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name) if inherit != 'none': try: self.base = factory.create(inherit) except ThemeError: - raise ThemeError(_('no theme named %r found, inherited by %r') % + raise ThemeError(__('no theme named %r found, inherited by %r') % (inherit, name)) def get_theme_dirs(self): @@ -113,8 +113,8 @@ class Theme(object): return self.base.get_config(section, name, default) if default is NODEFAULT: - raise ThemeError(_('setting %s.%s occurs in none of the ' - 'searched theme configs') % (section, name)) + raise ThemeError(__('setting %s.%s occurs in none of the ' + 'searched theme configs') % (section, name)) else: return default @@ -234,7 +234,7 @@ class HTMLThemeFactory(object): if callable(target): themedir = target() if not isinstance(themedir, string_types): - logger.warning(_('Theme extension %r does not respond correctly.') % + logger.warning(__('Theme extension %r does not respond correctly.') % entry_point.module_name) else: themedir = target @@ -261,8 +261,8 @@ class HTMLThemeFactory(object): name = entry[:-4] themes[name] = pathname else: - logger.warning(_('file %r on theme path is not a valid ' - 'zipfile or contains no theme'), entry) + logger.warning(__('file %r on theme path is not a valid ' + 'zipfile or contains no theme'), entry) else: if path.isfile(path.join(pathname, THEMECONF)): themes[entry] = pathname @@ -277,11 +277,11 @@ class HTMLThemeFactory(object): if name not in self.themes: if name == 'sphinx_rtd_theme': - raise ThemeError(_('sphinx_rtd_theme is no longer a hard dependency ' - 'since version 1.4.0. Please install it manually.' - '(pip install sphinx_rtd_theme)')) + raise ThemeError(__('sphinx_rtd_theme is no longer a hard dependency ' + 'since version 1.4.0. Please install it manually.' + '(pip install sphinx_rtd_theme)')) else: - raise ThemeError(_('no theme named %r found ' - '(missing theme.conf?)') % name) + raise ThemeError(__('no theme named %r found ' + '(missing theme.conf?)') % name) return Theme(name, self.themes[name], factory=self) diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index 8e2580a2d..4c4e094d1 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -17,7 +17,7 @@ from docutils.utils import get_source_line from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.environment import NoUri -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.transforms import SphinxTransform from sphinx.util import logging @@ -135,8 +135,8 @@ class ReferencesResolver(SphinxTransform): return None if len(results) > 1: nice_results = ' or '.join(':%s:' % r[0] for r in results) - logger.warning(_('more than one target found for \'any\' cross-' - 'reference %r: could be %s'), target, nice_results, + logger.warning(__('more than one target found for \'any\' cross-' + 'reference %r: could be %s'), target, nice_results, location=node) res_role, newnode = results[0] # Override "any" class with the actual role type to get the styling @@ -165,10 +165,10 @@ class ReferencesResolver(SphinxTransform): if domain and typ in domain.dangling_warnings: msg = domain.dangling_warnings[typ] elif node.get('refdomain', 'std') not in ('', 'std'): - msg = (_('%s:%s reference target not found: %%(target)s') % + msg = (__('%s:%s reference target not found: %%(target)s') % (node['refdomain'], typ)) else: - msg = _('%r reference target not found: %%(target)s') % typ + msg = __('%r reference target not found: %%(target)s') % typ logger.warning(msg % {'target': target}, location=node, type='ref', subtype=typ) diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index 1fe72af58..c984bcfaf 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -22,7 +22,7 @@ from docutils.utils import Reporter from docutils.parsers.rst import directives, roles, convert_directive_function from sphinx.errors import ExtensionError -from sphinx.locale import _ +from sphinx.locale import __ from sphinx.util import logging logger = logging.getLogger(__name__) @@ -193,6 +193,6 @@ def directive_helper(obj, has_content=None, argument_spec=None, **option_spec): return convert_directive_function(obj) else: if has_content or argument_spec or option_spec: - raise ExtensionError(_('when adding directive classes, no ' - 'additional arguments may be given')) + raise ExtensionError(__('when adding directive classes, no ' + 'additional arguments may be given')) return obj From 8f8f0fb868b0935c43d2ea4158877529d948ccc9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 25 Jun 2017 18:11:28 +0900 Subject: [PATCH 06/18] Fix #3860: Don't download images when builders not supported images --- CHANGES | 1 + sphinx/transforms/post_transforms/images.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 9ad92038d..4522b15c7 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,7 @@ Bugs fixed * #3873: Failure of deprecation warning mechanism of ``sphinx.util.compat.Directive`` * #3874: Bogus warnings for "citation not referenced" for cross-file citations +* #3860: Don't download images when builders not supported images Testing -------- diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index e1cc9f78b..196b6dd5e 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -57,7 +57,9 @@ class ImageDownloader(BaseImageConverter): def match(self, node): # type: (nodes.Node) -> bool - if self.app.builder.supported_remote_images: + if self.app.builder.supported_image_types == []: + return False + elif self.app.builder.supported_remote_images: return False else: return '://' in node['uri'] @@ -108,7 +110,9 @@ class DataURIExtractor(BaseImageConverter): def match(self, node): # type: (nodes.Node) -> bool - if self.app.builder.supported_data_uri_images: + if self.app.builder.supported_remote_images == []: + return False + elif self.app.builder.supported_data_uri_images is True: return False else: return 'data:' in node['uri'] From ea87cfba4fccdc86cacc3ebc54c96f6bf861d310 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 25 Jun 2017 18:49:13 +0900 Subject: [PATCH 07/18] Fix #3860: Remote image URIs without filename break builders not supported remote images --- CHANGES | 2 ++ sphinx/transforms/post_transforms/images.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4522b15c7..1bfbed055 100644 --- a/CHANGES +++ b/CHANGES @@ -39,6 +39,8 @@ Bugs fixed ``sphinx.util.compat.Directive`` * #3874: Bogus warnings for "citation not referenced" for cross-file citations * #3860: Don't download images when builders not supported images +* #3860: Remote image URIs without filename break builders not supported remote + images Testing -------- diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 196b6dd5e..c57355774 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -20,7 +20,7 @@ from sphinx.transforms import SphinxTransform from sphinx.util import logging, requests from sphinx.util import epoch_to_rfc1123, rfc1123_to_epoch from sphinx.util.images import guess_mimetype, get_image_extension, parse_data_uri -from sphinx.util.osutil import ensuredir +from sphinx.util.osutil import ensuredir, movefile if False: # For type annotation @@ -69,6 +69,8 @@ class ImageDownloader(BaseImageConverter): basename = os.path.basename(node['uri']) if '?' in basename: basename = basename.split('?')[0] + if basename == '': + basename = sha1(node['uri']).hexdigest() dirname = node['uri'].replace('://', '/').translate({ord("?"): u"/", ord("&"): u"/"}) ensuredir(os.path.join(self.imagedir, dirname)) @@ -96,6 +98,14 @@ class ImageDownloader(BaseImageConverter): os.utime(path, (timestamp, timestamp)) mimetype = guess_mimetype(path, default='*') + if mimetype != '*' and os.path.splitext(basename)[1] == '': + # append a suffix if URI does not contain suffix + ext = get_image_extension(mimetype) + newpath = os.path.join(self.imagedir, dirname, basename + ext) + movefile(path, newpath) + self.app.env.original_image_uri.pop(path) + self.app.env.original_image_uri[newpath] = node['uri'] + path = newpath node['candidates'].pop('?') node['candidates'][mimetype] = path node['uri'] = path From 372e8f2c64c7e8aa530d79a57f9626ad542e1357 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 25 Jun 2017 18:51:49 +0900 Subject: [PATCH 08/18] Add simple svg tester for imghdr --- sphinx/util/images.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 2d228e66d..eba295a3c 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -120,3 +120,17 @@ def parse_data_uri(uri): image_data = base64.b64decode(data) return DataURI(mimetype, charset, image_data) + + +def test_svg(h, f): + """An additional imghdr library helper; test the header is SVG's or not.""" + try: + if ' Date: Sun, 25 Jun 2017 18:52:12 +0900 Subject: [PATCH 09/18] Show correct URI if image candidate not found --- sphinx/builders/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index fdf524bf6..007964e82 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -22,6 +22,7 @@ from six import itervalues from docutils import nodes from sphinx.deprecation import RemovedInSphinx20Warning +from sphinx.environment.adapters.asset import ImageAdapter from sphinx.util import i18n, path_stabilize, logging, status_iterator from sphinx.util.osutil import SEP, relative_uri from sphinx.util.i18n import find_catalog @@ -200,6 +201,7 @@ class Builder(object): def post_process_images(self, doctree): # type: (nodes.Node) -> None """Pick the best candidate for all image URIs.""" + images = ImageAdapter(self.env) for node in doctree.traverse(nodes.image): if '?' in node['candidates']: # don't rewrite nonlocal image URIs @@ -210,7 +212,8 @@ class Builder(object): if candidate: break else: - logger.warning('no matching candidate for image URI %r', node['uri'], + logger.warning('no matching candidate for image URI %r', + images.get_original_image_uri(node['uri']), location=node) continue node['uri'] = candidate From 1d0f667ecd6fcaecadb59f7006f1816a2a236a16 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 25 Jun 2017 23:03:23 +0900 Subject: [PATCH 10/18] Update warning message (ref: #3873) --- sphinx/util/compat.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sphinx/util/compat.py b/sphinx/util/compat.py index 20975f83d..3a5356ff7 100644 --- a/sphinx/util/compat.py +++ b/sphinx/util/compat.py @@ -35,9 +35,8 @@ class _DeprecationWrapper(object): def __getattr__(self, attr): # type: (str) -> Any if attr in self._deprecated: - warnings.warn("sphinx.util.compat.%s is deprecated and will be " - "removed in Sphinx 1.7, please use the standard " - "library version instead." % attr, + warnings.warn("sphinx.util.compat.%s is deprecated and will be removed " + "in Sphinx 1.7, please use docutils' instead." % attr, RemovedInSphinx17Warning) return self._deprecated[attr] return getattr(self._mod, attr) From f0766ce4cd7f4c54c1afd16cb03ebf9458a62d4f Mon Sep 17 00:00:00 2001 From: Yoshiki Shibukawa Date: Fri, 30 Jun 2017 01:21:16 +0900 Subject: [PATCH 11/18] fix #3840: epub_uid should be XML Name --- CHANGES | 1 + doc/conf.py | 1 + doc/config.rst | 5 ++++- sphinx/builders/epub3.py | 6 +++--- sphinx/util/__init__.py | 33 +++++++++++++++++++++++++++++++++ tests/test_build_epub.py | 2 ++ tests/test_util.py | 11 ++++++++++- 7 files changed, 54 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 9ad92038d..4228c27c2 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,7 @@ Bugs fixed * #3873: Failure of deprecation warning mechanism of ``sphinx.util.compat.Directive`` * #3874: Bogus warnings for "citation not referenced" for cross-file citations +* #3840: make checking ``epub_uid`` strict Testing -------- diff --git a/doc/conf.py b/doc/conf.py index 3cde5bf3b..62c5c13d5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,6 +34,7 @@ epub_theme = 'epub' epub_basename = 'sphinx' epub_author = 'Georg Brandl' epub_publisher = 'http://sphinx-doc.org/' +epub_uid = 'web-site' epub_scheme = 'url' epub_identifier = epub_publisher epub_pre_files = [('index.xhtml', 'Welcome')] diff --git a/doc/config.rst b/doc/config.rst index 3aab2e7b2..a094195e9 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -1360,7 +1360,10 @@ the `Dublin Core metadata `_. .. confval:: epub_uid A unique identifier for the document. This is put in the Dublin Core - metadata. You may use a random string. The default value is ``'unknown'``. + metadata. You may use a + `XML's Name format `_ string. + You can't use hyphen, period, numbers as a first character. + The default value is ``'unknown'``. .. confval:: epub_cover diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index 19baad344..6256b0f6d 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -17,7 +17,7 @@ from collections import namedtuple from sphinx import package_dir from sphinx.config import string_classes, ENUM from sphinx.builders import _epub_base -from sphinx.util import logging +from sphinx.util import logging, xmlname_checker from sphinx.util.fileutil import copy_asset_file if False: @@ -89,8 +89,8 @@ class Epub3Builder(_epub_base.EpubBuilder): 'conf value "epub_language" (or "language") ' 'should not be empty for EPUB3') # unique-identifier attribute - if not self.app.config.epub_uid: - self.app.warn('conf value "epub_uid" should not be empty for EPUB3') + if not xmlname_checker().match(self.app.config.epub_uid): + self.app.warn('conf value "epub_uid" should be XML NAME for EPUB3') # dc:title if not self.app.config.epub_title: self.app.warn( diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 295848e40..03f8ce6a3 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -631,3 +631,36 @@ def epoch_to_rfc1123(epoch): def rfc1123_to_epoch(rfc1123): return mktime(strptime(rfc1123, '%a, %d %b %Y %H:%M:%S %Z')) + + +def xmlname_checker(): + # https://www.w3.org/TR/REC-xml/#NT-Name + # Only Python 3.3 or newer support character code in regular expression + name_start_chars = [ + u':', [u'A', u'Z'], u'_', [u'a', u'z'], [u'\u00C0', u'\u00D6'], + [u'\u00D8', u'\u00F6'], [u'\u00F8', u'\u02FF'], [u'\u0370', u'\u037D'], + [u'\u037F', u'\u1FFF'], [u'\u200C', u'\u200D'], [u'\u2070', u'\u218F'], + [u'\u2C00', u'\u2FEF'], [u'\u3001', u'\uD7FF'], [u'\uF900', u'\uFDCF'], + [u'\uFDF0', u'\uFFFD']] + + if sys.version_info.major == 3: + name_start_chars.append([u'\U00010000', u'\U000EFFFF']) + + name_chars = [ + u"\\-", u"\\.", [u'0', u'9'], u'\u00B7', [u'\u0300', u'\u036F'], + [u'\u203F', u'\u2040'] + ] + + def convert(entries, splitter=u'|'): + results = [] + for entry in entries: + if isinstance(entry, list): + results.append(u'[%s]' % convert(entry, u'-')) + else: + results.append(entry) + return splitter.join(results) + + start_chars_regex = convert(name_start_chars) + name_chars_regex = convert(name_chars) + return re.compile(u'(%s)(%s|%s)*' % ( + start_chars_regex, start_chars_regex, name_chars_regex)) diff --git a/tests/test_build_epub.py b/tests/test_build_epub.py index e5d86b0ed..397547734 100644 --- a/tests/test_build_epub.py +++ b/tests/test_build_epub.py @@ -245,3 +245,5 @@ def test_epub_writing_mode(app): # vertical / writing-mode (CSS) css = (app.outdir / '_static' / 'epub.css').text() assert 'writing-mode: vertical-rl;' in css + + diff --git a/tests/test_util.py b/tests/test_util.py index b0543a246..84ce44007 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -14,7 +14,8 @@ from mock import patch from sphinx.util import logging from sphinx.util import ( - display_chunk, encode_uri, parselinenos, split_docinfo, status_iterator + display_chunk, encode_uri, parselinenos, split_docinfo, status_iterator, + xmlname_checker ) from sphinx.testing.util import strip_escseq @@ -115,3 +116,11 @@ def test_parselinenos(): parselinenos('-', 10) with pytest.raises(ValueError): parselinenos('3-1', 10) + + + +def test_xmlname_check(): + checker = xmlname_checker() + assert checker.match('id-pub') + assert checker.match('webpage') + assert not checker.match('1bfda21') From 1f8604e481383a7ceaa24d2e34e66f92acac5522 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 1 Jul 2017 18:20:01 +0900 Subject: [PATCH 12/18] Fix :manpage: role is smarty-quoted (refs: #3286) --- sphinx/addnodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 6cb47d63d..762dc9bbb 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -287,7 +287,7 @@ class abbreviation(nodes.Inline, nodes.TextElement): """Node for abbreviations with explanations.""" -class manpage(nodes.Inline, nodes.TextElement): +class manpage(nodes.Inline, nodes.FixedTextElement): """Node for references to manpages.""" From 82adedbc56bc00f0371918bd7cda1d412623de41 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Jul 2017 17:08:27 +0900 Subject: [PATCH 13/18] Update CHANGES for PR #3876 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index f0d4e8db5..b4b5f0865 100644 --- a/CHANGES +++ b/CHANGES @@ -44,6 +44,7 @@ Bugs fixed * #3833: command line messages are translated unintentionally with ``language`` setting. * #3840: make checking ``epub_uid`` strict +* #3851, #3706: Fix about box drawing characters for PDF output Testing -------- From b3e0e29f4cd3a3bec12a07336ebbda430e25e27e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Jul 2017 18:26:41 +0900 Subject: [PATCH 14/18] Fix #3900: autosummary could not find methods --- CHANGES | 1 + sphinx/ext/autosummary/generate.py | 5 ++++- .../autodoc_dummy_module.py | 9 +++++++++ tests/roots/test-ext-autosummary/conf.py | 9 +++++++++ tests/roots/test-ext-autosummary/contents.rst | 6 ++++++ tests/test_ext_autosummary.py | 19 +++++++++++++++++++ 6 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autosummary/autodoc_dummy_module.py create mode 100644 tests/roots/test-ext-autosummary/conf.py create mode 100644 tests/roots/test-ext-autosummary/contents.rst diff --git a/CHANGES b/CHANGES index b4b5f0865..f4e569130 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,7 @@ Bugs fixed setting. * #3840: make checking ``epub_uid`` strict * #3851, #3706: Fix about box drawing characters for PDF output +* #3900: autosummary could not find methods Testing -------- diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index d40b4787d..775490471 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -195,7 +195,10 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', continue documenter = get_documenter(value, obj) if documenter.objtype == typ: - if imported or getattr(value, '__module__', None) == obj.__name__: + if typ == 'method': + items.append(name) + elif imported or getattr(value, '__module__', None) == obj.__name__: + # skip imported members if expected items.append(name) public = [x for x in items if x in include_public or not x.startswith('_')] diff --git a/tests/roots/test-ext-autosummary/autodoc_dummy_module.py b/tests/roots/test-ext-autosummary/autodoc_dummy_module.py new file mode 100644 index 000000000..aa4aaa9f2 --- /dev/null +++ b/tests/roots/test-ext-autosummary/autodoc_dummy_module.py @@ -0,0 +1,9 @@ +from os import * + + +class Foo: + def __init__(self): + pass + + def bar(self): + pass diff --git a/tests/roots/test-ext-autosummary/conf.py b/tests/roots/test-ext-autosummary/conf.py new file mode 100644 index 000000000..f4d696cc9 --- /dev/null +++ b/tests/roots/test-ext-autosummary/conf.py @@ -0,0 +1,9 @@ +import sys, os + +sys.path.insert(0, os.path.abspath('.')) + +extensions = ['sphinx.ext.autosummary'] +autosummary_generate = True + +# The suffix of source filenames. +source_suffix = '.rst' diff --git a/tests/roots/test-ext-autosummary/contents.rst b/tests/roots/test-ext-autosummary/contents.rst new file mode 100644 index 000000000..ccde9b2c5 --- /dev/null +++ b/tests/roots/test-ext-autosummary/contents.rst @@ -0,0 +1,6 @@ + +.. autosummary:: + :toctree: generated + + autodoc_dummy_module + autodoc_dummy_module.Foo diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index ddd033154..9fcd00984 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -126,3 +126,22 @@ def test_escaping(app, status, warning): title = etree_parse(docpage).find('section/title') assert str_content(title) == 'underscore_module_' + + +@pytest.mark.sphinx('dummy', testroot='ext-autosummary') +def test_autosummary_generate(app, status, warning): + app.builder.build_all() + + module = (app.srcdir / 'generated' / 'autodoc_dummy_module.rst').text() + assert (' .. autosummary::\n' + ' \n' + ' Foo\n' + ' \n' in module) + + Foo = (app.srcdir / 'generated' / 'autodoc_dummy_module.Foo.rst').text() + assert '.. automethod:: __init__' in Foo + assert (' .. autosummary::\n' + ' \n' + ' ~Foo.__init__\n' + ' ~Foo.bar\n' + ' \n' in Foo) From dc6a8a54574ac699df8044f0b1875c76895f1daa Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Jul 2017 18:41:03 +0900 Subject: [PATCH 15/18] test: Fix autosummary testcase --- .../{autodoc_dummy_module.py => autosummary_dummy_module.py} | 0 tests/roots/test-ext-autosummary/contents.rst | 4 ++-- tests/test_ext_autosummary.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename tests/roots/test-ext-autosummary/{autodoc_dummy_module.py => autosummary_dummy_module.py} (100%) diff --git a/tests/roots/test-ext-autosummary/autodoc_dummy_module.py b/tests/roots/test-ext-autosummary/autosummary_dummy_module.py similarity index 100% rename from tests/roots/test-ext-autosummary/autodoc_dummy_module.py rename to tests/roots/test-ext-autosummary/autosummary_dummy_module.py diff --git a/tests/roots/test-ext-autosummary/contents.rst b/tests/roots/test-ext-autosummary/contents.rst index ccde9b2c5..3b43086a2 100644 --- a/tests/roots/test-ext-autosummary/contents.rst +++ b/tests/roots/test-ext-autosummary/contents.rst @@ -2,5 +2,5 @@ .. autosummary:: :toctree: generated - autodoc_dummy_module - autodoc_dummy_module.Foo + autosummary_dummy_module + autosummary_dummy_module.Foo diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index 9fcd00984..81fd35762 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -132,13 +132,13 @@ def test_escaping(app, status, warning): def test_autosummary_generate(app, status, warning): app.builder.build_all() - module = (app.srcdir / 'generated' / 'autodoc_dummy_module.rst').text() + module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').text() assert (' .. autosummary::\n' ' \n' ' Foo\n' ' \n' in module) - Foo = (app.srcdir / 'generated' / 'autodoc_dummy_module.Foo.rst').text() + Foo = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.rst').text() assert '.. automethod:: __init__' in Foo assert (' .. autosummary::\n' ' \n' From 4791c35edf8fe3262f7de98fc3472c6432c31224 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Jul 2017 20:00:21 +0900 Subject: [PATCH 16/18] Fix #3902: Emit error if ``latex_documents`` contains non-unicode string in py2 --- CHANGES | 1 + sphinx/builders/latex.py | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f4e569130..789732a51 100644 --- a/CHANGES +++ b/CHANGES @@ -46,6 +46,7 @@ Bugs fixed * #3840: make checking ``epub_uid`` strict * #3851, #3706: Fix about box drawing characters for PDF output * #3900: autosummary could not find methods +* #3902: Emit error if ``latex_documents`` contains non-unicode string in py2 Testing -------- diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py index b7aef0f4f..03a75caab 100644 --- a/sphinx/builders/latex.py +++ b/sphinx/builders/latex.py @@ -13,6 +13,8 @@ import os import warnings from os import path +from six import text_type + from docutils import nodes from docutils.io import FileOutput from docutils.utils import new_document @@ -21,7 +23,7 @@ from docutils.frontend import OptionParser from sphinx import package_dir, addnodes, highlighting from sphinx.deprecation import RemovedInSphinx17Warning from sphinx.config import string_classes, ENUM -from sphinx.errors import SphinxError +from sphinx.errors import SphinxError, ConfigError from sphinx.locale import _ from sphinx.builders import Builder from sphinx.environment import NoUri @@ -278,6 +280,23 @@ def validate_config_values(app): "LaTeX markup since Sphinx 1.4.5 uses only prefixed macro names.", RemovedInSphinx17Warning) + for document in app.config.latex_documents: + try: + text_type(document[2]) + except UnicodeDecodeError: + raise ConfigError( + 'Invalid latex_documents.title found (might contain non-ASCII chars. ' + 'Please use u"..." notation instead): %r' % (document,) + ) + + try: + text_type(document[3]) + except UnicodeDecodeError: + raise ConfigError( + 'Invalid latex_documents.author found (might contain non-ASCII chars. ' + 'Please use u"..." notation instead): %r' % (document,) + ) + def default_latex_engine(config): # type: (Config) -> unicode From ddeccfc2c747942646b24fd52a0d09757cc9308b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Jul 2017 20:15:05 +0900 Subject: [PATCH 17/18] Bump to 1.6.3 final --- CHANGES | 16 ++-------------- sphinx/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 789732a51..487bee72f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,14 +1,5 @@ -Release 1.6.3 (in development) -============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- +Release 1.6.3 (released Jul 02, 2017) +===================================== Features added -------------- @@ -48,9 +39,6 @@ Bugs fixed * #3900: autosummary could not find methods * #3902: Emit error if ``latex_documents`` contains non-unicode string in py2 -Testing --------- - Release 1.6.2 (released May 28, 2017) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 2f661bafb..e8386160c 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -34,13 +34,13 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '1.6.3+' +__version__ = '1.6.3' __released__ = '1.6.3' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 6, 3, 'beta', 0) +version_info = (1, 6, 3, 'final', 0) package_dir = path.abspath(path.dirname(__file__)) From 7a8384c81fe390debfc76c4724cc7ff0c173e585 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Jul 2017 20:18:52 +0900 Subject: [PATCH 18/18] Bump version --- CHANGES | 21 +++++++++++++++++++++ sphinx/__init__.py | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 487bee72f..3ff50f28b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,24 @@ +Release 1.6.4 (in development) +============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + Release 1.6.3 (released Jul 02, 2017) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index e8386160c..e574ffe6c 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -34,13 +34,13 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '1.6.3' -__released__ = '1.6.3' # used when Sphinx builds its own docs +__version__ = '1.6.4+' +__released__ = '1.6.4' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 6, 3, 'final', 0) +version_info = (1, 6, 4, 'beta', 0) package_dir = path.abspath(path.dirname(__file__))