diff --git a/pyproject.toml b/pyproject.toml
index 1b9ebb78b..2db49ba28 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,47 +1,6 @@
-[build-system]
-build-backend = "flit_core.buildapi"
-requires = ["flit_core >=3.8.0,<4"]
-
[project]
name = "rougail"
version = "1.2.0a26"
-authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
-readme = "README.md"
-description = "A consistency handling system that was initially designed in the configuration management"
-requires-python = ">=3.8"
-license = {file = "LICENSE"}
-classifiers = [
- "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
- "Programming Language :: Python",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Programming Language :: Python :: 3.13",
- "Programming Language :: Python :: 3.14",
- "Programming Language :: Python :: 3",
- "Operating System :: OS Independent",
- "Natural Language :: English",
- "Natural Language :: French",
-
-]
-dependencies = [
- "ruamel.yaml ~= 0.18.6",
- "pydantic ~= 2.9.2",
- "jinja2 ~= 3.1.4",
- "tiramisu >=5.0,<6"
-]
-
-[project.optional-dependencies]
-dev = [
- "pylint ~= 3.0.3",
- "pytest ~= 8.2.2",
- "lxml ~= 5.2.2"
-]
-
-[project.urls]
-Home = "https://forge.cloud.silique.fr/stove/rougail"
[tool.commitizen]
name = "cz_conventional_commits"
@@ -50,7 +9,10 @@ version_scheme = "pep440"
version_provider = "pep621"
version_files = [
"src/rougail/__version__.py",
- "pyproject.toml:version"
+ "rougail-pyproject.toml:version",
+ "rougail-pyproject.toml:rougail-base == ",
+ "rougail-base-pyproject.toml:version",
]
update_changelog_on_bump = true
changelog_merge_prerelease = true
+
diff --git a/rougail-base-pyproject.toml b/rougail-base-pyproject.toml
new file mode 100644
index 000000000..8e500cf4a
--- /dev/null
+++ b/rougail-base-pyproject.toml
@@ -0,0 +1,52 @@
+[build-system]
+build-backend = "flit_core.buildapi"
+requires = ["flit_core >=3.8.0,<4"]
+
+[project]
+name = "rougail-base"
+version = "1.2.0a26"
+authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
+readme = "README.md"
+description = "A consistency handling system that was initially designed in the configuration management"
+requires-python = ">=3.10"
+license = {file = "LICENSE"}
+classifiers = [
+ "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
+ "Programming Language :: Python :: 3",
+ "Operating System :: OS Independent",
+ "Natural Language :: English",
+ "Natural Language :: French",
+
+]
+dependencies = [
+ "jinja2 ~= 3.1.4",
+ "tiramisu ~= 5.0"
+]
+
+[project.optional-dependencies]
+dev = [
+ "pylint ~= 3.0.3",
+ "pytest ~= 8.2.2",
+]
+
+[tool.flit.module]
+name = "rougail"
+
+[tool.flit.sdist]
+exclude = [
+ "src/rougail/annotator",
+ "src/rougail/config",
+ "src/rougail/convert",
+ "src/rougail/structural_commandline",
+ "src/rougail/structural_directory",
+ "src/rougail/update",
+ ]
+
+[project.urls]
+Home = "https://forge.cloud.silique.fr/stove/rougail"
diff --git a/rougail-pyproject.toml b/rougail-pyproject.toml
new file mode 100644
index 000000000..856c2de11
--- /dev/null
+++ b/rougail-pyproject.toml
@@ -0,0 +1,35 @@
+[build-system]
+build-backend = "flit_core.buildapi"
+requires = ["flit_core >=3.8.0,<4"]
+
+[project]
+name = "rougail"
+version = "1.2.0a26"
+authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
+description = "A consistency handling system that was initially designed in the configuration management"
+classifiers = [
+ "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
+ "Programming Language :: Python",
+ "Operating System :: OS Independent",
+ "Natural Language :: English",
+ "Natural Language :: French",
+
+]
+dependencies = [
+ "ruamel.yaml ~= 0.18.6",
+ "pydantic ~= 2.9.2",
+ "rougail-base == 1.2.0a26",
+]
+
+[tool.flit.sdist]
+exclude = [
+ "src/rougail/error.py",
+ "src/rougail/i18n.py",
+ "src/rougail/tiramisu.py",
+ "src/rougail/user_datas.py",
+ "src/rougail/utils.py",
+ "src/rougail/__version__.py",
+ ]
+
+[project.urls]
+Home = "https://forge.cloud.silique.fr/stove/rougail"
diff --git a/src/rougail/__init__.py b/src/rougail/__init__.py
index 60ba4d432..265b5936d 100644
--- a/src/rougail/__init__.py
+++ b/src/rougail/__init__.py
@@ -24,56 +24,10 @@ details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
"""
-
-from tiramisu import Config
-from warnings import warn
-
-from .convert import RougailConvert
-from .config import RougailConfig
-from .utils import normalize_family
-from .object_model import CONVERT_OPTION
-from .user_datas import UserDatas
-from .tiramisu import tiramisu_display_name
from .__version__ import __version__
-
-
-class Rougail(UserDatas):
- """Main Rougail object"""
-
- def __init__(
- self,
- rougailconfig=None,
- ) -> None:
- if rougailconfig is None:
- rougailconfig = RougailConfig
- self.rougailconfig = rougailconfig
- self.converted = RougailConvert(self.rougailconfig)
- self.config = None
-
- def run(self):
- """Get Tiramisu Config"""
- if not self.config:
- tiram_obj = self.converted.save()
- optiondescription = {}
- custom_types = {
- custom.__name__: custom
- for custom in self.rougailconfig["custom_types"].values()
- }
- exec(tiram_obj, custom_types, optiondescription) # pylint: disable=W0122
- self.config = Config(
- optiondescription["option_0"],
- display_name=tiramisu_display_name,
- )
- self.config.property.read_write()
- return self.config
-
- def get_config(self):
- warn(
- "get_config is deprecated, use run instead",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.run()
-
-
-__all__ = ("Rougail", "RougailConfig")
+try:
+ from .convert import Rougail
+ from .config import RougailConfig
+ __all__ = ("Rougail", "RougailConfig", "__version__")
+except ModuleNotFoundError as err:
+ __all__ = ("__version__",)
diff --git a/src/rougail/annotator/family.py b/src/rougail/annotator/family.py
index 7c1c8db4a..ca6427e11 100644
--- a/src/rougail/annotator/family.py
+++ b/src/rougail/annotator/family.py
@@ -29,7 +29,7 @@ from typing import Optional
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.annotator.variable import Walk
-from rougail.object_model import VariableCalculation
+from rougail.convert.object_model import VariableCalculation
class Mode: # pylint: disable=R0903
diff --git a/src/rougail/annotator/property.py b/src/rougail/annotator/property.py
index af883c60b..cb834b6e5 100644
--- a/src/rougail/annotator/property.py
+++ b/src/rougail/annotator/property.py
@@ -29,7 +29,7 @@ from typing import Union
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.annotator.variable import Walk
-from rougail.object_model import Calculation
+from rougail.convert.object_model import Calculation
PROPERTIES = (
diff --git a/src/rougail/annotator/value.py b/src/rougail/annotator/value.py
index b61c4b202..2c7f82443 100644
--- a/src/rougail/annotator/value.py
+++ b/src/rougail/annotator/value.py
@@ -29,7 +29,7 @@ from rougail.annotator.variable import Walk
from rougail.i18n import _
from rougail.error import DictConsistencyError
-from rougail.object_model import Calculation
+from rougail.convert.object_model import Calculation
class Annotator(Walk): # pylint: disable=R0903
diff --git a/src/rougail/annotator/variable.py b/src/rougail/annotator/variable.py
index 55d4a5d89..eea7912e3 100644
--- a/src/rougail/annotator/variable.py
+++ b/src/rougail/annotator/variable.py
@@ -29,7 +29,7 @@ from tiramisu.error import display_list
from rougail.i18n import _
from rougail.utils import calc_multi_for_type_variable
from rougail.error import DictConsistencyError
-from rougail.object_model import Calculation, VariableCalculation
+from rougail.convert.object_model import Calculation, VariableCalculation
class Walk:
diff --git a/src/rougail/config.py b/src/rougail/config/__init__.py
similarity index 97%
rename from src/rougail/config.py
rename to src/rougail/config/__init__.py
index f77989abc..fad6c80b5 100644
--- a/src/rougail/config.py
+++ b/src/rougail/config/__init__.py
@@ -26,17 +26,15 @@ You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
"""
-from sys import version_info
-
from pathlib import Path
from tiramisu import Config
from ruamel.yaml import YAML
-from .utils import _, load_modules, normalize_family
-from .convert import RougailConvert
-from .object_model import get_convert_option_types
+from ..utils import _, load_modules
+from ..tiramisu import normalize_family
+from ..convert import RougailConvert
+from ..convert.object_model import get_convert_option_types
-if version_info.major == 3 and version_info.minor:
- import rougail.structural_commandline.object_model
+#import rougail.structural_commandline.object_model
RENAMED = {
"dictionaries_dir": "main_dictionaries",
@@ -53,7 +51,7 @@ def get_sub_modules():
global SUBMODULES
if SUBMODULES is None:
SUBMODULES = {}
- for submodule in Path(__file__).parent.iterdir():
+ for submodule in Path(__file__).parent.parent.iterdir():
if submodule.name.startswith("_") or not submodule.is_dir():
continue
config_file = submodule / "config.py"
@@ -181,6 +179,7 @@ class _RougailConfig:
yield f"{option.path()}: {option.value.get()}"
def __repr__(self):
+ print(self.config)
self.config.property.read_write()
try:
values = "\n".join(self.parse(self.config))
diff --git a/src/rougail/convert.py b/src/rougail/convert.py
deleted file mode 100644
index 2d0509fbe..000000000
--- a/src/rougail/convert.py
+++ /dev/null
@@ -1,1166 +0,0 @@
-"""Takes a bunch of Rougail YAML dispatched in differents folders
-as an input and outputs a Tiramisu's file.
-
-Created by:
-EOLE (http://eole.orion.education.fr)
-Copyright (C) 2005-2018
-
-Forked by:
-Cadoles (http://www.cadoles.com)
-Copyright (C) 2019-2021
-
-Silique (https://www.silique.fr)
-Copyright (C) 2022-2025
-
-This program is free software: you can redistribute it and/or modify it
-under the terms of the GNU Lesser General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this program. If not, see .
-"""
-
-import logging
-from pathlib import Path
-from typing import (
- Any,
- Dict,
- Iterator,
- List,
- Literal,
- Optional,
- Tuple,
- Union,
- get_type_hints,
-)
-
-from ruamel.yaml.comments import CommentedMap
-from pydantic import ValidationError
-
-from warnings import warn
-
-from tiramisu.error import display_list
-
-from .annotator import SpaceAnnotator
-from .i18n import _
-from .object_model import CONVERT_OPTION # Choice,
-from .object_model import (
- PROPERTY_ATTRIBUTE,
- CALCULATION_TYPES,
- CALCULATION_PROPERTY_TYPES,
- PARAM_TYPES,
- AnyParam,
- Calculation,
- Dynamic,
- Family,
- SymLink,
- Variable,
- VariableCalculation,
-)
-from .tiramisureflector import TiramisuReflector
-from .utils import load_modules
-from .path import Paths
-from .error import DictConsistencyError
-
-property_types = Union[Literal[True], Calculation]
-properties_types = Dict[str, property_types]
-
-
-class Property:
- def __init__(self) -> None:
- self._properties: Dict[str, properties_types] = {}
-
- def add(
- self,
- path: str,
- property_: str,
- value: property_types,
- ) -> None:
- self._properties.setdefault(path, {})[property_] = value
-
- def get(self, path: str) -> None:
- return self._properties.setdefault(path, {})
-
- def remove(self, path: str, property_: str) -> None:
- del self._properties[path][property_]
-
- def __getitem__(
- self,
- path: str,
- ) -> properties_types:
- return self._properties.get(path, {})
-
- def __contains__(
- self,
- path: str,
- ) -> bool:
- return path in self._properties
-
-
-information_types = Dict[str, Union[str, int, float, bool]]
-
-
-class Informations:
- def __init__(self) -> None:
- self._data: Dict[str, information_types] = {}
-
- def add(
- self,
- path: str,
- key: str,
- data: Any,
- ) -> None:
- if path not in self._data:
- self._data[path] = {}
- if key in self._data[path]:
- raise Exception(f'an information "{key}" is already present in "{path}"')
- self._data[path][key] = data
-
- def get(
- self,
- path: str,
- ) -> information_types:
- return self._data.get(path, {})
-
-
-class ParserVariable:
- def __init__(self, rougailconfig):
- self.rougailconfig = rougailconfig
- self.load_config()
- self.paths = Paths(None)
- self.families = []
- self.variables = []
- self.parents = {".": []}
- self.index = 0
- self.reflector_names = {}
- self.leaders = []
- self.followers = []
- self.dynamics_variable = {}
- self.multis = {}
- self.default_multi = {}
- self.jinja = {}
- #
- self.convert_options = list(CONVERT_OPTION)
- self.convert_options.extend(self.custom_types)
- #
- self.exclude_imports = []
- self.informations = Informations()
- self.properties = Property()
- # self.choices = Appendable()
- self.has_dyn_option = False
- self.is_init = False
- super().__init__()
-
- def load_config(self) -> None:
- rougailconfig = self.rougailconfig
- self.suffix = rougailconfig["suffix"]
- self.default_dictionary_format_version = rougailconfig[
- "default_dictionary_format_version"
- ]
- self.custom_types = rougailconfig["custom_types"]
- self.functions_files = rougailconfig["functions_files"]
- self.modes_level = rougailconfig["modes_level"]
- if self.modes_level:
- self.default_variable_mode = rougailconfig["default_variable_mode"]
- self.default_family_mode = rougailconfig["default_family_mode"]
- self.extra_annotators = rougailconfig["extra_annotators"]
- 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"
- ]
- self.structurals = rougailconfig["step.structural"]
- try:
- self.user_datas = rougailconfig["step.user_data"]
- except:
- self.user_datas = []
- 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"):
- for option in sub_od:
- if option.owner.isdefault():
- continue
- convert_option = CONVERT_OPTION[sub_od.name()]
- if "initkwargs" not in convert_option:
- convert_option["initkwargs"] = {}
- convert_option["initkwargs"][option.name()] = option.value.get()
-
- def init(self):
- if self.is_init:
- return
- variable = Variable
- family = Family
- root = Path(__file__).parent
- self.walker = None
- for structural_name in self.structurals:
- structural = f"structural_{structural_name}"
- module_path = root / structural / "__init__.py"
- if not module_path.is_file():
- continue
- module = load_modules(f"rougail.{structural}", str(module_path))
- if "Variable" in module.__all__:
- variable = type(
- variable.__name__ + "_" + structural,
- (variable, module.Variable),
- {},
- )
- if "Family" in module.__all__:
- family = type(
- family.__name__ + "_" + structural, (family, module.Family), {}
- )
- if not self.walker and "Walker" in module.__all__:
- self.walker = module.Walker
- self.variable = variable
- self.family = family
- self.dynamic = type(Dynamic.__name__, (Dynamic, family), {})
- hint = get_type_hints(self.dynamic)
- # FIXME: only for format 1.0
- self.family_types = hint["type"].__args__ # pylint: disable=W0201
- self.family_attrs = frozenset( # pylint: disable=W0201
- set(hint) - {"name", "path", "xmlfiles"} | {"redefine", "exists"}
- )
- self.family_calculations = self.search_calculation( # pylint: disable=W0201
- hint
- )
- #
- hint = get_type_hints(self.variable)
- self.variable_types = (
- self.convert_options
- ) # hint["type"].__args__ # pylint: disable=W0201
- #
- self.variable_attrs = frozenset( # pylint: disable=W0201
- set(hint) - {"name", "path", "xmlfiles"} | {"redefine", "exists"}
- )
- self.variable_calculations = self.search_calculation( # pylint: disable=W0201
- hint
- )
- self.is_init = True
-
- ###############################################################################################
- # determine if the object is a family or a variable
- ###############################################################################################
- def is_family_or_variable(
- self,
- path: str,
- obj: dict,
- family_is_leadership: bool,
- filename: str,
- ) -> Literal["variable", "family"]:
- """Check object to determine if it's a variable or a family"""
- # it's already has a variable or a family
- if path in self.paths:
- if path in self.families:
- return "family"
- return "variable"
- # it's: "my_variable:"
- if not obj:
- return "variable"
- # check type attributes
- obj_type = self.get_family_or_variable_type(obj)
- if obj_type:
- if obj_type in self.family_types:
- return "family"
- if obj_type in self.variable_types:
- return "variable"
- msg = _("unknown type {0} for {1}").format(obj_type, path)
- raise DictConsistencyError(msg, 43, [filename])
- # in a leadership there is only variable
- if family_is_leadership:
- return "variable"
- # all attributes are in variable object
- # and values in attributes are not dict is not Calculation
- if isinstance(obj, dict):
- extra_keys = set(obj) - self.variable_attrs
- if not extra_keys:
- for key, value in obj.items():
- if (
- isinstance(value, dict)
- and key != "params"
- and not self.is_calculation(
- key,
- value,
- self.variable_calculations,
- False,
- )
- ):
- break
- else:
- return "variable"
- else:
- if self.version == "1.0":
- msg = f'Invalid value for the variable "{path}": "{obj}"'
- raise DictConsistencyError(msg, 102, [filename])
- return "variable"
- return "family"
-
- def get_family_or_variable_type(
- self,
- obj: dict,
- ) -> Optional[str]:
- """Check 'type' attributes"""
- if not isinstance(obj, dict):
- return None
- if "_type" in obj:
- # only family has _type attributs
- return obj["_type"]
- if "type" in obj and isinstance(obj["type"], str):
- return obj["type"]
- return None
-
- ###############################################################################################
- # create, update or delete family or variable object
- ###############################################################################################
- def family_or_variable(
- self,
- filename: str,
- name: str,
- subpath: str,
- obj: dict,
- comment: Optional[str],
- *,
- first_variable: bool = False,
- family_is_leadership: bool = False,
- family_is_dynamic: bool = False,
- parent_dynamic: Optional[str] = None,
- ) -> None:
- if name.startswith("_"):
- msg = f'the variable or family name "{name}" is incorrect, it must not starts with "_" character'
- raise DictConsistencyError(msg, 16, [filename])
- if not subpath:
- path = name
- else:
- path = f"{subpath}.{name}"
- if self.version == "0.1" and not isinstance(obj, dict) and obj is not None:
- msg = f'the variable "{path}" has a wrong type "{type(obj)}"'
- raise DictConsistencyError(msg, 17, [filename])
- typ = self.is_family_or_variable(
- path,
- obj,
- family_is_leadership,
- filename,
- )
- logging.info("family_or_variable: %s is a %s", path, typ)
- if typ == "family":
- parser = self.parse_family
- else:
- parser = self.parse_variable
- parser(
- filename,
- name,
- path,
- obj,
- comment=comment,
- first_variable=first_variable,
- family_is_leadership=family_is_leadership,
- family_is_dynamic=family_is_dynamic,
- parent_dynamic=parent_dynamic,
- )
-
- def parse_family(
- self,
- filename: str,
- name: str,
- path: str,
- obj: Optional[Dict[str, Any]],
- *,
- comment: Optional[str] = None,
- first_variable: bool = False,
- family_is_leadership: bool = False,
- family_is_dynamic: bool = False,
- parent_dynamic: Optional[str] = None,
- ) -> None:
- """Parse a family"""
- if obj is None:
- obj = {}
- family_obj = {}
- subfamily_obj = {}
- if self.version != "1.0":
- exists = obj.pop("exists", None)
- else:
- exists = None
- force_to_attrs = list(self.list_attributes(obj, filename))
- for key, value in obj.items():
- if key in force_to_attrs:
- if key.startswith("_"):
- key = key[1:]
- family_obj[key] = value
- else:
- subfamily_obj[key] = value
- if self.version != "1.0" and not family_obj and comment:
- family_obj["description"] = comment
-
- if path in self.paths:
- # 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):
- 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,
- obj,
- filename,
- family_is_dynamic,
- typ="family",
- )
- if self.load_unexist_redefine or exists in [None, True]:
- self.paths.add(
- path,
- self.paths[path].model_copy(update=obj),
- family_is_dynamic,
- parent_dynamic,
- force=True,
- )
- self.paths[path].xmlfiles.append(filename)
- force_not_first = True
- if self.paths[path].type == "dynamic":
- family_is_dynamic = True
- parent_dynamic = path
- else:
- redefine = family_obj.pop("redefine", None)
- if not self.load_unexist_redefine and exists is None and redefine:
- raise Exception(
- f'cannot redefine the inexisting family "{path}" in {filename}'
- )
- if not self.load_unexist_redefine and exists is True:
- return
- extra_attrs = set(family_obj) - self.family_attrs
- if extra_attrs:
- raise Exception(f"extra attrs ... {extra_attrs}")
- obj_type = self.get_family_or_variable_type(family_obj)
- if obj_type is None:
- # auto set type
- if "_dynamic" in family_obj:
- dynamic = family_obj["_dynamic"]
- elif "dynamic" in family_obj:
- dynamic = family_obj["dynamic"]
- else:
- dynamic = None
- if isinstance(dynamic, (list, dict)):
- family_obj["type"] = obj_type = "dynamic"
- if obj_type == "dynamic":
- family_is_dynamic = True
- if "{{ identifier }}" not in name:
- if "{{ suffix }}" in name:
- name = name.replace("{{ suffix }}", "{{ identifier }}")
- path = path.replace("{{ suffix }}", "{{ identifier }}")
- elif "variable" in family_obj:
- name += "{{ identifier }}"
- path += "{{ identifier }}"
- else:
- msg = f'dynamic family name must have "{{{{ identifier }}}}" in his name for "{path}"'
- raise DictConsistencyError(msg, 13, [filename])
- parent_dynamic = path
- self.add_family(
- path,
- name,
- family_obj,
- filename,
- family_is_dynamic,
- parent_dynamic,
- )
- force_not_first = False
- if self.paths[path].type == "leadership":
- family_is_leadership = True
- for idx, key in enumerate(subfamily_obj):
- value = subfamily_obj[key]
- first_variable = not force_not_first and idx == 0
- comment = self.get_comment(key, obj)
- self.family_or_variable(
- filename,
- key,
- path,
- value,
- comment,
- first_variable=first_variable,
- family_is_leadership=family_is_leadership,
- family_is_dynamic=family_is_dynamic,
- parent_dynamic=parent_dynamic,
- )
-
- 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("_"):
- # if key starts with _, it's an attribute
- yield key
- # if same key without _ exists, it's a variable!
- true_key = key[1:]
- if true_key in obj:
- force_to_variable.append(true_key)
- continue
- if isinstance(value, dict) and not self.is_calculation(
- key,
- value,
- self.family_calculations,
- False,
- ):
- # it's a dict, so a new variables!
- continue
- # 'variable' for compatibility to format 1.0
- if (
- key == "variable"
- and obj.get("type") != "dynamic"
- and obj.get("_type") != "dynamic"
- ):
- continue
- if key in self.family_attrs:
- yield key
-
- def add_family(
- self,
- path: str,
- name: str,
- family: dict,
- filename: str,
- family_is_dynamic: bool,
- parent_dynamic: str,
- ) -> None:
- """Add a new family"""
- family["path"] = path
- family["namespace"] = self.namespace
- family["version"] = self.version
- family["xmlfiles"] = [filename]
- obj_type = self.get_family_or_variable_type(family)
- if obj_type == "dynamic":
- family_obj = self.dynamic
- if self.version == "1.0":
- if "variable" not in family:
- raise DictConsistencyError(
- f'dynamic family must have "variable" attribute for "{path}"',
- 101,
- family["xmlfiles"],
- )
- if "dynamic" in family:
- raise DictConsistencyError(
- 'variable and dynamic cannot be set together in the dynamic family "{path}"',
- 100,
- family["xmlfiles"],
- )
- family["dynamic"] = {
- "type": "variable",
- "variable": family["variable"],
- "propertyerror": False,
- "allow_none": True,
- }
- del family["variable"]
- # FIXME only for 1.0
- if "variable" in family:
- family["dynamic"] = {
- "type": "variable",
- "variable": family["variable"],
- "propertyerror": False,
- "allow_none": True,
- }
- del family["variable"]
- if self.version != "1.0":
- warning = f'"variable" attribute in dynamic family "{ path }" is depreciated in {filename}'
- warn(warning)
- if "variable" in family:
- raise Exception(
- f'dynamic family must not have "variable" attribute for "{family["path"]}" in {family["xmlfiles"]}'
- )
- else:
- family_obj = self.family
- # convert to Calculation objects
- self.parse_parameters(
- path,
- family,
- filename,
- family_is_dynamic,
- typ="family",
- )
- try:
- self.paths.add(
- path,
- family_obj(name=name, **family),
- family_is_dynamic,
- parent_dynamic,
- )
- except ValidationError as err:
- raise Exception(f'invalid family "{path}" in "{filename}": {err}') from err
- self.set_name(
- self.paths[path],
- "optiondescription_",
- )
- if "." not in path:
- parent = "."
- else:
- parent = path.rsplit(".", 1)[0]
- self.parents[parent].append(path)
- self.parents[path] = []
- self.families.append(path)
-
- def parse_variable(
- self,
- filename: str,
- name: str,
- path: str,
- obj: Optional[Dict[str, Any]],
- *,
- comment: Optional[str] = None,
- first_variable: bool = False,
- family_is_leadership: bool = False,
- family_is_dynamic: bool = False,
- parent_dynamic: Optional[str] = None,
- ) -> None:
- """Parse variable"""
- if self.version == "1.0" or isinstance(obj, dict):
- if obj is None:
- obj = {}
- extra_attrs = set(obj) - self.variable_attrs
- else:
- extra_attrs = []
- obj = {"default": obj}
- if comment:
- obj["description"] = comment
- if extra_attrs:
- raise DictConsistencyError(
- f'"{path}" is not a valid variable, there are additional '
- f'attributes: "{", ".join(extra_attrs)}"',
- 65,
- [filename],
- )
- self.parse_parameters(
- path,
- obj,
- filename,
- family_is_dynamic,
- )
- self.parse_params(path, obj, filename)
- self.parse_secret_manager(path, obj, filename, family_is_dynamic)
- exists = obj.pop("exists", None)
- if path in self.paths:
- 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),
- family_is_dynamic,
- parent_dynamic,
- force=True,
- )
- self.paths[path].xmlfiles.append(filename)
- else:
- if not self.load_unexist_redefine and exists is True:
- # this variable must exist
- # but it's not the case
- # so do nothing
- return
- redefine = obj.pop("redefine", False)
- if not self.load_unexist_redefine and redefine:
- msg = f'cannot redefine the inexisting variable "{path}"'
- raise DictConsistencyError(msg, 46, [filename])
- obj["path"] = path
- self.add_variable(name, obj, filename, family_is_dynamic, parent_dynamic)
- if family_is_leadership:
- if first_variable:
- self.leaders.append(path)
- else:
- self.followers.append(path)
-
- def parse_parameters(
- self,
- path: str,
- obj: dict,
- filename: str,
- family_is_dynamic: bool,
- *,
- typ: str = "variable",
- ):
- """Parse variable or family parameters"""
- if typ == "variable":
- calculations = self.variable_calculations
- else:
- calculations = self.family_calculations
- for key, value in obj.items():
- if self.is_calculation(
- key,
- value,
- calculations,
- False,
- ):
- try:
- self.set_calculation(
- obj,
- key,
- value,
- path,
- family_is_dynamic,
- [filename],
- )
- except ValidationError as err:
- raise Exception(
- f'the {typ} "{path}" in "{filename}" has an invalid "{key}": {err}'
- ) from err
- continue
- if not isinstance(value, list):
- continue
- for idx, val in enumerate(value):
- if not self.is_calculation(
- key,
- val,
- calculations,
- True,
- ):
- continue
- try:
- self.set_calculation(
- obj,
- key,
- val,
- path,
- family_is_dynamic,
- [filename],
- inside_list=True,
- index=idx,
- )
- except ValidationError as err:
- raise Exception(
- f'the {typ} "{path}" in "{filename}" has an invalid "{key}" '
- f"at index {idx}: {err}"
- ) from err
-
- def parse_params(self, path, obj, filename):
- """Parse variable params"""
- 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],
- )
- params = []
- for key, val in obj["params"].items():
- try:
- params.append(
- AnyParam(
- key=key,
- value=val,
- type="any",
- path=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
- ),
- 54,
- [filename],
- ) from err
- obj["params"] = params
-
- def parse_secret_manager(self, path, obj, filename, 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,
- [filename],
- )
-
- def add_variable(
- self,
- name: str,
- variable: dict,
- filename: str,
- family_is_dynamic: bool,
- parent_dynamic: Optional[str],
- ) -> None:
- if "{ suffix" in variable["path"]:
- raise Exception()
- """Add a new variable"""
- if not isinstance(filename, list):
- filename = [filename]
-
- variable["namespace"] = self.namespace
- variable["version"] = self.version
- variable["xmlfiles"] = filename
- variable_type = self.get_family_or_variable_type(variable)
- obj = {
- "symlink": SymLink,
- "choice": self.variable,
- }.get(variable_type, self.variable)
- try:
- variable_obj = obj(name=name, **variable)
- except ValidationError as err:
- raise Exception(
- f'invalid variable "{variable["path"]}" in "{filename}": {err}'
- ) from err
- self.paths.add(
- variable["path"],
- variable_obj,
- family_is_dynamic,
- parent_dynamic,
- )
- self.variables.append(variable["path"])
- if "." in variable["path"]:
- parent_path = variable["path"].rsplit(".", 1)[0]
- else:
- parent_path = "."
- self.parents[parent_path].append(variable["path"])
- self.set_name(
- variable_obj,
- "option_",
- )
-
- def del_family(
- self,
- path: str,
- ) -> None:
- """The family is empty, so delete it"""
- del self.paths[path]
- self.families.remove(path)
- del self.parents[path]
- if "." in path:
- parent = path.rsplit(".", 1)[0]
- else:
- parent = "."
- self.parents[parent].remove(path)
-
- ###############################################################################################
- # set tiramisu file name
- ###############################################################################################
- def set_name(
- self,
- obj: Union[Variable, Family],
- option_prefix: str,
- ):
- """Set Tiramisu object name"""
- self.index += 1
- self.reflector_names[obj.path] = f"{option_prefix}{self.index}{self.suffix}"
-
- ###############################################################################################
- # calculations
- ###############################################################################################
- def is_calculation(
- self,
- attribute: str,
- value: dict,
- calculations: list,
- inside_list: bool,
- ):
- """Check if it's a calculation"""
- if inside_list:
- calculations = calculations[0]
- else:
- 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
- typ = set(CALCULATION_TYPES) & set(value)
- # XXX variable is also set to information
- if typ == {"variable", "information"}:
- typ = {"information"}
- if len(typ) == 1:
- value["type"] = list(typ)[0]
- return True
- return False
-
- def set_calculation(
- self,
- obj: dict,
- attribute: str,
- value: dict,
- path: str,
- family_is_dynamic: bool,
- xmlfiles: List[str],
- *,
- inside_list: bool = False,
- index: int = None,
- ):
- """This variable is a calculation"""
- calculation_object = value.copy()
- typ = calculation_object.pop("type")
-
- calculation_object["attribute_name"] = attribute
- calculation_object["path"] = path
- calculation_object["inside_list"] = inside_list
- calculation_object["version"] = self.version
- calculation_object["namespace"] = self.namespace
- calculation_object["xmlfiles"] = xmlfiles
- #
- if "params" in calculation_object:
- if not isinstance(calculation_object["params"], dict):
- raise Exception("params must be a dict")
- params = []
- for key, val in calculation_object["params"].items():
- if isinstance(val, dict) and "type" not in val:
- # auto set type
- param_typ = set(CALCULATION_TYPES) & set(val)
- # XXX variable is also set to information
- if param_typ == {"variable", "information"}:
- param_typ = {"information"}
- if len(param_typ) == 1:
- val["type"] = list(param_typ)[0]
- if not isinstance(val, dict) or "type" not in val:
- param_typ = "any"
- val = {
- "value": val,
- "type": "any",
- }
- else:
- if self.version == "1.0" and val["type"] == "suffix":
- val["type"] = "identifier"
- param_typ = val["type"]
- val["key"] = key
- val["path"] = path
- val["family_is_dynamic"] = family_is_dynamic
- val["attribute"] = attribute
- val["namespace"] = self.namespace
- 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}',
- 29,
- xmlfiles,
- ) from err
- calculation_object["params"] = params
- #
- return_type = calculation_object.get("return_type")
- if return_type:
- if return_type not in self.variable_types:
- raise Exception(
- f'unknown "return_type" in {attribute} of variable "{path}"'
- )
- #
- if typ == "identifier" and not family_is_dynamic:
- 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)
- else:
- calc = CALCULATION_TYPES[typ](**calculation_object)
- if index is None:
- obj[attribute] = calc
- else:
- obj[attribute][index] = calc
-
-
-class RougailConvert(ParserVariable):
- """Main Rougail conversion"""
-
- supported_version = ["1.0", "1.1"]
-
- def __init__(self, rougailconfig) -> None:
- self.annotator = False
- self.has_namespace = False
- super().__init__(rougailconfig)
-
- def search_calculation(
- self,
- hint: dict,
- ) -> Tuple[List[Any], List[Any]]:
- """attribute is calculated if typing is like: Union[Calculation, xxx]"""
- inside_list = []
- outside_list = []
- for key, value in hint.items():
- if "Union" in value.__class__.__name__ and (
- Calculation in value.__args__ or VariableCalculation in value.__args__
- ):
- outside_list.append(key)
- if (
- "Union" in value.__class__.__name__
- and "_GenericAlias" in value.__args__[0].__class__.__name__
- and Calculation in value.__args__[0].__args__
- ):
- inside_list.append(key)
- if (
- "Union" in value.__class__.__name__
- and value.__args__[0].__class__.__name__ == "_GenericAlias"
- and "Union" in value.__args__[0].__args__[0].__class__.__name__
- and Calculation in value.__args__[0].__args__[0].__args__
- ):
- inside_list.append(key)
- return inside_list, outside_list
-
- def create_namespace(
- self, namespace_description: str, namespace_path: Optional[str] = None
- ) -> None:
- if namespace_path is None:
- namespace_path = self.namespace
- self.version = ""
- self.parse_family(
- "",
- self.namespace,
- namespace_path,
- {
- "description": namespace_description,
- },
- )
-
- def get_comment(
- self,
- name: str,
- objects: CommentedMap,
- ) -> Optional[str]:
- if name in objects.ca.items:
- comment = objects.ca.items[name][2]
- else:
- comment = None
- if comment:
- comment = comment.value[1:].strip()
- return comment
-
- def parse_root_file(
- self,
- filename: str,
- path: str,
- version: str,
- objects: dict,
- ) -> None:
- self.version = version
- for name, obj in objects.items():
- comment = self.get_comment(name, objects)
- self.family_or_variable(
- filename,
- name,
- path,
- obj,
- comment,
- )
-
- def validate_file_version(
- self,
- obj: dict,
- filename: str,
- ) -> None:
- """version is mandatory in YAML file"""
- if obj is None:
- obj = {}
- for name in ["_version", "version"]:
- if name not in obj:
- continue
- version = str(obj.pop(name))
- break
- else:
- # the `version` attribute is not mandatory
- default_version = self.default_dictionary_format_version
- if default_version is not None:
- version = default_version
- else:
- msg = '"version" attribut is mandatory in YAML file'
- raise DictConsistencyError(msg, 27, [filename])
-
- if version not in self.supported_version:
- msg = f'version "{version}" is not supported, list of supported versions: {display_list(self.supported_version, separator="or", add_quote=True)}'
- raise DictConsistencyError(msg, 28, [filename])
- return version
-
- def annotate(
- self,
- ):
- """Apply annotation"""
- if not self.paths.has_value():
- self.init()
- self.parse_directories()
- if self.annotator:
- raise DictConsistencyError(
- _("Cannot execute annotate multiple time"), 85, None
- )
- SpaceAnnotator(self)
- self.annotator = True
-
- def parse_directories(self) -> None:
- if not self.walker:
- msg = _(
- 'invalid "structural" definition ({0}), we cannot load any structural file!'
- ).format(self.structurals)
- raise DictConsistencyError(msg, 51, None)
- self.init()
- self.walker(self)
-
- def reflect(self) -> None:
- """Apply TiramisuReflector"""
- functions_files = [
- func for func in self.functions_files if func not in self.exclude_imports
- ]
- self.reflector = TiramisuReflector(
- self,
- functions_files,
- )
-
- def save(
- self,
- ):
- """Return tiramisu object declaration as a string"""
- self.init()
- self.annotate()
- self.reflect()
- output = self.reflector.get_text() + "\n"
- filename = self.tiramisu_cache
- if filename:
- with open(filename, "w", encoding="utf-8") as tiramisu:
- tiramisu.write(output)
- # print(output)
- return output
diff --git a/src/rougail/convert/__init__.py b/src/rougail/convert/__init__.py
new file mode 100644
index 000000000..9160ddef1
--- /dev/null
+++ b/src/rougail/convert/__init__.py
@@ -0,0 +1,63 @@
+"""Silique (https://www.silique.fr)
+Copyright (C) 2022-2025
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program. If not, see .
+"""
+
+from tiramisu import Config
+from warnings import warn
+
+from .convert import RougailConvert
+from ..config import RougailConfig
+from ..user_datas import UserDatas
+from ..tiramisu import tiramisu_display_name
+
+
+class Rougail(UserDatas):
+ """Main Rougail object"""
+
+ def __init__(
+ self,
+ rougailconfig=None,
+ ) -> None:
+ if rougailconfig is None:
+ rougailconfig = RougailConfig
+ self.rougailconfig = rougailconfig
+ self.converted = RougailConvert(self.rougailconfig)
+ self.config = None
+
+ def run(self):
+ """Get Tiramisu Config"""
+ if not self.config:
+ tiram_obj = self.converted.save()
+ optiondescription = {}
+ custom_types = {
+ custom.__name__: custom
+ for custom in self.rougailconfig["custom_types"].values()
+ }
+ exec(tiram_obj, custom_types, optiondescription) # pylint: disable=W0122
+ self.config = Config(
+ optiondescription["option_0"],
+ display_name=tiramisu_display_name,
+ )
+ self.config.property.read_write()
+ return self.config
+
+ def get_config(self):
+ warn(
+ "get_config is deprecated, use run instead",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.run()
diff --git a/src/rougail/object_model.py b/src/rougail/convert/object_model.py
similarity index 99%
rename from src/rougail/object_model.py
rename to src/rougail/convert/object_model.py
index 98a275039..12bb0d272 100644
--- a/src/rougail/object_model.py
+++ b/src/rougail/convert/object_model.py
@@ -28,10 +28,10 @@ from pydantic import (
)
import tiramisu
from tiramisu.config import get_common_path
-from .utils import get_jinja_variable_to_param, calc_multi_for_type_variable, undefined
-from .i18n import _
-from .error import DictConsistencyError, VariableCalculationDependencyError
-from .tiramisu import CONVERT_OPTION
+from ..utils import get_jinja_variable_to_param, calc_multi_for_type_variable, undefined
+from ..i18n import _
+from ..error import DictConsistencyError, VariableCalculationDependencyError
+from ..tiramisu import CONVERT_OPTION
BASETYPE = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None]
PROPERTY_ATTRIBUTE = ["frozen", "hidden", "disabled", "mandatory"]
diff --git a/src/rougail/path.py b/src/rougail/convert/path.py
similarity index 99%
rename from src/rougail/path.py
rename to src/rougail/convert/path.py
index a8f6d0040..2d29b5baf 100644
--- a/src/rougail/path.py
+++ b/src/rougail/convert/path.py
@@ -24,9 +24,9 @@ from typing import (
import logging
from re import compile, findall
-from .i18n import _
+from ..i18n import _
from .object_model import Family, Variable
-from .utils import normalize_family
+from ..tiramisu import normalize_family
class Paths:
diff --git a/src/rougail/tiramisureflector.py b/src/rougail/convert/tiramisureflector.py
similarity index 99%
rename from src/rougail/tiramisureflector.py
rename to src/rougail/convert/tiramisureflector.py
index 57434c60e..bd5c7e4c2 100644
--- a/src/rougail/tiramisureflector.py
+++ b/src/rougail/convert/tiramisureflector.py
@@ -30,10 +30,10 @@ from typing import Optional, Union
from json import dumps
from os.path import isfile, basename
-from .i18n import _
-from .error import DictConsistencyError, VariableCalculationDependencyError
-from .utils import normalize_family
-from .object_model import Calculation, CONVERT_OPTION
+from ..i18n import _
+from ..error import DictConsistencyError, VariableCalculationDependencyError
+from ..tiramisu import normalize_family, CONVERT_OPTION
+from .object_model import Calculation
class BaseElt: # pylint: disable=R0903
diff --git a/src/rougail/error.py b/src/rougail/error.py
index ab0161a40..f6661482f 100644
--- a/src/rougail/error.py
+++ b/src/rougail/error.py
@@ -26,6 +26,7 @@ along with this program. If not, see .
"""
from .i18n import _
+from .tiramisu import display_xmlfiles
class ConfigError(Exception):
diff --git a/src/rougail/structural_directory/__init__.py b/src/rougail/structural_directory/__init__.py
index 8288f24bc..6b6cd9393 100644
--- a/src/rougail/structural_directory/__init__.py
+++ b/src/rougail/structural_directory/__init__.py
@@ -22,8 +22,8 @@ from pathlib import Path
from ruamel.yaml import YAML
-from ..utils import normalize_family
-from ..path import Paths
+from ..tiramisu import normalize_family
+from ..convert.path import Paths
from ..error import DictConsistencyError
from ..i18n import _
diff --git a/src/rougail/user_datas.py b/src/rougail/user_datas.py
index 2c08222ea..0bd75cac3 100644
--- a/src/rougail/user_datas.py
+++ b/src/rougail/user_datas.py
@@ -22,7 +22,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from typing import List
from re import findall
-from rougail.utils import normalize_family, undefined
from tiramisu import Calculation
from tiramisu.error import (
PropertiesOptionError,
@@ -31,10 +30,11 @@ from tiramisu.error import (
ConfigError,
CancelParam,
)
+from .utils import undefined
+from .tiramisu import normalize_family, CONVERT_OPTION
from .error import DictConsistencyError
from .i18n import _
-from .object_model import CONVERT_OPTION
class UserDatas:
diff --git a/tests/dictionaries/00_2default_calculated_variable_description_multi_line/tiramisu/base.py b/tests/dictionaries/00_2default_calculated_variable_description_multi_line/tiramisu/base.py
new file mode 100644
index 000000000..9226f3b84
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_description_multi_line/tiramisu/base.py
@@ -0,0 +1,17 @@
+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 first variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
+option_3 = StrOption(name="var2", doc="a second variable", default=Calculation(func['calc_value'], Params((ParamOption(option_2)))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
+option_4 = StrOption(name="var3", doc="a new variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
+optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3, option_4], properties=frozenset({"basic"}), informations={'ymlfiles': ['']})
+option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])
diff --git a/tests/dictionaries/00_2default_calculated_variable_description_multi_line/tiramisu/no_namespace.py b/tests/dictionaries/00_2default_calculated_variable_description_multi_line/tiramisu/no_namespace.py
new file mode 100644
index 000000000..1b129710d
--- /dev/null
+++ b/tests/dictionaries/00_2default_calculated_variable_description_multi_line/tiramisu/no_namespace.py
@@ -0,0 +1,12 @@
+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="var1", doc="a first variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
+option_2 = StrOption(name="var2", doc="a second variable", default=Calculation(func['calc_value'], Params((ParamOption(option_1)))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
+option_3 = StrOption(name="var3", doc="a new variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
+option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2, option_3])