From 999b53889ca3605d8cb0364cf9fc64feb8b9f04f Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 19 Mar 2025 11:43:04 +0100 Subject: [PATCH] feat: add rougail secret_manager --- src/rougail/__init__.py | 2 +- src/rougail/annotator/family.py | 6 +- src/rougail/annotator/variable.py | 20 ++++- src/rougail/config.py | 50 ++++++++--- src/rougail/convert.py | 90 ++++++++++++++----- src/rougail/object_model.py | 66 ++++++++++---- src/rougail/tiramisu.py | 27 +++++- src/rougail/tiramisureflector.py | 5 ++ src/rougail/user_datas.py | 22 ++++- .../makedict/after.json | 6 ++ .../makedict/base.json | 3 + .../makedict/before.json | 6 ++ .../makedict/mandatory.json | 1 + .../tiramisu/base.py | 15 ++++ .../makedict/after.json | 6 ++ .../makedict/base.json | 3 + .../makedict/before.json | 6 ++ .../makedict/mandatory.json | 1 + .../tiramisu/base.py | 16 ++++ .../makedict/after.json | 6 ++ .../makedict/base.json | 3 + .../makedict/before.json | 6 ++ .../makedict/mandatory.json | 1 + .../tiramisu/base.py | 16 ++++ .../tiramisu/no_namespace.py | 11 +++ .../makedict/after.json | 13 +++ .../makedict/base.json | 7 ++ .../makedict/before.json | 13 +++ .../makedict/mandatory.json | 1 + .../tiramisu/base.py | 16 ++++ .../tiramisu/no_namespace.py | 11 +++ .../makedict/after.json | 25 ++++++ .../makedict/base.json | 10 +++ .../makedict/before.json | 25 ++++++ .../makedict/mandatory.json | 1 + .../tiramisu/base.py | 19 ++++ .../tiramisu/no_namespace.py | 14 +++ .../makedict/after.json | 10 +++ .../makedict/base.json | 4 + .../makedict/before.json | 10 +++ .../makedict/mandatory.json | 1 + .../tiramisu/base.py | 18 ++++ .../makedict/after.json | 10 +++ .../makedict/base.json | 4 + .../makedict/before.json | 10 +++ .../makedict/mandatory.json | 1 + .../tiramisu/base.py | 18 ++++ .../makedict/after.json | 24 +++++ .../makedict/base.json | 12 +++ .../makedict/before.json | 24 +++++ .../makedict/mandatory.json | 1 + .../tiramisu/base.py | 18 ++++ tests/test_1_flattener.py | 2 +- 53 files changed, 652 insertions(+), 64 deletions(-) create mode 100644 tests/dictionaries/00_8calculation_namespace/makedict/after.json create mode 100644 tests/dictionaries/00_8calculation_namespace/makedict/base.json create mode 100644 tests/dictionaries/00_8calculation_namespace/makedict/before.json create mode 100644 tests/dictionaries/00_8calculation_namespace/makedict/mandatory.json create mode 100644 tests/dictionaries/00_8calculation_namespace/tiramisu/base.py create mode 100644 tests/dictionaries/00_8calculation_param_namespace/makedict/after.json create mode 100644 tests/dictionaries/00_8calculation_param_namespace/makedict/base.json create mode 100644 tests/dictionaries/00_8calculation_param_namespace/makedict/before.json create mode 100644 tests/dictionaries/00_8calculation_param_namespace/makedict/mandatory.json create mode 100644 tests/dictionaries/00_8calculation_param_namespace/tiramisu/base.py create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/after.json create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/base.json create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/before.json create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/mandatory.json create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi2/tiramisu/base.py create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi2/tiramisu/no_namespace.py create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/after.json create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/base.json create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/before.json create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/mandatory.json create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi3/tiramisu/base.py create mode 100644 tests/dictionaries/04_5disabled_calculation_variable_multi3/tiramisu/no_namespace.py create mode 100644 tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/after.json create mode 100644 tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/base.json create mode 100644 tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/before.json create mode 100644 tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/mandatory.json create mode 100644 tests/dictionaries/60_0family_dynamic_forbidden_char/tiramisu/base.py create mode 100644 tests/dictionaries/60_0family_dynamic_forbidden_char/tiramisu/no_namespace.py create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/after.json create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/base.json create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/before.json create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/mandatory.json create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/tiramisu/base.py create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/after.json create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/base.json create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/before.json create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/mandatory.json create mode 100644 tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/tiramisu/base.py create mode 100644 tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/after.json create mode 100644 tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/base.json create mode 100644 tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/before.json create mode 100644 tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/mandatory.json create mode 100644 tests/dictionaries/60_5family_dynamic_variable_outside_1_0/tiramisu/base.py diff --git a/src/rougail/__init__.py b/src/rougail/__init__.py index 79a63f3de..99d7b10fc 100644 --- a/src/rougail/__init__.py +++ b/src/rougail/__init__.py @@ -43,7 +43,7 @@ def tiramisu_display_name( """Replace the Tiramisu display_name function to display path + description""" doc = kls._get_information(subconfig, "doc", None) comment = f" ({doc})" if doc and doc != kls.impl_getname() else "" - if "{{ identifier }}" in comment: + if "{{ identifier }}" in comment and subconfig.identifiers: comment = comment.replace("{{ identifier }}", str(subconfig.identifiers[-1])) path = kls.impl_getpath() if "{{ identifier }}" in path and subconfig.identifiers: diff --git a/src/rougail/annotator/family.py b/src/rougail/annotator/family.py index b2db5ae7c..92f9f3d11 100644 --- a/src/rougail/annotator/family.py +++ b/src/rougail/annotator/family.py @@ -136,7 +136,9 @@ 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.dynamics_variable.setdefault(path, []).append( + family.path + ) self.objectspace.informations.add(family.path, "dynamic_variable", path) def change_modes(self): @@ -341,7 +343,7 @@ class Annotator(Walk): if self._has_mode(variable): msg = _( 'the variable "{0}" is in "{1}" mode but family has the higher family mode "{2}"' - ).format(variable.name, variable_mode, family_mode) + ).format(variable.path, variable_mode, family_mode) raise DictConsistencyError(msg, 61, variable.xmlfiles) self._set_auto_mode(variable, family_mode) if not variable.mode: diff --git a/src/rougail/annotator/variable.py b/src/rougail/annotator/variable.py index cf99752ac..0ee09c536 100644 --- a/src/rougail/annotator/variable.py +++ b/src/rougail/annotator/variable.py @@ -69,12 +69,28 @@ class Annotator(Walk): # pylint: disable=R0903 bool: "boolean", float: "float", } + self.verify_secret_managers() self.verify_choices() self.convert_variable() self.convert_test() self.convert_examples() self.convert_help() + def verify_secret_managers(self): + for variable in self.get_variables(): + if not variable.secret_manager: + continue + path = variable.path + if variable.type not in ["unix_user", "secret"]: + msg = _('only "unix_user" or "secret" variable type can have "secret_manager" attribute, but "{0}" has type "{1}"') + raise DictConsistencyError(msg.format(path, variable.type), 56, variable.xmlfiles) + if variable.multi and path not in self.objectspace.leaders: + msg = _('the variable "{0}" has attribute "secret_manager" but is a multi variable') + raise DictConsistencyError(msg.format(path), 57, variable.xmlfiles) + if variable.default is not None: + msg = _('the variable "{0}" has attribute "secret_manager" so must not have default value') + raise DictConsistencyError(msg.format(path), 59, variable.xmlfiles) + def convert_variable(self): """convert variable""" for variable in self.get_variables(): @@ -130,7 +146,9 @@ class Annotator(Walk): # pylint: disable=R0903 ): return # copy type and params - calculated_variable, identifier = variable.default.get_variable(self.objectspace) + 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/config.py b/src/rougail/config.py index 12205d492..8060cdf4e 100644 --- a/src/rougail/config.py +++ b/src/rougail/config.py @@ -73,8 +73,7 @@ def get_level(module): class _RougailConfig: - def __init__(self, backward_compatibility: bool, - add_extra_options: bool): + def __init__(self, backward_compatibility: bool, add_extra_options: bool): self.backward_compatibility = backward_compatibility self.add_extra_options = add_extra_options self.root = None @@ -86,7 +85,9 @@ class _RougailConfig: ) if self.root: rougailconfig.config.value.importation(self.config.value.exportation()) - rougailconfig.config.property.importation(self.config.property.exportation()) + rougailconfig.config.property.importation( + self.config.property.exportation() + ) rougailconfig.config.property.read_only() rougailconfig.root = self.root rougailconfig.config = self.config @@ -99,7 +100,9 @@ class _RougailConfig: return rougailconfig def generate_config(self): - root, extra_vars = _rougail_config(self.backward_compatibility, self.add_extra_options) + root, extra_vars = _rougail_config( + self.backward_compatibility, self.add_extra_options + ) self.root = root self.config = Config( self.root, @@ -343,6 +346,13 @@ load_unexist_redefine: commandline: false default: False +secret_manager: + + pattern: + description: {_("The secret pattern to build item name in Bitwarden")} + help: {_("The pattern is in Jinja format")} + default: "{{{{ project }}}} - {{{{ environment }}}} - {{{{ service }}}} - {{{{ user }}}}" + """ processes = { "structural": [], @@ -367,14 +377,22 @@ load_unexist_redefine: {NAME}: description: Select for {NAME} - choices: """.format( NAME=normalize_family(process), ) + if process != "structural": + rougail_process += """ + alternative_name: {NAME[0]} +""".format( + NAME=normalize_family(process), + ) + rougail_process += """ + choices: +""" for obj in objects: rougail_process += f" - {obj['name']}\n" if process == "structural": -# rougail_process += """ commandline: false + # rougail_process += """ commandline: false rougail_process += """ multi: true default: - directory @@ -391,11 +409,11 @@ load_unexist_redefine: jinja: | """ for hidden_output in hidden_outputs: - rougail_process += """ {% if _.output == 'NAME' %} + rougail_process += """ {% if _.output is not propertyerror and _.output == 'NAME' %} Cannot load structural for NAME output - {% endif %}""".replace( - "NAME", hidden_output - ) + {% endif %} +""".replace("NAME", hidden_output + ) elif process == "user data": rougail_process += """ multi: true mandatory: false""" @@ -411,7 +429,7 @@ load_unexist_redefine: jinja: | """ for hidden_output in hidden_outputs: - rougail_process += """ {% if _.output == 'NAME' %} + rougail_process += """ {% if _.output is not propertyerror and _.output == 'NAME' %} Cannot load user data for NAME output {% endif %}""".replace( "NAME", hidden_output @@ -455,10 +473,7 @@ default_params: mandatory: false default: {value} """ - for process_empty in processes_empty: - rougail_process += process_empty rougail_options += rougail_process - # print(rougail_options) convert = FakeRougailConvert(add_extra_options) convert.init() convert.namespace = None @@ -468,6 +483,13 @@ default_params: "1.1", YAML().load(rougail_options), ) + for process_empty in processes_empty: + convert.parse_root_file( + "rougail.config", + "", + "1.1", + YAML().load(process_empty), + ) extra_vars = {} objects = [] for obj in sorted( diff --git a/src/rougail/convert.py b/src/rougail/convert.py index cb9137c11..f4dc76a31 100644 --- a/src/rougail/convert.py +++ b/src/rougail/convert.py @@ -179,12 +179,16 @@ class ParserVariable: ] self.structurals = rougailconfig["step.structural"] self.user_datas = rougailconfig["step.user_data"] - self.output = rougailconfig["step.output"] + try: + self.output = rougailconfig["step.output"] + except: + self.output = None self.tiramisu_cache = rougailconfig["tiramisu_cache"] self.load_unexist_redefine = rougailconfig["load_unexist_redefine"] + self.secret_pattern = rougailconfig['secret_manager.pattern'] # change default initkwargs in CONVERT_OPTION - if hasattr(rougailconfig, 'config'): - for sub_od in rougailconfig.config.option('default_params'): + if hasattr(rougailconfig, "config"): + for sub_od in rougailconfig.config.option("default_params"): for option in sub_od: if option.owner.isdefault(): continue @@ -216,9 +220,7 @@ class ParserVariable: family = type( family.__name__ + "_" + structural, (family, module.Family), {} ) - if "Walker" in module.__all__: - if self.walker: - raise Exception("multiple walker defined!") + if not self.walker and "Walker" in module.__all__: self.walker = module.Walker self.variable = variable self.family = family @@ -393,7 +395,7 @@ class ParserVariable: exists = obj.pop("exists", None) else: exists = None - force_to_attrs = list(self.list_attributes(obj)) + force_to_attrs = list(self.list_attributes(obj, filename)) for key, value in obj.items(): if key in force_to_attrs: if key.startswith("_"): @@ -406,7 +408,9 @@ class ParserVariable: if family_obj: if exists in [None, True] and not obj.pop("redefine", False): msg = _('family "{0}" define multiple time').format(path) - raise DictConsistencyError(msg, 32, self.paths[path].xmlfiles + [filename]) + raise DictConsistencyError( + msg, 32, self.paths[path].xmlfiles + [filename] + ) # convert to Calculation objects self.parse_parameters( path, @@ -499,10 +503,17 @@ class ParserVariable: def list_attributes( self, obj: Dict[str, Any], + filename: str, ) -> Iterator[str]: """List attributes""" force_to_variable = [] for key, value in obj.items(): + if not isinstance(key, str): + raise DictConsistencyError( + f'a key is not in string format: {key}', + 103, + [filename], + ) if key in force_to_variable: continue if key.startswith("_"): @@ -656,13 +667,17 @@ class ParserVariable: version, ) self.parse_params(path, obj, filename) + self.parse_secret_manager(path, obj, filename, version, family_is_dynamic) 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 = _('variable "{0}" define multiple time').format(path) - raise DictConsistencyError(msg, 45, self.paths[path].xmlfiles + [filename]) + if not self.load_unexist_redefine: + if exists is False: + return + if not obj.pop("redefine", False): + 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), @@ -764,10 +779,11 @@ class ParserVariable: if "params" not in obj: return if not isinstance(obj["params"], dict): - raise DictConsistencyError(_("params must be a dict for {0}").format(path), - 55, - [filename], - ) + raise DictConsistencyError( + _("params must be a dict for {0}").format(path), + 55, + [filename], + ) params = [] for key, val in obj["params"].items(): try: @@ -780,17 +796,45 @@ class ParserVariable: is_follower=None, attribute=None, family_is_dynamic=None, + namespace=self.namespace, xmlfiles=[filename], ) ) except ValidationError as err: raise DictConsistencyError( - _('"{0}" has an invalid "params" for {1}: {2}').format(key, path, err), + _('"{0}" has an invalid "params" for {1}: {2}').format( + key, path, err + ), 54, [filename], ) from err obj["params"] = params + def parse_secret_manager(self, path, obj, filename, version, family_is_dynamic): + """Parse variable secret_manager""" + if "secret_manager" not in obj: + return + if not isinstance(obj["secret_manager"], dict): + raise DictConsistencyError( + _("secret_manager must be a dict for {0}").format(path), + 64, + [filename], + ) + secret_manager = {"type": "jinja", + "jinja": self.secret_pattern, + "params": obj["secret_manager"], + } + self.set_calculation( + obj, + "secret_manager", + secret_manager, + path, + family_is_dynamic, + False, + version, + [filename], + ) + def add_variable( self, name: str, @@ -878,6 +922,9 @@ class ParserVariable: calculations = calculations[1] if not isinstance(value, dict) or attribute not in calculations: return False + return self.check_auto_type(value) + + def check_auto_type(self, value): if "type" in value: return value["type"] in CALCULATION_TYPES # auto set type @@ -943,6 +990,7 @@ class ParserVariable: val["family_is_dynamic"] = family_is_dynamic val["is_follower"] = is_follower val["attribute"] = attribute + val["namespace"] = self.namespace val["xmlfiles"] = xmlfiles if param_typ not in PARAM_TYPES: raise DictConsistencyError( @@ -968,7 +1016,7 @@ class ParserVariable: ) # if typ == "identifier" and not family_is_dynamic: - msg = f'identifier calculation for "{attribute}" in "{path}" cannot be set none dynamic family' + msg = f'identifier calculation for "{attribute}" in "{path}" cannot be set variable is not in dynamic family' raise DictConsistencyError(msg, 53, xmlfiles) if attribute in PROPERTY_ATTRIBUTE: calc = CALCULATION_PROPERTY_TYPES[typ](**calculation_object) @@ -1106,7 +1154,9 @@ class RougailConvert(ParserVariable): def parse_directories(self) -> None: if not self.walker: - msg = _('invalid "structural" definition ({0}), we cannot load any structural file!').format(self.structurals) + msg = _( + 'invalid "structural" definition ({0}), we cannot load any structural file!' + ).format(self.structurals) raise DictConsistencyError(msg, 51, None) self.init() self.walker(self) diff --git a/src/rougail/object_model.py b/src/rougail/object_model.py index 25f0c0d8c..a3c7a1f04 100644 --- a/src/rougail/object_model.py +++ b/src/rougail/object_model.py @@ -113,23 +113,23 @@ def get_convert_option_types(): if obj == tiramisu.SymLinkOption: continue if obj == tiramisu.ChoiceOption: - inst = obj('a', 'a', ('a',), **initkwargs) + inst = obj("a", "a", ("a",), **initkwargs) else: - inst = obj('a', 'a', **initkwargs) - extra = getattr(inst, '_extra', {}) + inst = obj("a", "a", **initkwargs) + extra = getattr(inst, "_extra", {}) if not extra: continue params = [] for key, value in extra.items(): - if key.startswith('_'): + if key.startswith("_"): continue multi = False if isinstance(value, bool): - key_type = 'boolean' + key_type = "boolean" elif isinstance(value, str): - key_type = 'string' + key_type = "string" elif isinstance(value, list): - key_type = 'string' + key_type = "string" multi = True params.append((key, key_type, multi, value)) yield typ, params @@ -137,6 +137,7 @@ def get_convert_option_types(): class Param(BaseModel): key: str + namespace: Optional[str] model_config = ConfigDict(extra="forbid") def __init__( @@ -191,19 +192,24 @@ class IndexParam(Param): self, **kwargs, ) -> None: - if not kwargs["is_follower"]: msg = f'the variable "{kwargs["path"]}" is not a follower, so cannot have index type for param in "{kwargs["attribute"]}"' raise DictConsistencyError(msg, 25, kwargs["xmlfiles"]) super().__init__(**kwargs) +class NamespaceParam(Param): + type: str + namespace: str + + PARAM_TYPES = { "any": AnyParam, "variable": VariableParam, "identifier": IdentifierParam, "information": InformationParam, "index": IndexParam, + "namespace": NamespaceParam, } @@ -238,7 +244,7 @@ class Calculation(BaseModel): ) if not variable: if not param.get("optional"): - msg = f'cannot find variable "{param["variable"]}" defined attribute in "{self.attribute_name}" for "{self.path}"' + msg = f'cannot find variable "{param["variable"]}" defined in attribute "{self.attribute_name}" for "{self.path}"' raise DictConsistencyError(msg, 22, self.xmlfiles) continue if not isinstance(variable, objectspace.variable): @@ -288,6 +294,7 @@ class JinjaCalculation(Calculation): "validators", "choices", "dynamic", + "secret_manager", ] jinja: StrictStr params: Optional[List[Param]] = None @@ -366,7 +373,7 @@ class JinjaCalculation(Calculation): self, objectspace, ) -> dict: - if self.attribute_name == "default": + if self.attribute_name in ["default", "secret_manager"]: if self.return_type: raise Exception("return_type not allowed!") variable = objectspace.paths[self.path] @@ -474,7 +481,8 @@ class _VariableCalculation(Calculation): "variable": variable, "propertyerror": self.propertyerror, } - if isinstance(self, VariableCalculation) and self.optional: + is_variable_calculation = isinstance(self, VariableCalculation) + if is_variable_calculation and self.optional: param["optional"] = self.optional if identifier: param["identifier"] = identifier @@ -520,9 +528,10 @@ class _VariableCalculation(Calculation): variable.multi or variable.path.rsplit(".", 1)[0] != self.path.rsplit(".", 1)[0] ): - # it's not a follower or not in same leadership - msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is a multi' - raise DictConsistencyError(msg, 21, self.xmlfiles) + if is_variable_calculation: + # it's not a follower or not in same leadership + msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is a multi' + raise DictConsistencyError(msg, 21, self.xmlfiles) else: params[None][0]["index"] = {"index": {"type": "index"}} if self.path in objectspace.followers: @@ -598,9 +607,12 @@ class VariablePropertyCalculation(_VariableCalculation): when = self.when_not inverse = True else: - if variable.type != "boolean": - raise Exception("only boolean!") - when = True + if variable.multi: + when = [] + else: + if variable.type != "boolean": + raise Exception("only boolean!") + when = True inverse = False params["when"] = when params["inverse"] = inverse @@ -737,13 +749,31 @@ class IndexCalculation(Calculation): } +class NamespaceCalculation(Calculation): + attribute_name: Literal["default", "secret_manager"] + + def to_function( + self, + objectspace, + ) -> dict: + namespace = objectspace.namespace + if namespace: + namespace = objectspace.paths[namespace].description + return { + "function": "calc_value", + "params": {None: [namespace]}, + } + + CALCULATION_TYPES = { "jinja": JinjaCalculation, "information": InformationCalculation, "variable": VariableCalculation, "identifier": IdentifierCalculation, +# FOR VERSION 1.0 "suffix": IdentifierCalculation, "index": IndexCalculation, + "namespace": NamespaceCalculation, } CALCULATION_PROPERTY_TYPES = { "jinja": JinjaCalculation, @@ -753,6 +783,7 @@ CALCULATION_PROPERTY_TYPES = { "index": IndexCalculation, } BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, Calculation, None] +SECRET_BASETYPE_CALC = Union[StrictStr, JinjaCalculation] class Family(BaseModel): @@ -799,6 +830,7 @@ class Variable(BaseModel): validators: Optional[List[Calculation]] = None # value default: Union[List[BASETYPE_CALC], BASETYPE_CALC] = None + secret_manager: Optional[JinjaCalculation] = None # properties auto_save: bool = False mandatory: Union[None, bool, Calculation] = None diff --git a/src/rougail/tiramisu.py b/src/rougail/tiramisu.py index 0ce12895f..0ff3cbd6b 100644 --- a/src/rougail/tiramisu.py +++ b/src/rougail/tiramisu.py @@ -86,8 +86,10 @@ def test_propertyerror(value: Any) -> bool: ENV.tests["propertyerror"] = test_propertyerror -def load_functions(path): +def load_functions(path, dict_func=None): global _SourceFileLoader, _spec_from_loader, _module_from_spec, func + if dict_func is None: + dict_func = func loader = _SourceFileLoader("func", path) spec = _spec_from_loader(loader.name, loader) func_ = _module_from_spec(spec) @@ -95,7 +97,7 @@ def load_functions(path): for function in dir(func_): if function.startswith("_"): continue - func[function] = getattr(func_, function) + dict_func[function] = getattr(func_, function) def rougail_calc_value(*args, __default_value=None, __internal_multi=False, **kwargs): @@ -107,6 +109,21 @@ def rougail_calc_value(*args, __default_value=None, __internal_multi=False, **kw return values +def kw_to_string(kw, root=None): +# {'_': {'interfaces': {'ip': ['192.168.44.19', '192.168.44.12'], 'domain_name': ['nginx-reverse-proxy.reverseproxy.test.net', 'nginx-reverse-proxy.localdns.test.net']}, 'dns_client_address': 'nginx-reverse-proxy.localdns.test.net'}} + for name, data in kw.items(): + if root is None: + path = name + else: + path = root + '.' + name + if isinstance(data, dict): + yield from kw_to_string(data, root=path) + else: + yield f"{path}={data}" + + pass + + @function_waiting_for_error def jinja_to_function( __internal_variable, @@ -144,12 +161,13 @@ def jinja_to_function( try: values = ENV.get_template(__internal_jinja).render(kw, **func).strip() except Exception as err: + kw_str = ", ".join(kw_to_string(kw)) raise ConfigError( - f'cannot calculating "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}' + f'cannot calculating "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)} with parameters "{kw_str}": {err}' ) from err convert = CONVERT_OPTION[__internal_type].get("func", str) if __internal_multi: - values = [convert(val) for val in values.split("\n") if val != ""] + values = [convert(val.strip()) for val in values.split("\n") if val.strip() != ""] if not values and __default_value is not None: return __default_value return values @@ -204,6 +222,7 @@ func["jinja_to_property"] = jinja_to_property func["jinja_to_property_help"] = jinja_to_property_help func["variable_to_property"] = variable_to_property func["valid_with_jinja"] = valid_with_jinja +func["normalize_family"] = normalize_family class ConvertDynOptionDescription(DynOptionDescription): diff --git a/src/rougail/tiramisureflector.py b/src/rougail/tiramisureflector.py index 1f59134b2..ff9358273 100644 --- a/src/rougail/tiramisureflector.py +++ b/src/rougail/tiramisureflector.py @@ -350,6 +350,11 @@ class Common: else: value = str(param["value"]) return "ParamValue(" + value + ")" + if param["type"] == "namespace": + namespace = param["namespace"] + if namespace: + namespace = self.objectspace.paths[param["namespace"]].description + return f"ParamValue('{namespace}')" raise Exception("pfff") def build_option_param( diff --git a/src/rougail/user_datas.py b/src/rougail/user_datas.py index 10249d551..832c094f0 100644 --- a/src/rougail/user_datas.py +++ b/src/rougail/user_datas.py @@ -23,7 +23,12 @@ from typing import List from re import findall from tiramisu import undefined, Calculation -from tiramisu.error import PropertiesOptionError, LeadershipError, ConfigError, CancelParam +from tiramisu.error import ( + PropertiesOptionError, + LeadershipError, + ConfigError, + CancelParam, +) from .i18n import _ from .object_model import CONVERT_OPTION @@ -151,8 +156,15 @@ class UserDatas: if path not in self.values: continue options = self.values[path].get("options", {}) - if options.get('allow_secrets_variables', True) is False 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"])) + if ( + options.get("allow_secrets_variables", True) is False + 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) @@ -212,7 +224,9 @@ class UserDatas: option = self.config.option(path) if option.isoptiondescription(): self.errors.warnings( - _('the option "{0}" is an option description').format(option.path()) + _('the option "{0}" is an option description').format( + option.path() + ) ) continue value = data["values"] diff --git a/tests/dictionaries/00_8calculation_namespace/makedict/after.json b/tests/dictionaries/00_8calculation_namespace/makedict/after.json new file mode 100644 index 000000000..5f85fbed7 --- /dev/null +++ b/tests/dictionaries/00_8calculation_namespace/makedict/after.json @@ -0,0 +1,6 @@ +{ + "rougail.variable": { + "owner": "default", + "value": "Rougail" + } +} diff --git a/tests/dictionaries/00_8calculation_namespace/makedict/base.json b/tests/dictionaries/00_8calculation_namespace/makedict/base.json new file mode 100644 index 000000000..4074985f8 --- /dev/null +++ b/tests/dictionaries/00_8calculation_namespace/makedict/base.json @@ -0,0 +1,3 @@ +{ + "rougail.variable": "Rougail" +} diff --git a/tests/dictionaries/00_8calculation_namespace/makedict/before.json b/tests/dictionaries/00_8calculation_namespace/makedict/before.json new file mode 100644 index 000000000..5f85fbed7 --- /dev/null +++ b/tests/dictionaries/00_8calculation_namespace/makedict/before.json @@ -0,0 +1,6 @@ +{ + "rougail.variable": { + "owner": "default", + "value": "Rougail" + } +} diff --git a/tests/dictionaries/00_8calculation_namespace/makedict/mandatory.json b/tests/dictionaries/00_8calculation_namespace/makedict/mandatory.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/dictionaries/00_8calculation_namespace/makedict/mandatory.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/dictionaries/00_8calculation_namespace/tiramisu/base.py b/tests/dictionaries/00_8calculation_namespace/tiramisu/base.py new file mode 100644 index 000000000..2274c5bda --- /dev/null +++ b/tests/dictionaries/00_8calculation_namespace/tiramisu/base.py @@ -0,0 +1,15 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_2 = StrOption(name="variable", doc="a variable", default=Calculation(func['calc_value'], Params((ParamValue("Rougail")))), properties=frozenset({"standard"}), informations={'type': 'string'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2], properties=frozenset({"standard"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/00_8calculation_param_namespace/makedict/after.json b/tests/dictionaries/00_8calculation_param_namespace/makedict/after.json new file mode 100644 index 000000000..5f85fbed7 --- /dev/null +++ b/tests/dictionaries/00_8calculation_param_namespace/makedict/after.json @@ -0,0 +1,6 @@ +{ + "rougail.variable": { + "owner": "default", + "value": "Rougail" + } +} diff --git a/tests/dictionaries/00_8calculation_param_namespace/makedict/base.json b/tests/dictionaries/00_8calculation_param_namespace/makedict/base.json new file mode 100644 index 000000000..4074985f8 --- /dev/null +++ b/tests/dictionaries/00_8calculation_param_namespace/makedict/base.json @@ -0,0 +1,3 @@ +{ + "rougail.variable": "Rougail" +} diff --git a/tests/dictionaries/00_8calculation_param_namespace/makedict/before.json b/tests/dictionaries/00_8calculation_param_namespace/makedict/before.json new file mode 100644 index 000000000..5f85fbed7 --- /dev/null +++ b/tests/dictionaries/00_8calculation_param_namespace/makedict/before.json @@ -0,0 +1,6 @@ +{ + "rougail.variable": { + "owner": "default", + "value": "Rougail" + } +} diff --git a/tests/dictionaries/00_8calculation_param_namespace/makedict/mandatory.json b/tests/dictionaries/00_8calculation_param_namespace/makedict/mandatory.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/dictionaries/00_8calculation_param_namespace/makedict/mandatory.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/dictionaries/00_8calculation_param_namespace/tiramisu/base.py b/tests/dictionaries/00_8calculation_param_namespace/tiramisu/base.py new file mode 100644 index 000000000..085a2e6b9 --- /dev/null +++ b/tests/dictionaries/00_8calculation_param_namespace/tiramisu/base.py @@ -0,0 +1,16 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +dict_env['default_rougail.variable'] = "{{ namespace }}" +option_2 = StrOption(name="variable", doc="a variable", default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.variable"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(False), '__internal_files': ParamValue(['../rougail-tests/structures/00_8calculation_param_namespace/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.variable"), 'namespace': ParamValue('Rougail')})), properties=frozenset({"standard"}), informations={'type': 'string'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2], properties=frozenset({"standard"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/after.json b/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/after.json new file mode 100644 index 000000000..d4b25401c --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/after.json @@ -0,0 +1,6 @@ +{ + "rougail.condition": { + "owner": "default", + "value": [] + } +} diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/base.json b/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/base.json new file mode 100644 index 000000000..d7abab462 --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/base.json @@ -0,0 +1,3 @@ +{ + "rougail.condition": [] +} diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/before.json b/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/before.json new file mode 100644 index 000000000..d4b25401c --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/before.json @@ -0,0 +1,6 @@ +{ + "rougail.condition": { + "owner": "default", + "value": [] + } +} diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/mandatory.json b/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/mandatory.json new file mode 100644 index 000000000..8fcc81633 --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi2/makedict/mandatory.json @@ -0,0 +1 @@ +["rougail.condition"] \ No newline at end of file diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi2/tiramisu/base.py b/tests/dictionaries/04_5disabled_calculation_variable_multi2/tiramisu/base.py new file mode 100644 index 000000000..e485b2949 --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi2/tiramisu/base.py @@ -0,0 +1,16 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_2 = StrOption(name="condition", doc="a condition", multi=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'}) +option_3 = StrOption(name="variable", doc="a variable", multi=True, properties=frozenset({"basic", "mandatory", Calculation(func['variable_to_property'], Params((ParamValue("disabled"), ParamOption(option_2)), kwargs={'__internal_multi': ParamValue(True), 'when': ParamValue([]), 'inverse': ParamValue(False)}), help_function=func['variable_to_property'])}), informations={'type': 'string'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"basic"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi2/tiramisu/no_namespace.py b/tests/dictionaries/04_5disabled_calculation_variable_multi2/tiramisu/no_namespace.py new file mode 100644 index 000000000..3dec71082 --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi2/tiramisu/no_namespace.py @@ -0,0 +1,11 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_1 = StrOption(name="condition", doc="a condition", multi=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'}) +option_2 = StrOption(name="variable", doc="a variable", multi=True, properties=frozenset({"basic", "mandatory", Calculation(func['variable_to_property'], Params((ParamValue("disabled"), ParamOption(option_1)), kwargs={'__internal_multi': ParamValue(True), 'when': ParamValue([]), 'inverse': ParamValue(False)}), help_function=func['variable_to_property'])}), informations={'type': 'string'}) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2]) diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/after.json b/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/after.json new file mode 100644 index 000000000..690c221ea --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/after.json @@ -0,0 +1,13 @@ +{ + "rougail.condition": { + "owner": "default", + "value": [ + "val1", + "val2" + ] + }, + "rougail.variable": { + "owner": "default", + "value": [] + } +} diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/base.json b/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/base.json new file mode 100644 index 000000000..dab247add --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/base.json @@ -0,0 +1,7 @@ +{ + "rougail.condition": [ + "val1", + "val2" + ], + "rougail.variable": [] +} diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/before.json b/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/before.json new file mode 100644 index 000000000..690c221ea --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/before.json @@ -0,0 +1,13 @@ +{ + "rougail.condition": { + "owner": "default", + "value": [ + "val1", + "val2" + ] + }, + "rougail.variable": { + "owner": "default", + "value": [] + } +} diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/mandatory.json b/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/mandatory.json new file mode 100644 index 000000000..bdc34fddd --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi3/makedict/mandatory.json @@ -0,0 +1 @@ +["rougail.variable"] \ No newline at end of file diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi3/tiramisu/base.py b/tests/dictionaries/04_5disabled_calculation_variable_multi3/tiramisu/base.py new file mode 100644 index 000000000..5f6025e4a --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi3/tiramisu/base.py @@ -0,0 +1,16 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_2 = StrOption(name="condition", doc="a condition", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_3 = StrOption(name="variable", doc="a variable", multi=True, properties=frozenset({"basic", "mandatory", Calculation(func['variable_to_property'], Params((ParamValue("disabled"), ParamOption(option_2)), kwargs={'__internal_multi': ParamValue(True), 'when': ParamValue([]), 'inverse': ParamValue(False)}), help_function=func['variable_to_property'])}), informations={'type': 'string'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"basic"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/04_5disabled_calculation_variable_multi3/tiramisu/no_namespace.py b/tests/dictionaries/04_5disabled_calculation_variable_multi3/tiramisu/no_namespace.py new file mode 100644 index 000000000..f8800e619 --- /dev/null +++ b/tests/dictionaries/04_5disabled_calculation_variable_multi3/tiramisu/no_namespace.py @@ -0,0 +1,11 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_1 = StrOption(name="condition", doc="a condition", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_2 = StrOption(name="variable", doc="a variable", multi=True, properties=frozenset({"basic", "mandatory", Calculation(func['variable_to_property'], Params((ParamValue("disabled"), ParamOption(option_1)), kwargs={'__internal_multi': ParamValue(True), 'when': ParamValue([]), 'inverse': ParamValue(False)}), help_function=func['variable_to_property'])}), informations={'type': 'string'}) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2]) diff --git a/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/after.json b/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/after.json new file mode 100644 index 000000000..1af9b9048 --- /dev/null +++ b/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/after.json @@ -0,0 +1,25 @@ +{ + "rougail.var": { + "owner": "default", + "value": [ + "val.1", + "val.2" + ] + }, + "rougail.dynval_1.var1": { + "owner": "default", + "value": "val.1" + }, + "rougail.dynval_1.var2": { + "owner": "default", + "value": "val.1" + }, + "rougail.dynval_2.var1": { + "owner": "default", + "value": "val.2" + }, + "rougail.dynval_2.var2": { + "owner": "default", + "value": "val.2" + } +} diff --git a/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/base.json b/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/base.json new file mode 100644 index 000000000..d85c7b561 --- /dev/null +++ b/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/base.json @@ -0,0 +1,10 @@ +{ + "rougail.var": [ + "val.1", + "val.2" + ], + "rougail.dynval_1.var1": "val.1", + "rougail.dynval_1.var2": "val.1", + "rougail.dynval_2.var1": "val.2", + "rougail.dynval_2.var2": "val.2" +} diff --git a/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/before.json b/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/before.json new file mode 100644 index 000000000..1af9b9048 --- /dev/null +++ b/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/before.json @@ -0,0 +1,25 @@ +{ + "rougail.var": { + "owner": "default", + "value": [ + "val.1", + "val.2" + ] + }, + "rougail.dynval_1.var1": { + "owner": "default", + "value": "val.1" + }, + "rougail.dynval_1.var2": { + "owner": "default", + "value": "val.1" + }, + "rougail.dynval_2.var1": { + "owner": "default", + "value": "val.2" + }, + "rougail.dynval_2.var2": { + "owner": "default", + "value": "val.2" + } +} diff --git a/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/mandatory.json b/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/mandatory.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/dictionaries/60_0family_dynamic_forbidden_char/makedict/mandatory.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/dictionaries/60_0family_dynamic_forbidden_char/tiramisu/base.py b/tests/dictionaries/60_0family_dynamic_forbidden_char/tiramisu/base.py new file mode 100644 index 000000000..949054479 --- /dev/null +++ b/tests/dictionaries/60_0family_dynamic_forbidden_char/tiramisu/base.py @@ -0,0 +1,19 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +dict_env['default_rougail.dyn{{ identifier }}.var2'] = "{{ identifier }}" +option_2 = StrOption(name="var", doc="A suffix variable", multi=True, default=["val.1", "val.2"], default_multi="val.1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_4 = StrOption(name="var1", doc="A dynamic variable", default=Calculation(func['calc_value'], Params((ParamIdentifier()))), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_5 = StrOption(name="var2", doc="A dynamic variable", default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.dyn{{ identifier }}.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(False), '__internal_files': ParamValue(['../rougail-tests/structures/60_0family_dynamic_forbidden_char/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.dyn{{ identifier }}.var2"), 'identifier': ParamIdentifier()})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +optiondescription_3 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="A dynamic family", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_2)))), children=[option_4, option_5], properties=frozenset({"standard"}), informations={'dynamic_variable': 'rougail.var'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, optiondescription_3], properties=frozenset({"standard"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/60_0family_dynamic_forbidden_char/tiramisu/no_namespace.py b/tests/dictionaries/60_0family_dynamic_forbidden_char/tiramisu/no_namespace.py new file mode 100644 index 000000000..3a9eb1c3f --- /dev/null +++ b/tests/dictionaries/60_0family_dynamic_forbidden_char/tiramisu/no_namespace.py @@ -0,0 +1,14 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +dict_env['default_dyn{{ identifier }}.var2'] = "{{ identifier }}" +option_1 = StrOption(name="var", doc="A suffix variable", multi=True, default=["val.1", "val.2"], default_multi="val.1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_3 = StrOption(name="var1", doc="A dynamic variable", default=Calculation(func['calc_value'], Params((ParamIdentifier()))), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_4 = StrOption(name="var2", doc="A dynamic variable", default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_dyn{{ identifier }}.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(False), '__internal_files': ParamValue(['../rougail-tests/structures/60_0family_dynamic_forbidden_char/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("dyn{{ identifier }}.var2"), 'identifier': ParamIdentifier()})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +optiondescription_2 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="A dynamic family", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_1)))), children=[option_3, option_4], properties=frozenset({"standard"}), informations={'dynamic_variable': 'var'}) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, optiondescription_2]) diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/after.json b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/after.json new file mode 100644 index 000000000..fec8f4dc2 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/after.json @@ -0,0 +1,10 @@ +{ + "rougail.var1": { + "owner": "default", + "value": [] + }, + "rougail.var2": { + "owner": "default", + "value": null + } +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/base.json b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/base.json new file mode 100644 index 000000000..a20d18a3f --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/base.json @@ -0,0 +1,4 @@ +{ + "rougail.var1": [], + "rougail.var2": null +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/before.json b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/before.json new file mode 100644 index 000000000..fec8f4dc2 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/before.json @@ -0,0 +1,10 @@ +{ + "rougail.var1": { + "owner": "default", + "value": [] + }, + "rougail.var2": { + "owner": "default", + "value": null + } +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/mandatory.json b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/mandatory.json new file mode 100644 index 000000000..4fb69ce52 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/makedict/mandatory.json @@ -0,0 +1 @@ +["rougail.var2"] \ No newline at end of file diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/tiramisu/base.py b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/tiramisu/base.py new file mode 100644 index 000000000..766fae98c --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_2/tiramisu/base.py @@ -0,0 +1,18 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_2 = StrOption(name="var1", doc="A suffix variable", multi=True, properties=frozenset({"standard"}), informations={'type': 'string', 'test': ('val1',)}) +option_4 = StrOption(name="var", doc="A dynamic variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'}) +optiondescription_3 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="dyn{{ identifier }}", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_2)))), children=[option_4], properties=frozenset({"basic"}), informations={'dynamic_variable': 'rougail.var1'}) +option_5 = StrOption(name="var2", doc="A variable calculated", default=Calculation(func['calc_value'], Params((ParamDynOption(option_4, ["val1"], optional=True)))), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, optiondescription_3, option_5], properties=frozenset({"basic"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/after.json b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/after.json new file mode 100644 index 000000000..e05750f15 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/after.json @@ -0,0 +1,10 @@ +{ + "rougail.var2": { + "owner": "default", + "value": null + }, + "rougail.var1": { + "owner": "default", + "value": [] + } +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/base.json b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/base.json new file mode 100644 index 000000000..4b4cc1083 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/base.json @@ -0,0 +1,4 @@ +{ + "rougail.var2": null, + "rougail.var1": [] +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/before.json b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/before.json new file mode 100644 index 000000000..e05750f15 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/before.json @@ -0,0 +1,10 @@ +{ + "rougail.var2": { + "owner": "default", + "value": null + }, + "rougail.var1": { + "owner": "default", + "value": [] + } +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/mandatory.json b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/mandatory.json new file mode 100644 index 000000000..4fb69ce52 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/makedict/mandatory.json @@ -0,0 +1 @@ +["rougail.var2"] \ No newline at end of file diff --git a/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/tiramisu/base.py b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/tiramisu/base.py new file mode 100644 index 000000000..76c43fbea --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_suffix_empty_3/tiramisu/base.py @@ -0,0 +1,18 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_5 = StrOption(name="var", doc="A dynamic variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'}) +option_2 = StrOption(name="var2", doc="A variable calculated", default=Calculation(func['calc_value'], Params((ParamDynOption(option_5, ["val1"], optional=True)))), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_3 = StrOption(name="var1", doc="A suffix variable", multi=True, properties=frozenset({"standard"}), informations={'type': 'string', 'test': ('val1', 'val2')}) +optiondescription_4 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="dyn{{ identifier }}", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_3)))), children=[option_5], properties=frozenset({"basic"}), informations={'dynamic_variable': 'rougail.var1'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3, optiondescription_4], properties=frozenset({"basic"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/after.json b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/after.json new file mode 100644 index 000000000..d3125ba01 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/after.json @@ -0,0 +1,24 @@ +{ + "rougail.var": { + "owner": "default", + "value": [ + "val1", + "val2" + ] + }, + "rougail.my_dyn_family_val1.var": { + "owner": "default", + "value": "val1" + }, + "rougail.my_dyn_family_val2.var": { + "owner": "default", + "value": "val2" + }, + "rougail.var2": { + "owner": "default", + "value": [ + "val1", + "val2" + ] + } +} diff --git a/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/base.json b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/base.json new file mode 100644 index 000000000..8bde92058 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/base.json @@ -0,0 +1,12 @@ +{ + "rougail.var": [ + "val1", + "val2" + ], + "rougail.my_dyn_family_val1.var": "val1", + "rougail.my_dyn_family_val2.var": "val2", + "rougail.var2": [ + "val1", + "val2" + ] +} diff --git a/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/before.json b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/before.json new file mode 100644 index 000000000..d3125ba01 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/before.json @@ -0,0 +1,24 @@ +{ + "rougail.var": { + "owner": "default", + "value": [ + "val1", + "val2" + ] + }, + "rougail.my_dyn_family_val1.var": { + "owner": "default", + "value": "val1" + }, + "rougail.my_dyn_family_val2.var": { + "owner": "default", + "value": "val2" + }, + "rougail.var2": { + "owner": "default", + "value": [ + "val1", + "val2" + ] + } +} diff --git a/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/mandatory.json b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/mandatory.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/makedict/mandatory.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/tiramisu/base.py b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/tiramisu/base.py new file mode 100644 index 000000000..d60315c10 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_variable_outside_1_0/tiramisu/base.py @@ -0,0 +1,18 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_2 = StrOption(name="var", doc="a suffix variable", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_4 = StrOption(name="var", doc="a variable inside a dynamic family", default=Calculation(func['calc_value'], Params((ParamIdentifier()))), properties=frozenset({"standard"}), informations={'type': 'string'}) +optiondescription_3 = ConvertDynOptionDescription(name="my_dyn_family_{{ identifier }}", doc="a dynamic family", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_2, notraisepropertyerror=True)), kwargs={'allow_none': ParamValue(True)})), children=[option_4], properties=frozenset({"standard"}), informations={'dynamic_variable': 'rougail.var'}) +option_5 = StrOption(name="var2", doc="a variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_4)), kwargs={'__internal_multi': ParamValue(True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, optiondescription_3, option_5], properties=frozenset({"standard"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/test_1_flattener.py b/tests/test_1_flattener.py index aec9458e8..7527461df 100644 --- a/tests/test_1_flattener.py +++ b/tests/test_1_flattener.py @@ -45,7 +45,7 @@ excludes = set([ ]) test_ok -= excludes test_raise -= excludes -# test_ok = ['00_9default_calculation_multi_optional'] +# test_ok = ['00_8calculation_param_namespace'] #test_ok = [] #test_raise = ['88valid_enum_invalid_default'] #test_raise = []