Add i18n support

Change-Id: I46f82ae4c9803c866dfb594064df15e2b1a5c2ad
This commit is contained in:
sslypushenko 2016-09-05 15:26:10 +03:00
parent 2f8fc0d65f
commit e95f43bc6e
13 changed files with 151 additions and 90 deletions

2
babel.cfg Normal file
View File

@ -0,0 +1,2 @@
[python: **.py]

33
muranopkgcheck/i18n.py Normal file
View File

@ -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

View File

@ -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):

View File

@ -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))

View File

@ -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'}]

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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_)

View File

@ -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

View File

@ -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