diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..15cd6cb --- /dev/null +++ b/babel.cfg @@ -0,0 +1,2 @@ +[python: **.py] + diff --git a/muranopkgcheck/i18n.py b/muranopkgcheck/i18n.py new file mode 100644 index 0000000..8b23ebd --- /dev/null +++ b/muranopkgcheck/i18n.py @@ -0,0 +1,33 @@ + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""oslo.i18n integration module. +See http://docs.openstack.org/developer/oslo.i18n/usage.html +""" + +import oslo_i18n + +_translators = oslo_i18n.TranslatorFactory(domain='murano-pkg-checker') + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical diff --git a/muranopkgcheck/manager.py b/muranopkgcheck/manager.py index 8663ed5..1f00008 100644 --- a/muranopkgcheck/manager.py +++ b/muranopkgcheck/manager.py @@ -22,6 +22,8 @@ import six import stevedore from muranopkgcheck import error +from muranopkgcheck.i18n import _ +from muranopkgcheck.i18n import _LE from muranopkgcheck import log from muranopkgcheck import pkg_loader from muranopkgcheck.validators import VALIDATORS @@ -76,17 +78,18 @@ class Manager(object): check_locals = tb.tb_frame.f_locals.copy() check_locals.pop('self', None) if validator_class: - msg = ('Checker {} from {} failed!' - ''.format(check_name, - validator_class.__class__.__name__)) + msg = (_('Checker {} from {} failed!' + '').format(check_name, + validator_class.__class__.__name__)) else: - msg = ('Checker {} failed!' - ''.format(check_name)) + msg = (_('Checker {} failed!' + '').format(check_name)) LOG.error('{} {}\n{}'.format(msg, - 'Checker locals:', + _('Checker locals:'), pprint.pformat(check_locals)), exc_info=exc_info) - e = error.report.E000(msg + ' See more information in logs.') + e = error.report.E000( + msg + _(' See more information in logs.')) if isinstance(e, types.GeneratorType): errors.extend(self._to_list(e, select, ignore)) else: @@ -115,7 +118,8 @@ class Manager(object): @staticmethod def failure_hook(_, ep, err): - LOG.error('Could not load %r: %s', ep.name, err) + LOG.error(_LE('Could not load {plugin}: {error}' + '').format(plugin=ep.name, error=err)) raise err def validate(self, validators=None, select=None, ignore=None): diff --git a/muranopkgcheck/pkg_loader.py b/muranopkgcheck/pkg_loader.py index 4c28b99..d9f10ef 100644 --- a/muranopkgcheck/pkg_loader.py +++ b/muranopkgcheck/pkg_loader.py @@ -24,6 +24,7 @@ import six import yaml from muranopkgcheck import consts +from muranopkgcheck.i18n import _ from muranopkgcheck import log from muranopkgcheck import yaml_loader @@ -195,4 +196,4 @@ def load_package(path, quiet=False): LOG.debug("{} failed to load '{}'" "".format(loader_cls.__name__, path)) else: - raise ValueError("Can not load package: '{}'".format(path)) + raise ValueError(_('Can not load package: "{}"').format(path)) diff --git a/muranopkgcheck/tests/test_manifest_validator.py b/muranopkgcheck/tests/test_manifest_validator.py index ea2de1d..a4a3a87 100644 --- a/muranopkgcheck/tests/test_manifest_validator.py +++ b/muranopkgcheck/tests/test_manifest_validator.py @@ -97,16 +97,16 @@ class ManifestValidatorTests(helpers.BaseValidatorTestClass): 'org.openstack.Instance': 'Instance.yaml'} self.loaded_package.search_for.return_value = ['FlowClassifier.yaml'] self.g = self.mv._valid_classes(data) - self.assertIn('File is present in Manifest Instance.yaml, but not in ' - 'filesystem', next(self.g).message) + self.assertIn('File "Instance.yaml" is present in Manifest, ' + 'but not in filesystem', next(self.g).message) def test_extra_file_in_directory(self): data = {'org.openstack.Instance': 'Instance.yaml'} self.loaded_package.search_for.return_value = ['FlowClassifier.yaml', 'Instance.yaml'] self.g = self.mv._valid_classes(data) - self.assertIn('File is not present in Manifest, but it is in ' - 'filesystem: FlowClassifier.yaml', next(self.g).message) + self.assertIn('File "FlowClassifier.yaml" is not present in Manifest, ' + 'but it is in filesystem', next(self.g).message) def test_classess_list(self): data = [{'org.openstack.Instance': 'Instance.yaml'}] diff --git a/muranopkgcheck/tests/test_muranopl_validator.py b/muranopkgcheck/tests/test_muranopl_validator.py index 6016c5d..7559f48 100644 --- a/muranopkgcheck/tests/test_muranopl_validator.py +++ b/muranopkgcheck/tests/test_muranopl_validator.py @@ -67,7 +67,7 @@ class MuranoPlTests(helpers.BaseValidatorTestClass): def test_import_error(self): self.g = self.mpl_validator._valid_import(['aaa.bbb', 'ccc.ddd', 'fff', 'w_ww#']) - self.assertIn('Wrong namespace of import "w_ww#"', + self.assertIn('Wrong namespace or FNQ of extended class "w_ww#"', next(self.g).message) def test_double_underscored_name(self): diff --git a/muranopkgcheck/tests/test_package.py b/muranopkgcheck/tests/test_package.py index ddd784a..e1887e6 100644 --- a/muranopkgcheck/tests/test_package.py +++ b/muranopkgcheck/tests/test_package.py @@ -29,12 +29,12 @@ class PackageValidatorTests(helpers.BaseValidatorTestClass): self.loaded_package.search_for.return_value = [ 'manifest.yaml', 'LICENSE', 'logo'] self.g = self.pv.run() - self.assertIn('Unknown "logo" in package directory', + self.assertIn('Unknown "logo" in the package', next(self.g).message) def test_known_files_missing_req(self): self.loaded_package.search_for.return_value = [ 'manifest.yaml', 'logo.png'] self.g = self.pv.run() - self.assertIn('Missing "LICENSE" in package directory', + self.assertIn('Missing "LICENSE" in the package', next(self.g).message) diff --git a/muranopkgcheck/validators/base.py b/muranopkgcheck/validators/base.py index d6eb22a..6862344 100644 --- a/muranopkgcheck/validators/base.py +++ b/muranopkgcheck/validators/base.py @@ -19,6 +19,7 @@ import re import six from muranopkgcheck import error +from muranopkgcheck.i18n import _ from muranopkgcheck import log LOG = log.getLogger(__name__) @@ -55,10 +56,10 @@ class BaseValidator(object): pass def _valid_string(self, value): + if not isinstance(value, six.string_types): - yield error.report.E040('Value is not a string "{0}"' - .format(value), - value) + yield error.report.E040(_('Value is not a string "{}"' + '').format(value), value) def _check_name(self, name): if isinstance(name, six.string_types) and NAME_REGEX.match(name): @@ -123,7 +124,7 @@ class YamlValidator(BaseValidator): if len(multi_documents) > 1 and not self._allows_multi: reports_chain.append([ - error.report.E005('Multi document is not allowed in {0}' + error.report.E005(_('Multi document is not allowed in {}') .format(file_._path))]) for ast in multi_documents: @@ -141,17 +142,18 @@ class YamlValidator(BaseValidator): if value['required']) - set(ast.keys()) for m in missing: reports_chain.append([ - error.report.E020('Missing required key "{0}"' + error.report.E020(_('Missing required key "{}"') .format(m), m)]) + return itertools.chain(*reports_chain) def _valid_keywords(self, present, known): unknown = set(present) - set(known) for u in unknown: - yield error.report.E021('Unknown keyword "{0}"'.format(u), u) + yield error.report.E021(_('Unknown keyword "{}"').format(u), u) def _unknown_keyword(self, key, value): - yield error.report.W010('Unknown keyword "{0}"'.format(key), key) + yield error.report.W010(_('Unknown keyword "{}"').format(key), key) def _null_checker(self, value): pass diff --git a/muranopkgcheck/validators/manifest.py b/muranopkgcheck/validators/manifest.py index 38c782f..822ab31 100644 --- a/muranopkgcheck/validators/manifest.py +++ b/muranopkgcheck/validators/manifest.py @@ -19,6 +19,7 @@ import semantic_version import six from muranopkgcheck import error +from muranopkgcheck.i18n import _ from muranopkgcheck.validators import base from muranopkgcheck import yaml_loader @@ -51,44 +52,44 @@ class ManifestValidator(base.YamlValidator): format_ = str(value).split('/', 1) if len(format_) > 1: if format_[0] != 'MuranoPL': - yield error.report.W030('Not supported format "{0}"' - .format(value), value) + yield error.report.W030(_('Not supported format "{}"' + '').format(value), value) return ver = format_[-1] if str(ver) not in ['1.0', '1.1', '1.2', '1.3', '1.4']: - yield error.report.W030('Not supported format version "{0}"' - .format(value), value) + yield error.report.W030(_('Not supported format version "{}"' + '').format(value), value) def _valid_fullname(self, fullname): if not self._check_fqn_name(fullname): - yield error.report.E073('Invalid FullName "{0}"' + yield error.report.E073(_('Invalid FullName "{}"') .format(fullname), fullname) def _valid_tags(self, value): if not isinstance(value, list): - yield error.report.E070('Tags should be a list', value) + yield error.report.E070(_('Tags should be a list'), value) def _valid_require(self, value): if not isinstance(value, dict): - yield error.report.E005('Require is not a dict type', value) + yield error.report.E005(_('Require is not a dict type'), value) return for fqn, ver in six.iteritems(value): if not self._check_fqn_name(fqn): - yield error.report.E005('Require key is not valid FQN "{0}"' - .format(fqn), fqn) + yield error.report.E005(_('Require key is not valid FQN "{}"' + '').format(fqn), fqn) def _valid_type(self, value): if value not in ('Application', 'Library'): - yield error.report.E071('Type is invalid "{0}"'.format(value), + yield error.report.E071(_('Type is invalid "{}"').format(value), value) def _valid_version(self, version): try: semantic_version.Version.coerce(str(version)) except ValueError: - yield error.report.E071('Version format should be compatible with ' - 'SemVer not "{0}"'.format(version), - version) + yield error.report.E071(_('Version format should be compatible ' + 'with SemVer not "{}"' + '').format(version), version) def _valid_logo_ui_existance(self, ast): if 'Logo' not in ast: @@ -99,33 +100,33 @@ class ManifestValidator(base.YamlValidator): def _valid_ui(self, value): if isinstance(value, six.string_types): if not self._loaded_pkg.exists(os.path.join('UI', value)): - yield error.report.W073('There is no UI file "{0}"' - .format(value), value) + yield error.report.W073(_('There is no UI file "{}"' + '').format(value), value) else: - yield error.report.E072('UI is not a string', value) + yield error.report.E072(_('UI is not a string'), value) def _valid_logo(self, value): if isinstance(value, six.string_types): if not self._loaded_pkg.exists(value): - yield error.report.W074('There is no Logo file "{0}"' - .format(value), value) + yield error.report.W074(_('There is no Logo file "{}"' + '').format(value), value) else: - yield error.report.E074('Logo is not a string', value) + yield error.report.E074(_('Logo is not a string'), value) def _valid_classes(self, value): if not isinstance(value, dict): - yield error.report.E074('Classes section should be a dict', value) + yield error.report.E074(_('Classes section should be a dict'), + value) return files = set(value.values()) existing_files = set(self._loaded_pkg.search_for('.*', 'Classes')) for fname in files - existing_files: - yield error.report.E050('File is present in Manifest {fname}, ' - 'but not in filesystem' - .format(fname=fname), - fname) + yield error.report.E050(_('File "{}" is present in Manifest, ' + 'but not in filesystem' + '').format(fname), fname) for fname in existing_files - files: - yield error.report.W020('File is not present in Manifest, but ' - 'it is in filesystem: {fname}' - .format(fname=fname), fname) + yield error.report.W020(_('File "{}" is not present in Manifest, ' + 'but it is in filesystem' + '').format(fname), fname) diff --git a/muranopkgcheck/validators/muranopl.py b/muranopkgcheck/validators/muranopl.py index bc07e25..024cb45 100644 --- a/muranopkgcheck/validators/muranopl.py +++ b/muranopkgcheck/validators/muranopl.py @@ -19,6 +19,7 @@ import six from muranopkgcheck.checkers import code_structure from muranopkgcheck.checkers import yaql_checker from muranopkgcheck import error +from muranopkgcheck.i18n import _ from muranopkgcheck.validators import base @@ -53,16 +54,16 @@ class MuranoPLValidator(base.YamlValidator): for imp in import_: yield self._valid_import(imp, False) elif not self._check_ns_fqn_name(import_): - yield error.report.E025('Wrong namespace of import "{0}"' - .format(import_), import_) + yield error.report.E025(_('Wrong namespace or FNQ of extended ' + 'class "{0}"').format(import_), import_) def _valid_name(self, value): if value.startswith('__') or \ not CLASSNAME_REGEX.match(value): - yield error.report.E011('Invalid class name "{0}"'.format(value), + yield error.report.E011(_('Invalid class name "{}"').format(value), value) elif not (value != value.lower() and value != value.upper()): - yield error.report.W011('Invalid class name "{0}"'.format(value), + yield error.report.W011(_('Invalid class name "{}"').format(value), value) def _valid_extends(self, value, can_be_list=True): @@ -71,8 +72,8 @@ class MuranoPLValidator(base.YamlValidator): yield self._valid_extends(cls, False) elif isinstance(value, six.string_types): if not self._check_ns_fqn_name(value): - yield error.report.E025('Wrong FNQ of extended class "{0}"' - .format(value), value) + yield error.report.E025(_('Wrong FNQ of extended class "{}"' + '').format(value), value) else: yield error.report.E025("Wrong type of Extends field", value) @@ -99,54 +100,55 @@ class MuranoPLValidator(base.YamlValidator): elif isinstance(contract, six.string_types): if not self.yaql_checker(contract) or \ not contract.startswith('$.') and contract != '$': - yield error.report.W048('Contract is not valid yaql "{0}"' - .format(contract), contract) + yield error.report.W048(_('Contract is not valid yaql "{}"' + '').format(contract), contract) else: - yield error.report.W048('Contract is not valid yaql "{0}"' - .format(contract), contract) + yield error.report.W048(_('Contract is not valid yaql "{}"' + '').format(contract), contract) def _valid_properties(self, properties): if not isinstance(properties, dict): - yield error.report.E026('Properties should be a dict', properties) + yield error.report.E026(_('Properties should be a dict'), + properties) return for property_name, property_data in six.iteritems(properties): usage = property_data.get('Usage') if usage: if usage not in PROPERTIES_USAGE_VALUES: - yield error.report.E042('Not allowed usage ' - '"{0}"'.format(usage), - usage) + yield error.report.E042(_('Not allowed usage "{}"' + '').format(usage), usage) contract = property_data.get('Contract') if contract is not None: yield self._valid_contract(contract) else: - yield error.report.E047('Missing Contract in property "{0}"' + yield error.report.E047(_('Missing Contract in property "{}"') .format(property_name), property_name) yield self._valid_keywords(property_data.keys(), PROPERTIES_KEYWORDS) def _valid_namespaces(self, value): if not isinstance(value, dict): - yield error.report.E044('Wrong type of namespace', value) + yield error.report.E044(_('Wrong type of namespace'), value) return for name, fqn in six.iteritems(value): if not self._check_fqn_name(fqn): - yield error.report.E060('Wrong namespace fqn "{0}"' - .format(fqn), fqn) + yield error.report.E060(_('Wrong namespace fqn ' + '"{}"').format(fqn), fqn) if not self._check_name(name) and name != '=': - yield error.report.E060('Wrong name for namespace ' - '"{0}"'.format(fqn), fqn) + yield error.report.E060(_('Wrong name for namespace ' + '"{}"').format(fqn), fqn) def _valid_methods(self, value): for method_name, method_data in six.iteritems(value): if not isinstance(method_data, dict): if method_data: - yield error.report.E046('Method is not a dict', + yield error.report.E046(_('Method is not a dict'), method_name) return + if not METHOD_NAME_REGEX.match(method_name): - yield error.report.E054('Invalid name of method "{0}"' + yield error.report.E054(_('Invalid name of method "{}"') .format(method_name), method_name) scope = method_data.get('Scope') if scope: @@ -164,46 +166,46 @@ class MuranoPLValidator(base.YamlValidator): def _valid_body(self, body): if not isinstance(body, (list, six.string_types, dict)): - yield error.report.E045('Body is not a list or scalar/yaql ' - 'expression', body) + yield error.report.E045(_('Body is not a list or scalar/yaql ' + 'expression'), body) else: yield self.code_structure.codeblock(body) def _valid_scope(self, scope): if self._loaded_pkg.format_version >= '1.4': if scope is not None and scope not in ('Public', 'Session'): - yield error.report.E044('Wrong Scope "{0}"'.format(scope), + yield error.report.E044(_('Wrong Scope "{}"').format(scope), scope) else: - yield error.report.E044('Scope is not supported version ' - 'earlier than 1.3"', scope) + yield error.report.E044(_('Scope is not supported version ' + 'earlier than 1.3"'), scope) def _valid_method_usage(self, usage): if usage == 'Action': if self._loaded_pkg.format_version >= '1.4': - yield error.report.W045('Usage "{0}" is deprecated since 1.4' - .format(usage), usage) + yield error.report.W045(_('Usage "{}" is deprecated since 1.4' + '').format(usage), usage) elif usage in frozenset(['Static', 'Extension']): if self._loaded_pkg.format_version <= '1.3': - yield error.report.W045('Usage "{0}" is available from 1.3' + yield error.report.W045(_('Usage "{}" is available from 1.3') .format(usage), usage) elif usage != 'Runtime': - yield error.report.W045('Unsupported usage type "{0}" ' + yield error.report.W045(_('Unsupported usage type "{}" ') .format(usage), usage) def _valid_arguments(self, arguments): if not isinstance(arguments, list): - yield error.report.E046('Methods arguments should be a list', + yield error.report.E046(_('Methods arguments should be a list'), arguments) return for argument in arguments: if not isinstance(argument, dict) or len(argument) != 1: - yield error.report.E046('Methods single argument should be a ' - 'one key dict', argument) + yield error.report.E046(_('Methods single argument should be ' + 'a one key dict'), argument) else: name = next(six.iterkeys(argument)) if not self._check_name(name): - yield error.report.E054('Invalid name of argument "{0}"' + yield error.report.E054(_('Invalid name of argument "{}"') .format(name), name) val = next(six.itervalues(argument)) contract = val.get('Contract') @@ -216,8 +218,8 @@ class MuranoPLValidator(base.YamlValidator): def _valid_argument_usage(self, usage): if self._loaded_pkg.format_version < '1.4': - yield error.report.E052('Arguments usage is available since 1.4 ', - usage) + yield error.report.E052(_('Arguments usage is available ' + 'since 1.4'), usage) if usage not in frozenset(['Standard', 'VarArgs', 'KwArgs']): - yield error.report.E053('Usage is invalid value "{0}"' + yield error.report.E053(_('Usage is invalid value "{}"') .format(usage), usage) diff --git a/muranopkgcheck/validators/package.py b/muranopkgcheck/validators/package.py index 17bfa91..73ab41d 100644 --- a/muranopkgcheck/validators/package.py +++ b/muranopkgcheck/validators/package.py @@ -14,6 +14,7 @@ from muranopkgcheck import error +from muranopkgcheck.i18n import _ from muranopkgcheck.validators import base KNOWN_FILES_DIR = frozenset(['manifest.yaml', 'UI', 'LICENSE', 'Classes']) @@ -35,8 +36,8 @@ class PackageValidator(base.BaseValidator): except Exception: logo_file = 'logo.png' for file_ in files - KNOWN_FILES_DIR - {logo_file}: - yield error.report.W120('Unknown "{0}" in package directory' + yield error.report.W120(_('Unknown "{}" in the package') .format(file_), file_) for file_ in REQUIRED_FILES_DIR - files: - yield error.report.W121('Missing "{0}" in package directory' + yield error.report.W121(_('Missing "{}" in the package') .format(file_), file_) diff --git a/requirements.txt b/requirements.txt index 752bbd9..8ce956a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ yaql>=1.1.0 # Apache 2.0 License six>=1.9.0 # MIT stevedore>=1.16.0 # Apache-2.0 semantic_version>=2.3.1 # BSD +oslo.i18n>=2.1.0 # Apache-2.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 8929d60..a97433d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,20 @@ all_files = 1 [upload_sphinx] upload-dir = doc/build/html +[compile_catalog] +directory = muranopkgcheck/locale +domain = murano-pkg-check + +[update_catalog] +domain = murano-pkg-check +output_dir = muranopkgcheck/locale +input_file = muranopkgcheck/locale/murano-pkg-check.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = muranopkgcheck/locale/murano-pkg-check.pot + [entry_points] console_scripts = murano-pkg-check = muranopkgcheck.cmd.run:main