diff --git a/locale/fr/LC_MESSAGES/rougail.po b/locale/fr/LC_MESSAGES/rougail.po index 318156a5b..529c54e97 100644 --- a/locale/fr/LC_MESSAGES/rougail.po +++ b/locale/fr/LC_MESSAGES/rougail.po @@ -45,7 +45,7 @@ msgid "" "the variable \"{0}\" is mandatory so in \"{1}\" mode but family has the " "higher family mode \"{2}\"" msgstr "" -"la variable \"{0}\" est obligatoire donc dans le mode \"{1}\" mais la " +"la variable \"{0}\" est obligatoire, donc en mode \"{1}\", mais la " "famille a un mode supérieur \"{2}\"" #: src/rougail/annotator/family.py:287 diff --git a/src/rougail/annotator/family.py b/src/rougail/annotator/family.py index df2813438..e2cd224f1 100644 --- a/src/rougail/annotator/family.py +++ b/src/rougail/annotator/family.py @@ -136,6 +136,7 @@ class Annotator(Walk): ) if family.version == "1.0" and "{{ suffix }}" in path: path = path.replace("{{ suffix }}", "{{ identifier }}") + self.objectspace.dynamics_variable.setdefault(path, []).append(family.path) self.objectspace.informations.add(family.path, "dynamic_variable", path) def change_modes(self): diff --git a/src/rougail/annotator/variable.py b/src/rougail/annotator/variable.py index 1349aeb7a..08e7d3265 100644 --- a/src/rougail/annotator/variable.py +++ b/src/rougail/annotator/variable.py @@ -130,14 +130,7 @@ class Annotator(Walk): # pylint: disable=R0903 ): return # copy type and params - calculated_variable_path = variable.default.variable - calculated_variable, identifier = self.objectspace.paths.get_with_dynamic( - calculated_variable_path, - variable.path, - variable.version, - variable.namespace, - variable.xmlfiles, - ) + calculated_variable, identifier = variable.default.get_variable(self.objectspace) if calculated_variable is None: return variable.type = calculated_variable.type diff --git a/src/rougail/convert.py b/src/rougail/convert.py index 13dc2f55b..3cc27b871 100644 --- a/src/rougail/convert.py +++ b/src/rougail/convert.py @@ -135,7 +135,7 @@ class ParserVariable: self.reflector_names = {} self.leaders = [] self.followers = [] - self.dynamics = [] + self.dynamics_variable = {} self.multis = {} self.default_multi = {} self.jinja = {} @@ -389,13 +389,8 @@ class ParserVariable: # it's just for modify subfamily or subvariable, do not redefine if family_obj: if exists in [None, True] and not obj.pop("redefine", False): - raise DictConsistencyError( - _( - 'The family "{0}" already exists and it is not redefined' - ).format(path), - 32, - [filename], - ) + msg = _('family "{0}" define multiple time').format(path) + raise DictConsistencyError(msg, 32, self.paths[path].xmlfiles + [filename]) # convert to Calculation objects self.parse_parameters( path, @@ -644,14 +639,14 @@ class ParserVariable: family_is_leadership is True and first_variable is False, version, ) - self.parse_params(path, obj) + self.parse_params(path, obj, filename) exists = obj.pop("exists", None) if path in self.paths: if not self.load_unexist_redefine and exists is False: return if not obj.pop("redefine", False): - msg = f'Variable "{path}" already exists' - raise DictConsistencyError(msg, 45, [filename]) + msg = _('variable "{0}" define multiple time').format(path) + raise DictConsistencyError(msg, 45, self.paths[path].xmlfiles + [filename]) self.paths.add( path, self.paths[path].model_copy(update=obj), @@ -748,12 +743,15 @@ class ParserVariable: f"at index {idx}: {err}" ) from err - def parse_params(self, path, obj): + def parse_params(self, path, obj, filename): """Parse variable params""" if "params" not in obj: return if not isinstance(obj["params"], dict): - raise Exception(f"params must be a dict for {path}") + raise DictConsistencyError(_("params must be a dict for {0}").format(path), + 55, + [filename], + ) params = [] for key, val in obj["params"].items(): try: @@ -766,12 +764,14 @@ class ParserVariable: is_follower=None, attribute=None, family_is_dynamic=None, - xmlfiles=None, + xmlfiles=[filename], ) ) except ValidationError as err: - raise Exception( - f'"{key}" has an invalid "params" for {path}: {err}' + raise DictConsistencyError( + _('"{0}" has an invalid "params" for {1}: {2}').format(key, path, err), + 54, + [filename], ) from err obj["params"] = params @@ -928,11 +928,17 @@ class ParserVariable: val["is_follower"] = is_follower val["attribute"] = attribute val["xmlfiles"] = xmlfiles + if param_typ not in PARAM_TYPES: + raise DictConsistencyError( + f'unknown type "{param_typ}" for "{path}"', + 52, + xmlfiles, + ) try: params.append(PARAM_TYPES[param_typ](**val)) except ValidationError as err: raise DictConsistencyError( - f'"{attribute}" has an invalid "{key}" for {path}: {err}', + f'"{attribute}" has an invalid "{key}" for "{path}": {err}', 29, xmlfiles, ) from err diff --git a/src/rougail/structural_directory/__init__.py b/src/rougail/structural_directory/__init__.py index 5939dae32..5d0b37a80 100644 --- a/src/rougail/structural_directory/__init__.py +++ b/src/rougail/structural_directory/__init__.py @@ -24,6 +24,8 @@ from ruamel.yaml import YAML from ..utils import normalize_family from ..path import Paths +from ..error import DictConsistencyError +from ..i18n import _ class Walker: @@ -124,7 +126,7 @@ class Walker: raise DictConsistencyError( _("duplicate dictionary file name {0}").format(file_path.name), 78, - [filenames[file_path.name][1]], + [filenames[file_path.name], str(file_path)], ) filenames[file_path.name] = str(file_path) diff --git a/src/rougail/structural_directory/config.py b/src/rougail/structural_directory/config.py index 31839357a..3f56be244 100644 --- a/src/rougail/structural_directory/config.py +++ b/src/rougail/structural_directory/config.py @@ -27,7 +27,8 @@ def get_rougail_config( main_namespace_default = "rougail" else: main_namespace_default = "null" - options = f"""main_dictionaries: + options = f""" +main_dictionaries: description: {_("Directories where dictionary files are placed")} type: unix_filename alternative_name: m diff --git a/src/rougail/tiramisu.py b/src/rougail/tiramisu.py index 9105b9643..710740ec1 100644 --- a/src/rougail/tiramisu.py +++ b/src/rougail/tiramisu.py @@ -165,7 +165,7 @@ def jinja_to_function( return values -def variable_to_property(prop, value, when, inverse): +def variable_to_property(prop, value, when, inverse, **kwargs): if isinstance(value, PropertiesOptionError): raise value from value if inverse: diff --git a/src/rougail/tiramisureflector.py b/src/rougail/tiramisureflector.py index 6fa6b8803..1bf5daf01 100644 --- a/src/rougail/tiramisureflector.py +++ b/src/rougail/tiramisureflector.py @@ -402,7 +402,7 @@ class Common: kwargs = [] if "params" in child: for key, value in child["params"].items(): - if not key: + if key is None: for val in value: new_args.append(self.populate_param(val)) else: diff --git a/src/rougail/user_datas.py b/src/rougail/user_datas.py index a72916f67..b80ccdecd 100644 --- a/src/rougail/user_datas.py +++ b/src/rougail/user_datas.py @@ -23,8 +23,9 @@ from typing import List from re import findall from tiramisu import undefined, Calculation -from tiramisu.error import PropertiesOptionError, LeadershipError, ConfigError +from tiramisu.error import PropertiesOptionError, LeadershipError, ConfigError, CancelParam +from .i18n import _ from .object_model import CONVERT_OPTION @@ -48,8 +49,10 @@ class UserDatas: def _populate_values(self, user_datas): for datas in user_datas: options = datas.get("options", {}) + source = datas["source"] for name, data in datas.get("values", {}).items(): self.values[name] = { + "source": source, "values": data, "options": options.copy(), } @@ -148,6 +151,9 @@ class UserDatas: if path not in self.values: continue options = self.values[path].get("options", {}) + if options.get('allow_secrets_variables', False) is True and option.type() == 'password': + self.errors.append(_('the variable "{0}" contains secrets and should not be defined in {1}').format(path, self.values[path]["source"])) + continue value = self.values[path]["values"] needs_convert = options.get("needs_convert", False) @@ -206,13 +212,13 @@ class UserDatas: option = self.config.option(path) if option.isoptiondescription(): self.errors.append( - f'the option "{option.path()}" is an option description' + _('the option "{0}" is an option description').format(option.path()) ) continue value = data["values"] if option.isfollower(): for index, val in enumerate(value): - if val is undefined: + if val is undefined or isinstance(val, CancelParam): continue self.config.option(path, index).value.set(val) else: diff --git a/src/rougail/utils.py b/src/rougail/utils.py index a77f44875..5eb64ef29 100644 --- a/src/rougail/utils.py +++ b/src/rougail/utils.py @@ -114,6 +114,8 @@ def get_jinja_variable_to_param( jinja_text, current_path, err ) raise DictConsistencyError(msg, 39, xmlfiles) from err + except AttributeError: + pass variables = list(variables) variables.sort(reverse=True) founded_variables = {} @@ -157,5 +159,7 @@ def get_jinja_variable_to_param( break if root_path: yield {}, None, root_path + else: + yield {}, None, vpath for variable_path, data in founded_variables.items(): yield data[1], data[0], variable_path diff --git a/tests/test_2_makedict.py b/tests/test_2_makedict.py index 5c110cab3..3a13dd9b6 100644 --- a/tests/test_2_makedict.py +++ b/tests/test_2_makedict.py @@ -119,7 +119,7 @@ def launch_flattener(test_dir, mkdir(makedict_dir) config_dict = dict(option_value(config.value.get())) if not isfile(Path(test_dir) / 'tiramisu' / 'base.py'): - tconfig_dict = {f'rougail.{path}': value for path, value in config_dict.items()} + tconfig_dict = config_add_rougail(config_dict) else: tconfig_dict = config_dict if not isfile(makedict_file) or debug: @@ -127,7 +127,7 @@ def launch_flattener(test_dir, dump(tconfig_dict, fh, indent=4) fh.write('\n') if filename == 'no_namespace': - config_dict = {f'rougail.{path}': value for path, value in config_dict.items()} + config_dict = config_add_rougail(config_dict) elif filename != 'base': config_dict_prefix = {'1': {}, '2': {}} for key, value in config_dict.items(): @@ -165,6 +165,18 @@ def launch_flattener(test_dir, mandatory(test_dir, mandatory_file, config.value.mandatory(), filename) +def config_add_rougail(config): + config_dict = {} + for path, value in config.items(): + if value and isinstance(value, list) and isinstance(value[0], dict): + config_dict[f'rougail.{path}'] = [] + for v in value: + config_dict[f'rougail.{path}'].append({f'rougail.{k}': w for k, w in v.items()}) + else: + config_dict[f'rougail.{path}'] = value + return config_dict + + def value_owner(test_dir, makedict_value_owner, config, filename): ret = {} for key, value in option_value(config.value.get(), True): @@ -189,7 +201,7 @@ def value_owner(test_dir, makedict_value_owner, config, filename): 'value': value, } if not isfile(Path(test_dir) / 'tiramisu' / 'base.py'): - tret = {f'rougail.{path}': value for path, value in ret.items()} + tret = config_add_rougail(ret) else: tret = ret if not isfile(makedict_value_owner) or debug: @@ -197,7 +209,7 @@ def value_owner(test_dir, makedict_value_owner, config, filename): dump(tret, fh, indent=4) fh.write('\n') if filename == 'no_namespace': - ret = {f'rougail.{path}': value for path, value in ret.items()} + ret = config_add_rougail(ret) elif filename != 'base': ret_prefix = {'1': {}, '2': {}} for key, value in ret.items():