From 4939b984bffcd6d56007a75a07615ed4571e0eca Mon Sep 17 00:00:00 2001
From: Emmanuel Garette <egarette@silique.fr>
Date: Tue, 6 Aug 2024 09:51:29 +0200
Subject: [PATCH] copy type/params and multi if default value is a variable
 calculation (#9 and #31)

---
 docs/fill.rst                                 | 18 ++++++++++
 src/rougail/annotator/family.py               |  1 -
 src/rougail/annotator/variable.py             | 35 +++++++++++++++++--
 .../__init__.py                               |  0
 .../dictionaries/rougail/00-base.yml          | 13 +++++++
 .../makedict/after.json                       | 10 ++++++
 .../makedict/base.json                        |  4 +++
 .../makedict/before.json                      | 10 ++++++
 .../makedict/mandatory.json                   |  1 +
 .../tiramisu/base.py                          | 11 ++++++
 .../tiramisu/multi.py                         | 16 +++++++++
 .../tiramisu/no_namespace.py                  | 10 ++++++
 tests/test_1_flattener.py                     |  2 ++
 13 files changed, 127 insertions(+), 4 deletions(-)
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/__init__.py
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/dictionaries/rougail/00-base.yml
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/makedict/after.json
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/makedict/base.json
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/makedict/before.json
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/makedict/mandatory.json
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/base.py
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/multi.py
 create mode 100644 tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/no_namespace.py

diff --git a/docs/fill.rst b/docs/fill.rst
index af3ba0b54..d796bff25 100644
--- a/docs/fill.rst
+++ b/docs/fill.rst
@@ -394,6 +394,24 @@ Copy a variable in another:
         type: variable
         variable: _.my_variable
 
+Copy the default value from a variable, means copy type, params and multi attribute too if not define in second variable.
+
+.. code-block:: yaml
+
+      ---
+      version: 1.1
+      my_variable:
+        multi: true
+        type: domainname
+        params:
+          allow_ip: true
+      my_calculated_variable:
+        default:
+          type: variable
+          variable: _.var1
+
+Here my_calculated_variable is a domainname variable with parameter allow_ip=True and multi to true.
+
 Copy one variable to another if the source has no `property` problem:
 
 .. code-block:: yaml
diff --git a/src/rougail/annotator/family.py b/src/rougail/annotator/family.py
index 6f327517d..4ce8d7602 100644
--- a/src/rougail/annotator/family.py
+++ b/src/rougail/annotator/family.py
@@ -30,7 +30,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 from typing import Optional
 from rougail.i18n import _
 from rougail.error import DictConsistencyError
-from rougail.utils import get_realpath
 from rougail.annotator.variable import Walk
 from rougail.object_model import VariableCalculation
 
diff --git a/src/rougail/annotator/variable.py b/src/rougail/annotator/variable.py
index 474899c38..04350f5cc 100644
--- a/src/rougail/annotator/variable.py
+++ b/src/rougail/annotator/variable.py
@@ -30,7 +30,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 from rougail.i18n import _
 from rougail.error import DictConsistencyError
-from rougail.object_model import Calculation
+from rougail.object_model import Calculation, VariableCalculation
+from rougail.utils import get_realpath
 from tiramisu.error import display_list
 
 
@@ -82,11 +83,16 @@ class Annotator(Walk):  # pylint: disable=R0903
 
     def convert_variable(self):
         """convert variable"""
+        for variable in self.get_variables():
+            if variable.version != "1.0":
+                if variable.type == "symlink":
+                    continue
+                self._convert_variable_inference(variable)
         for variable in self.get_variables():
             if variable.type == "symlink":
                 continue
             if variable.version != "1.0":
-                self._convert_variable_inference(variable)
+                self._default_variable_copy_informations(variable)
             if variable.multi is None:
                 variable.multi = False
             if variable.type is None:
@@ -112,12 +118,35 @@ class Annotator(Walk):  # pylint: disable=R0903
                     tested_value = variable.default
                 variable.type = self.basic_types.get(type(tested_value), None)
         # variable has no multi attribute
-        if variable.multi is None:
+        if variable.multi is None and not (variable.type is None and isinstance(variable.default, VariableCalculation)):
             if variable.path in self.objectspace.leaders:
                 variable.multi = True
             else:
                 variable.multi = isinstance(variable.default, list)
 
+    def _default_variable_copy_informations(
+            self,
+            variable,
+    ) -> None:
+        # if a variable has a variable as default value, that means the type/params or multi should has same value
+        if variable.type is not None or not isinstance(variable.default, VariableCalculation):
+            return
+        # copy type and params
+        calculated_variable_path = variable.default.variable
+        calculated_variable, suffix = self.objectspace.paths.get_with_dynamic(
+            calculated_variable_path, variable.default.path_prefix, variable.path, variable.version, variable.namespace, variable.xmlfiles
+        )
+        variable.type = calculated_variable.type
+        if variable.params is None and calculated_variable.params is not None:
+            variable.params = calculated_variable.params
+        # copy multi attribut
+        if variable.multi is None:
+            calculated_path = calculated_variable.path
+            if calculated_path in self.objectspace.leaders and variable.path in self.objectspace.followers and calculated_path.rsplit('.')[0] == variable.path.rsplit('.')[0]:
+                variable.multi = False
+            else:
+                variable.multi = calculated_variable.multi
+
     def _convert_variable(
         self,
         variable: dict,
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/__init__.py b/tests/dictionaries/00_2default_calculated_variable_transitive/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/dictionaries/rougail/00-base.yml b/tests/dictionaries/00_2default_calculated_variable_transitive/dictionaries/rougail/00-base.yml
new file mode 100644
index 000000000..81a09e501
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_transitive/dictionaries/rougail/00-base.yml
@@ -0,0 +1,13 @@
+---
+version: 1.1
+var1:
+  description: a first variable
+  multi: true
+  type: domainname
+  params:
+    allow_ip: true
+var2:
+  description: a second variable
+  default:
+    type: variable
+    variable: _.var1
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/after.json b/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/after.json
new file mode 100644
index 000000000..2c76fb1fd
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/after.json
@@ -0,0 +1,10 @@
+{
+    "rougail.var1": {
+        "owner": "default",
+        "value": []
+    },
+    "rougail.var2": {
+        "owner": "default",
+        "value": []
+    }
+}
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/base.json b/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/base.json
new file mode 100644
index 000000000..687890720
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/base.json
@@ -0,0 +1,4 @@
+{
+    "rougail.var1": [],
+    "rougail.var2": []
+}
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/before.json b/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/before.json
new file mode 100644
index 000000000..2c76fb1fd
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/before.json
@@ -0,0 +1,10 @@
+{
+    "rougail.var1": {
+        "owner": "default",
+        "value": []
+    },
+    "rougail.var2": {
+        "owner": "default",
+        "value": []
+    }
+}
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/mandatory.json b/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/mandatory.json
new file mode 100644
index 000000000..8c67ac5f8
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_transitive/makedict/mandatory.json
@@ -0,0 +1 @@
+["rougail.var1", "rougail.var2"]
\ No newline at end of file
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/base.py b/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/base.py
new file mode 100644
index 000000000..50a814961
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/base.py
@@ -0,0 +1,11 @@
+from tiramisu import *
+from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
+from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
+load_functions('tests/dictionaries/../eosfunc/test.py')
+ALLOWED_LEADER_PROPERTIES.add("basic")
+ALLOWED_LEADER_PROPERTIES.add("standard")
+ALLOWED_LEADER_PROPERTIES.add("advanced")
+option_2 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory", "notempty"}), informations={'type': 'domainname'})
+option_3 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_2)))), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "notempty", "standard"}), informations={'type': 'domainname'})
+optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", children=[option_2, option_3], properties=frozenset({"basic"}))
+option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/multi.py b/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/multi.py
new file mode 100644
index 000000000..87aa50795
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/multi.py
@@ -0,0 +1,16 @@
+from tiramisu import *
+from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
+from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
+load_functions('tests/dictionaries/../eosfunc/test.py')
+ALLOWED_LEADER_PROPERTIES.add("basic")
+ALLOWED_LEADER_PROPERTIES.add("standard")
+ALLOWED_LEADER_PROPERTIES.add("advanced")
+option_3 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory", "notempty"}), informations={'type': 'domainname'})
+option_4 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_3)))), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "notempty", "standard"}), informations={'type': 'domainname'})
+optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", children=[option_3, option_4], properties=frozenset({"basic"}))
+optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"basic"}))
+option_7 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory", "notempty"}), informations={'type': 'domainname'})
+option_8 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_7)))), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "notempty", "standard"}), informations={'type': 'domainname'})
+optiondescription_6 = OptionDescription(name="rougail", doc="Rougail", children=[option_7, option_8], properties=frozenset({"basic"}))
+optiondescription_5 = OptionDescription(name="2", doc="2", children=[optiondescription_6], properties=frozenset({"basic"}))
+option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_5])
diff --git a/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/no_namespace.py b/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/no_namespace.py
new file mode 100644
index 000000000..1b3e95c94
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_transitive/tiramisu/no_namespace.py
@@ -0,0 +1,10 @@
+from tiramisu import *
+from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
+from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
+load_functions('tests/dictionaries/../eosfunc/test.py')
+ALLOWED_LEADER_PROPERTIES.add("basic")
+ALLOWED_LEADER_PROPERTIES.add("standard")
+ALLOWED_LEADER_PROPERTIES.add("advanced")
+option_1 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory", "notempty"}), informations={'type': 'domainname'})
+option_2 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_1)))), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "notempty", "standard"}), informations={'type': 'domainname'})
+option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])
diff --git a/tests/test_1_flattener.py b/tests/test_1_flattener.py
index 97cca2382..c91737020 100644
--- a/tests/test_1_flattener.py
+++ b/tests/test_1_flattener.py
@@ -46,8 +46,10 @@ test_multi = True
 #test_multi = False
 test_base = test_multi
 #test_base = True
+#test_base = False
 test_no_namespace = test_multi
 #test_no_namespace = True
+#test_no_namespace = False
 
 ORI_DIR = getcwd()