From e29c0c8abc99b4f1bdc7ff2ef080dfba988f3c4e Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Fri, 8 Nov 2024 08:12:33 +0100 Subject: [PATCH] feat: add force_optional option to allow charging structure even if all variables are not available --- src/rougail/annotator/property.py | 2 +- src/rougail/config.py | 6 ++ src/rougail/convert.py | 1 + src/rougail/object_model.py | 67 +++++++++++-------- .../makedict/after.json | 13 +--- .../makedict/base.json | 7 +- .../makedict/before.json | 13 +--- .../makedict/mandatory.json | 2 +- .../60_0family_dynamic_test/tiramisu/base.py | 2 +- .../60_0family_dynamic_test/tiramisu/multi.py | 4 +- .../tiramisu/no_namespace.py | 2 +- 11 files changed, 54 insertions(+), 65 deletions(-) diff --git a/src/rougail/annotator/property.py b/src/rougail/annotator/property.py index ab1855ae2..d21ab07f5 100644 --- a/src/rougail/annotator/property.py +++ b/src/rougail/annotator/property.py @@ -120,7 +120,7 @@ class Annotator(Walk): else: value = [] for calculation in frozen: - calculation_copy = calculation.copy() + calculation_copy = calculation.model_copy() calculation_copy.attribute_name = "frozen" calculation_copy.ori_path = calculation_copy.path calculation_copy.path = path diff --git a/src/rougail/config.py b/src/rougail/config.py index a92486c96..9ddcb3a9b 100644 --- a/src/rougail/config.py +++ b/src/rougail/config.py @@ -182,6 +182,7 @@ class FakeRougailConvert(RougailConvert): self.base_option_name = "baseoption" self.export_with_import = True self.internal_functions = [] + self.force_optional = False self.plugins = ["structural_commandline"] self.add_extra_options = self.add_extra_options @@ -387,6 +388,11 @@ suffix: default: '' mandatory: false commandline: false + +force_optional: + description: Every variable in calculation are optional + negative_description: Variable in calculation are not optional by default + default: False """ processes = { "structural": [], diff --git a/src/rougail/convert.py b/src/rougail/convert.py index 69bbeaa94..e9e0cb1f1 100644 --- a/src/rougail/convert.py +++ b/src/rougail/convert.py @@ -390,6 +390,7 @@ class ParserVariable: self.base_option_name = rougailconfig["base_option_name"] self.export_with_import = rougailconfig["export_with_import"] self.internal_functions = rougailconfig["internal_functions"] + self.force_optional = rougailconfig["force_optional"] self.add_extra_options = rougailconfig[ "structural_commandline.add_extra_options" ] diff --git a/src/rougail/object_model.py b/src/rougail/object_model.py index 79e637dad..2cdecbe47 100644 --- a/src/rougail/object_model.py +++ b/src/rougail/object_model.py @@ -432,8 +432,10 @@ class _VariableCalculation(Calculation): needs_multi: Optional[bool] = None, ): if not variable: - msg = f'Variable not found "{self.variable}" for attribut "{self.attribute_name}" for variable "{self.path}"' - raise DictConsistencyError(msg, 88, self.xmlfiles) + if not objectspace.force_optional: + msg = f'Variable not found "{self.variable}" for attribut "{self.attribute_name}" for variable "{self.path}"' + raise DictConsistencyError(msg, 88, self.xmlfiles) + return {None: [['example']]} param = { "type": "variable", "variable": variable, @@ -503,7 +505,7 @@ class VariableCalculation(_VariableCalculation): msg = f'"{self.attribute_name}" variable shall not have an "optional" attribute for variable "{self.variable}"' raise DictConsistencyError(msg, 33, self.xmlfiles) variable, identifier = self.get_variable(objectspace) - if not variable and self.optional: + if not variable and self.optional or (objectspace.force_optional and self.attribute_name == "default"): raise VariableCalculationDependencyError() params = self.get_params( objectspace, @@ -532,30 +534,31 @@ class VariablePropertyCalculation(_VariableCalculation): identifier, needs_multi=False, ) - variable = params[None][0]["variable"] - if self.when is not undefined: - if self.version == "1.0": - msg = f'when is not allowed in format version 1.0 for attribute "{self.attribute_name}" for variable "{self.path}"' - raise DictConsistencyError(msg, 103, variable.xmlfiles) - if self.when_not is not undefined: - msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", when and when_not cannot set together' - raise DictConsistencyError(msg, 31, variable.xmlfiles) - when = self.when - inverse = False - elif self.when_not is not undefined: - if self.version == "1.0": - msg = f'when_not is not allowed in format version 1.0 for attribute "{self.attribute_name}" for variable "{self.path}"' - raise DictConsistencyError(msg, 104, variable.xmlfiles) - when = self.when_not - inverse = True - else: - if variable.type != "boolean": - raise Exception("only boolean!") - when = True - inverse = False + if params[None] and "variable" in params[None][0]: + variable = params[None][0]["variable"] + if self.when is not undefined: + if self.version == "1.0": + msg = f'when is not allowed in format version 1.0 for attribute "{self.attribute_name}" for variable "{self.path}"' + raise DictConsistencyError(msg, 103, variable.xmlfiles) + if self.when_not is not undefined: + msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", when and when_not cannot set together' + raise DictConsistencyError(msg, 31, variable.xmlfiles) + when = self.when + inverse = False + elif self.when_not is not undefined: + if self.version == "1.0": + msg = f'when_not is not allowed in format version 1.0 for attribute "{self.attribute_name}" for variable "{self.path}"' + raise DictConsistencyError(msg, 104, variable.xmlfiles) + when = self.when_not + inverse = True + else: + if variable.type != "boolean": + raise Exception("only boolean!") + when = True + inverse = False + params["when"] = when + params["inverse"] = inverse params[None].insert(0, self.attribute_name) - params["when"] = when - params["inverse"] = inverse return { "function": "variable_to_property", "params": params, @@ -593,9 +596,15 @@ class InformationCalculation(Calculation): self.namespace, self.xmlfiles, ) - if variable is None or identifier is not None: - raise Exception("pfff") - params[None][0]["variable"] = variable + if variable is None: + if not objectspace.force_optional: + msg = f'cannot find variable "{self.variable}" for the information "{self.information}" when calculating "{self.attribute_name}"' + raise DictConsistencyError(msg, 40, variable.xmlfiles) + if identifier is not None: + msg = f'identifier not allowed for the information "{self.information}" when calculating "{self.attribute_name}"' + raise DictConsistencyError(msg, 41, variable.xmlfiles) + if variable: + params[None][0]["variable"] = variable if self.default_values: params["__default_value"] = self.default_values return { diff --git a/tests/dictionaries/60_0family_dynamic_test/makedict/after.json b/tests/dictionaries/60_0family_dynamic_test/makedict/after.json index e72db9d1d..7799ba404 100644 --- a/tests/dictionaries/60_0family_dynamic_test/makedict/after.json +++ b/tests/dictionaries/60_0family_dynamic_test/makedict/after.json @@ -1,17 +1,6 @@ { "rougail.var": { "owner": "default", - "value": [ - "val1", - "val2" - ] - }, - "rougail.dynval1.var": { - "owner": "default", - "value": null - }, - "rougail.dynval2.var": { - "owner": "default", - "value": null + "value": [] } } diff --git a/tests/dictionaries/60_0family_dynamic_test/makedict/base.json b/tests/dictionaries/60_0family_dynamic_test/makedict/base.json index 475b83369..a132f4593 100644 --- a/tests/dictionaries/60_0family_dynamic_test/makedict/base.json +++ b/tests/dictionaries/60_0family_dynamic_test/makedict/base.json @@ -1,8 +1,3 @@ { - "rougail.var": [ - "val1", - "val2" - ], - "rougail.dynval1.var": null, - "rougail.dynval2.var": null + "rougail.var": [] } diff --git a/tests/dictionaries/60_0family_dynamic_test/makedict/before.json b/tests/dictionaries/60_0family_dynamic_test/makedict/before.json index e72db9d1d..7799ba404 100644 --- a/tests/dictionaries/60_0family_dynamic_test/makedict/before.json +++ b/tests/dictionaries/60_0family_dynamic_test/makedict/before.json @@ -1,17 +1,6 @@ { "rougail.var": { "owner": "default", - "value": [ - "val1", - "val2" - ] - }, - "rougail.dynval1.var": { - "owner": "default", - "value": null - }, - "rougail.dynval2.var": { - "owner": "default", - "value": null + "value": [] } } diff --git a/tests/dictionaries/60_0family_dynamic_test/makedict/mandatory.json b/tests/dictionaries/60_0family_dynamic_test/makedict/mandatory.json index ab51d9ec5..f030b0c48 100644 --- a/tests/dictionaries/60_0family_dynamic_test/makedict/mandatory.json +++ b/tests/dictionaries/60_0family_dynamic_test/makedict/mandatory.json @@ -1 +1 @@ -["rougail.dynval1.var", "rougail.dynval2.var"] \ No newline at end of file +["rougail.var"] \ No newline at end of file diff --git a/tests/dictionaries/60_0family_dynamic_test/tiramisu/base.py b/tests/dictionaries/60_0family_dynamic_test/tiramisu/base.py index 2e382f210..92c170544 100644 --- a/tests/dictionaries/60_0family_dynamic_test/tiramisu/base.py +++ b/tests/dictionaries/60_0family_dynamic_test/tiramisu/base.py @@ -10,7 +10,7 @@ except: 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_2 = StrOption(name="var", doc="A suffix variable", multi=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'string', 'test': ('val1', 'val2')}) option_4 = StrOption(name="var", doc="A dynamic variable", properties=frozenset({"basic", "mandatory"}), 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], properties=frozenset({"basic"}), informations={'dynamic_variable': 'rougail.var'}) optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, optiondescription_3], properties=frozenset({"basic"})) diff --git a/tests/dictionaries/60_0family_dynamic_test/tiramisu/multi.py b/tests/dictionaries/60_0family_dynamic_test/tiramisu/multi.py index 13c4f9592..0d80daa67 100644 --- a/tests/dictionaries/60_0family_dynamic_test/tiramisu/multi.py +++ b/tests/dictionaries/60_0family_dynamic_test/tiramisu/multi.py @@ -10,12 +10,12 @@ except: ALLOWED_LEADER_PROPERTIES.add("basic") ALLOWED_LEADER_PROPERTIES.add("standard") ALLOWED_LEADER_PROPERTIES.add("advanced") -option_3 = StrOption(name="var", doc="A suffix variable", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_3 = StrOption(name="var", doc="A suffix variable", multi=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'string', 'test': ('val1', 'val2')}) option_5 = StrOption(name="var", doc="A dynamic variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'}) optiondescription_4 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="A dynamic family", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_3)))), children=[option_5], properties=frozenset({"basic"}), informations={'dynamic_variable': '1.rougail.var'}) optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_3, optiondescription_4], properties=frozenset({"basic"})) optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"basic"})) -option_8 = StrOption(name="var", doc="A suffix variable", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_8 = StrOption(name="var", doc="A suffix variable", multi=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'string', 'test': ('val1', 'val2')}) option_10 = StrOption(name="var", doc="A dynamic variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'}) optiondescription_9 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="A dynamic family", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_8)))), children=[option_10], properties=frozenset({"basic"}), informations={'dynamic_variable': '2.rougail.var'}) optiondescription_7 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_8, optiondescription_9], properties=frozenset({"basic"})) diff --git a/tests/dictionaries/60_0family_dynamic_test/tiramisu/no_namespace.py b/tests/dictionaries/60_0family_dynamic_test/tiramisu/no_namespace.py index dcd83fc63..afd552e03 100644 --- a/tests/dictionaries/60_0family_dynamic_test/tiramisu/no_namespace.py +++ b/tests/dictionaries/60_0family_dynamic_test/tiramisu/no_namespace.py @@ -6,7 +6,7 @@ load_functions('tests/dictionaries/../eosfunc/test.py') ALLOWED_LEADER_PROPERTIES.add("basic") ALLOWED_LEADER_PROPERTIES.add("standard") ALLOWED_LEADER_PROPERTIES.add("advanced") -option_1 = StrOption(name="var", doc="A suffix variable", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'}) +option_1 = StrOption(name="var", doc="A suffix variable", multi=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'string', 'test': ('val1', 'val2')}) option_3 = StrOption(name="var", doc="A dynamic variable", properties=frozenset({"basic", "mandatory"}), 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], properties=frozenset({"basic"}), informations={'dynamic_variable': 'var'}) option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, optiondescription_2])