diff options
| author | milde <milde@929543f6-e4f2-0310-98a6-ba3bd3dd1d04> | 2020-01-26 21:23:12 +0000 |
|---|---|---|
| committer | milde <milde@929543f6-e4f2-0310-98a6-ba3bd3dd1d04> | 2020-01-26 21:23:12 +0000 |
| commit | 56dfe840ef1672de152202bc6822ad0f60e07f91 (patch) | |
| tree | c0a51435b7ce5f1fe7772b6102fa645874c48aed | |
| parent | 858321d42c430498e5e76d5bd7321c4c205ce8a3 (diff) | |
| download | docutils-56dfe840ef1672de152202bc6822ad0f60e07f91.tar.gz | |
Fix/update import of language modules (#385).
Docutils loads language modules from its own set or the working
directory. (The latter is an undocumented but tested feature.)
However, a Python file or a directory that is named like an
unsupported language (e.g. "tr") led to errors (#385).
* Use importlib.import_module() instead of the low-level __import__().
* Use a common class for docutils.languages.get_language() and
docutils.parsers.rst.get_language().
* Report missing language support also for parsers.rst.get_language().
cf. https://docutils.sourceforge.io/docs/howto/i18n.html
git-svn-id: http://svn.code.sf.net/p/docutils/code/trunk@8465 929543f6-e4f2-0310-98a6-ba3bd3dd1d04
| -rw-r--r-- | docutils/docutils/languages/__init__.py | 75 | ||||
| -rw-r--r-- | docutils/docutils/parsers/rst/languages/__init__.py | 41 | ||||
| -rw-r--r-- | docutils/docutils/parsers/rst/states.py | 2 |
3 files changed, 74 insertions, 44 deletions
diff --git a/docutils/docutils/languages/__init__.py b/docutils/docutils/languages/__init__.py index 2edf9d5d6..8b871f7d8 100644 --- a/docutils/docutils/languages/__init__.py +++ b/docutils/docutils/languages/__init__.py @@ -12,36 +12,65 @@ This package contains modules for language-dependent features of Docutils. __docformat__ = 'reStructuredText' import sys +from importlib import import_module from docutils.utils import normalize_language_tag +class LanguageImporter(object): -_languages = {} + packages = ('docutils.languages.', '') + warn_msg = ('Language "%s" not supported: ' + 'Docutils-generated text will be in English.') + fallback = 'en' + # TODO: use a dummy module returning emtpy strings?, configurable? -def get_language(language_code, reporter=None): - """Return module with language localizations. + def __init__(self): + self.cache = {} - `language_code` is a "BCP 47" language tag. - If there is no matching module, warn and fall back to English. - """ - # TODO: use a dummy module returning emtpy strings?, configurable? - for tag in normalize_language_tag(language_code): - tag = tag.replace('-', '_') # '-' not valid in module names - if tag in _languages: - return _languages[tag] + def import_from_packages(self, name): + """Try loading module `name` from `self.packages`.""" try: - module = __import__(tag, globals(), locals(), level=1) - except ImportError: + return self.cache[name] + except KeyError: + pass + for package in self.packages: try: - module = __import__(tag, globals(), locals(), level=0) - except ImportError: + module = import_module(package+name) + self.check_content(module) + except (ImportError, AttributeError, AssertionError): continue - _languages[tag] = module + self.cache[name] = module + return module + return None + + def check_content(self, module): + """Check if we got a Docutils language module.""" + assert isinstance(module.labels, dict) + assert isinstance(module.bibliographic_fields, dict) + assert isinstance(module.author_separators, list) + + def __call__(self, language_code, reporter=None): + try: + return self.cache[language_code] + except KeyError: + pass + for tag in normalize_language_tag(language_code): + tag = tag.replace('-', '_') # '-' not valid in module names + module = self.import_from_packages(tag) + if module is not None: + return module + if reporter is not None: + reporter.warning(self.warn_msg % language_code) + module = self.import_from_packages('en') + self.cache[tag] = module # warn only one time! return module - if reporter is not None: - reporter.warning( - 'language "%s" not supported: ' % language_code + - 'Docutils-generated text will be in English.') - module = __import__('en', globals(), locals(), level=1) - _languages[tag] = module # warn only one time! - return module + + +get_language = LanguageImporter() +"""Return module with language localizations + +from `docutils.languages` or the PYTHONPATH. + +The argument `language_code` is a "BCP 47" language tag. +If there is no matching module, warn and fall back to English. +""" diff --git a/docutils/docutils/parsers/rst/languages/__init__.py b/docutils/docutils/parsers/rst/languages/__init__.py index 98a9d8b8b..1227155cb 100644 --- a/docutils/docutils/parsers/rst/languages/__init__.py +++ b/docutils/docutils/parsers/rst/languages/__init__.py @@ -14,23 +14,24 @@ __docformat__ = 'reStructuredText' import sys -from docutils.utils import normalize_language_tag - - -_languages = {} - -def get_language(language_code): - for tag in normalize_language_tag(language_code): - tag = tag.replace('-', '_') # '-' not valid in module names - if tag in _languages: - return _languages[tag] - try: - module = __import__(tag, globals(), locals(), level=1) - except ImportError: - try: - module = __import__(tag, globals(), locals(), level=0) - except ImportError: - continue - _languages[tag] = module - return module - return None +from docutils.languages import LanguageImporter + +class RstLanguageImporter(LanguageImporter): + packages = ('docutils.parsers.rst.languages.', '') + warn_msg = 'rST localisation for language "%s" not found.' + fallback = None + + def check_content(self, module): + """Check if we got an rST language module.""" + assert isinstance(module.directives, dict) + assert isinstance(module.roles, dict) + +get_language = RstLanguageImporter() +"""Return module with language localizations for reStructuredText. + +Get translated directive and rolenames from `docutils.parsers.rst.languages` +or the PYTHONPATH. + +The argument `language_code` is a "BCP 47" language tag. +If there is no matching module, warn and return None. +""" diff --git a/docutils/docutils/parsers/rst/states.py b/docutils/docutils/parsers/rst/states.py index 4e338b5a0..15ae909ee 100644 --- a/docutils/docutils/parsers/rst/states.py +++ b/docutils/docutils/parsers/rst/states.py @@ -151,7 +151,7 @@ class RSTStateMachine(StateMachineWS): run the StateMachine. """ self.language = languages.get_language( - document.settings.language_code) + document.settings.language_code, document.reporter) self.match_titles = match_titles if inliner is None: inliner = Inliner() |
