Compare commits

...

4 commits

16 changed files with 308 additions and 29 deletions

View file

@ -1,3 +1,14 @@
## 1.2.0a62 (2026-02-11)
### Feat
- structural data in a string
### Fix
- structurals data in extra could be a simple file
- allow define an addition variable in a type
## 1.2.0a61 (2026-02-06) ## 1.2.0a61 (2026-02-06)
### Feat ### Feat

View file

@ -1,6 +1,6 @@
[project] [project]
name = "rougail" name = "rougail"
version = "1.2.0a61" version = "1.2.0a62"
[tool.commitizen] [tool.commitizen]
name = "cz_conventional_commits" name = "cz_conventional_commits"

View file

@ -4,7 +4,7 @@ requires = ["flit_core >=3.8.0,<4"]
[project] [project]
name = "rougail-base" name = "rougail-base"
version = "1.2.0a61" version = "1.2.0a62"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}] authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
readme = "README.md" readme = "README.md"
description = "A consistency handling system that was initially designed in the configuration management" description = "A consistency handling system that was initially designed in the configuration management"

View file

@ -4,7 +4,7 @@ requires = ["flit_core >=3.8.0,<4"]
[project] [project]
name = "rougail" name = "rougail"
version = "1.2.0a61" version = "1.2.0a62"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}] authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
description = "A consistency handling system that was initially designed in the configuration management" description = "A consistency handling system that was initially designed in the configuration management"
classifiers = [ classifiers = [
@ -18,7 +18,7 @@ classifiers = [
dependencies = [ dependencies = [
"ruamel.yaml ~= 0.18.6", "ruamel.yaml ~= 0.18.6",
"pydantic ~= 2.9.2", "pydantic ~= 2.9.2",
"rougail-base == 1.2.0a61", "rougail-base == 1.2.0a62",
] ]
[tool.flit.sdist] [tool.flit.sdist]

View file

@ -1 +1 @@
__version__ = "1.2.0a61" __version__ = "1.2.0a62"

View file

@ -408,7 +408,7 @@ class ParserVariable:
exists = obj.pop("exists", None) exists = obj.pop("exists", None)
else: else:
exists = None exists = None
redefine = obj.pop("redefine", False) or force_redefine is True redefine = obj.pop("redefine", False) or (force_redefine is True and path in self.paths)
obj_type = self.get_family_or_variable_type(obj) obj_type = self.get_family_or_variable_type(obj)
force_to_attrs = list(self.list_attributes(obj, sources, obj_type)) force_to_attrs = list(self.list_attributes(obj, sources, obj_type))
for key, value in obj.items(): for key, value in obj.items():
@ -699,7 +699,7 @@ class ParserVariable:
self.parse_params(path, obj, sources) self.parse_params(path, obj, sources)
self.parse_secret_manager(path, obj, sources, family_is_dynamic, copy_before_set) self.parse_secret_manager(path, obj, sources, family_is_dynamic, copy_before_set)
exists = obj.pop("exists", None) exists = obj.pop("exists", None)
redefine = obj.pop("redefine", False) or force_redefine is True redefine = obj.pop("redefine", False) or (force_redefine is True and path in self.paths)
if not redefine and not exists: if not redefine and not exists:
obj_type = obj.get("type") obj_type = obj.get("type")
if obj_type in self.custom_variable_types: if obj_type in self.custom_variable_types:

View file

@ -111,7 +111,7 @@ class VariableParam(Param):
variable: str variable: str
propertyerror: bool = True propertyerror: bool = True
whole: bool = False whole: bool = False
dynamic: bool = True # dynamic: bool = True
optional: bool = False optional: bool = False
def to_param( def to_param(
@ -254,6 +254,7 @@ class Calculation(BaseModel):
default_values: Any = None default_values: Any = None
namespace: Optional[str] namespace: Optional[str]
warnings: Optional[bool] = None warnings: Optional[bool] = None
description: Optional[StrictStr] = None
xmlfiles: List[str] xmlfiles: List[str]
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
@ -298,7 +299,6 @@ class JinjaCalculation(Calculation):
jinja: StrictStr jinja: StrictStr
params: Optional[List[Param]] = None params: Optional[List[Param]] = None
return_type: BASETYPE = None return_type: BASETYPE = None
description: Optional[StrictStr] = None
def _jinja_to_function( def _jinja_to_function(
self, self,
@ -719,7 +719,6 @@ class _VariableCalculation(Calculation):
class VariableCalculation(_VariableCalculation): class VariableCalculation(_VariableCalculation):
attribute_name: Literal["default", "choices", "dynamic"] attribute_name: Literal["default", "choices", "dynamic"]
description: Optional[StrictStr] = None
default: Any = undefined default: Any = undefined
def to_function( def to_function(
@ -778,7 +777,6 @@ class VariablePropertyCalculation(_VariableCalculation):
propertyerror: Union[Literal["transitive"], bool] = True propertyerror: Union[Literal["transitive"], bool] = True
when: Any = undefined when: Any = undefined
when_not: Any = undefined when_not: Any = undefined
description: Optional[StrictStr] = None
default: bool = False default: bool = False
def to_function( def to_function(
@ -870,7 +868,6 @@ class InformationCalculation(Calculation):
attribute_name: Literal["default", "choice", "dynamic"] attribute_name: Literal["default", "choice", "dynamic"]
information: StrictStr information: StrictStr
variable: Optional[StrictStr] variable: Optional[StrictStr]
description: Optional[StrictStr] = None
def to_function( def to_function(
self, self,
@ -928,7 +925,6 @@ class _IdentifierCalculation(Calculation):
class IdentifierCalculation(_IdentifierCalculation): class IdentifierCalculation(_IdentifierCalculation):
attribute_name: Literal["default", "choice", "dynamic"] attribute_name: Literal["default", "choice", "dynamic"]
description: Optional[StrictStr] = None
def to_function( def to_function(
self, self,
@ -948,7 +944,6 @@ class IdentifierPropertyCalculation(_IdentifierCalculation):
attribute_name: Literal[*PROPERTY_ATTRIBUTE] attribute_name: Literal[*PROPERTY_ATTRIBUTE]
when: Any = undefined when: Any = undefined
when_not: Any = undefined when_not: Any = undefined
description: Optional[StrictStr] = None
def to_function( def to_function(
self, self,
@ -991,7 +986,6 @@ class IdentifierPropertyCalculation(_IdentifierCalculation):
class IndexCalculation(Calculation): class IndexCalculation(Calculation):
attribute_name: Literal["default", "choice", "dynamic"] attribute_name: Literal["default", "choice", "dynamic"]
description: Optional[StrictStr] = None
def to_function( def to_function(
self, self,
@ -1011,7 +1005,6 @@ class IndexCalculation(Calculation):
class NamespaceCalculation(Calculation): class NamespaceCalculation(Calculation):
attribute_name: Literal["default", "secret_manager"] attribute_name: Literal["default", "secret_manager"]
description: Optional[StrictStr] = None
def to_function( def to_function(
self, self,
@ -1048,19 +1041,19 @@ SECRET_BASETYPE_CALC = Union[StrictStr, JinjaCalculation]
class Family(BaseModel): class Family(BaseModel):
name: str name: str
# informations # informations
description: Optional[str] = None description: Optional[StrictStr] = None
help: Optional[str] = None help: Optional[StrictStr] = None
mode: Optional[str] = None mode: Optional[StrictStr] = None
# validation # validation
type: Literal["family", "leadership", "dynamic"] = "family" type: Literal["family", "leadership", "dynamic"] = "family"
# properties # properties
hidden: Union[bool, Calculation] = False hidden: Union[bool, Calculation] = False
disabled: Union[bool, Calculation] = False disabled: Union[bool, Calculation] = False
# others # others
namespace: Optional[str] namespace: Optional[StrictStr]
path: str path: StrictStr
version: str version: StrictStr
xmlfiles: List[str] = [] xmlfiles: List[StrictStr] = []
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True) model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
@ -1074,9 +1067,9 @@ class Dynamic(Family):
class Variable(BaseModel): class Variable(BaseModel):
name: str name: str
# user informations # user informations
description: Optional[str] = None description: Optional[StrictStr] = None
help: Optional[str] = None help: Optional[StrictStr] = None
mode: Optional[str] = None mode: Optional[StrictStr] = None
tags: Optional[list] = None tags: Optional[list] = None
examples: Optional[list] = None examples: Optional[list] = None
test: Optional[list] = None test: Optional[list] = None
@ -1084,7 +1077,7 @@ class Variable(BaseModel):
## type will be set dynamically in `annotator/value.py`, default is None ## type will be set dynamically in `annotator/value.py`, default is None
type: str = None type: str = None
params: Optional[List[Param]] = None params: Optional[List[Param]] = None
regexp: Optional[str] = None regexp: Optional[StrictStr] = None
choices: Optional[Union[List[BASETYPE_CALC], Calculation]] = None choices: Optional[Union[List[BASETYPE_CALC], Calculation]] = None
multi: Optional[bool] = None multi: Optional[bool] = None
validators: Optional[List[Calculation]] = None validators: Optional[List[Calculation]] = None

View file

@ -96,8 +96,6 @@ extra_namespaces:
params: params:
allow_relative: true allow_relative: true
test_existence: true test_existence: true
types:
- directory
multi: true multi: true
disabled: disabled:
jinja: >- jinja: >-

View file

@ -0,0 +1,115 @@
"""
Silique (https://www.silique.fr)
Copyright (C) 2025-2026
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 <http://www.gnu.org/licenses/>.
"""
from typing import List, Optional
from itertools import chain
from ruamel.yaml import YAML
from ..tiramisu import normalize_family
from ..convert.path import Paths
#from ..error import DictConsistencyError
#from ..i18n import _
class Walker:
def __init__(
self,
convert,
) -> None:
"""Parse directories content"""
self.convert = convert
self.yaml = YAML()
rougailconfig = self.convert.rougailconfig
if rougailconfig["main_namespace"]:
self.convert.has_namespace = True
self.convert.paths = Paths(
rougailconfig["main_namespace"],
rougailconfig["isolated_namespace"],
)
self.load_with_extra(
rougailconfig["extra_namespaces"],
rougailconfig["main_namespace"],
rougailconfig["main_structural_strings"],
)
else:
self.convert.namespace = None
namespace_path = ""
if namespace_path in self.convert.parents:
raise Exception("pfff")
for string in rougailconfig["main_structural_strings"]:
self.parse_variable(
string,
namespace_path,
)
def load_with_extra(
self,
extra_structures: dict,
main_namespace: str,
main_strings: Optional[List[str]] = None,
) -> None:
directory_dict = chain(
(
(
main_namespace,
main_strings,
),
),
extra_structures.items(),
)
for namespace, extra_objects in directory_dict:
namespace_path = normalize_family(namespace)
if namespace_path in self.convert.parents:
raise Exception("pfff")
self.convert.namespace = namespace_path
for idx, string in enumerate(extra_objects):
if not idx:
# create only for the first file
self.convert.create_namespace(namespace, namespace_path)
self.parse_variable(
string,
namespace_path,
)
def parse_variable(
self,
string: str,
path: str,
) -> None:
objects = self.yaml.load(string)
if objects is None:
return
if path:
name = f'yaml file for {path}'
else:
name = 'yaml file'
version = self.convert.validate_file_version(
objects,
name,
)
self.convert.parse_root_file(
[name],
path,
version,
objects,
)
__all__ = ("Walker",)

View file

@ -0,0 +1,69 @@
"""
Silique (https://www.silique.fr)
Copyright (C) 2025-2026
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 <http://www.gnu.org/licenses/>.
"""
from ..utils import _
def get_rougail_config(
*,
backward_compatibility=True,
) -> dict:
if backward_compatibility:
main_namespace_default = "rougail"
else:
main_namespace_default = "null"
options = f"""
main_structural_strings:
description: {_("Structural files contents")}
help: {_("This variable is a list of string with YAML file format")}
multi: true
disabled:
jinja: >-
{{% if 'string' not in _.step.structural %}}
true
{{% elif cli is defined and cli.versions is defined and cli.versions %}}
true
{{% else %}}
false
{{% endif %}}
return_type: boolean
description: {_('string is not in "_.step.structural"')}
extra_namespaces:
strings:
description: {_("Extra structural contents")}
help: {_("This variable is a list of string with YAML file format")}
alternative_name: xc
multi: true
disabled:
jinja: >-
{{{{ 'string' not in __.step.structural }}}}
return_type: boolean
description: {_('string is not in "__.step.structural"')}
"""
return {
"name": "string",
"process": "structural",
"options": options,
"level": 15,
}
__all__ = "get_rougail_config"

View file

@ -81,3 +81,7 @@ def test_type_variable_hidden():
def test_type_dynfamily(): def test_type_dynfamily():
type_variable("family_dynfamily") type_variable("family_dynfamily")
def test_type_family_subfamily_add():
type_variable("family_subfamily_add")

View file

@ -0,0 +1,28 @@
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
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_3 = StrOption(name="a_first_variable", doc="a first variable", default="a modified value", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml', 'tests/types/structures/family_subfamily_add/00_structure.yml'], 'type': 'string'})
option_5 = StrOption(name="a_second_variable", doc="a second variable", default="a modified value", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml', 'tests/types/structures/family_subfamily_add/00_structure.yml'], 'type': 'string'})
optiondescription_4 = OptionDescription(name="a_sub_family", doc="My subfamily", children=[option_5], properties=frozenset({"standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml', 'tests/types/structures/family_subfamily_add/00_structure.yml']})
option_7 = StrOption(name="a_second_variable", doc="a_second_variable", default="a modified value", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['tests/types/structures/family_subfamily_add/00_structure.yml'], 'type': 'string'})
optiondescription_6 = OptionDescription(name="a_new_subfamily", doc="a_new_subfamily", children=[option_7], properties=frozenset({"standard"}), informations={'ymlfiles': ['tests/types/structures/family_subfamily_add/00_structure.yml']})
optiondescription_2 = OptionDescription(name="my_family_1", doc="My family type", children=[option_3, optiondescription_4, optiondescription_6], properties=frozenset({"standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml', 'tests/types/structures/family_subfamily_add/00_structure.yml']})
option_9 = StrOption(name="a_first_variable", doc="a first variable", default="a value", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml'], 'type': 'string'})
option_11 = StrOption(name="a_second_variable", doc="a second variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml'], 'type': 'string'})
optiondescription_10 = OptionDescription(name="a_sub_family", doc="My subfamily", children=[option_11], properties=frozenset({"basic"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml']})
optiondescription_8 = OptionDescription(name="my_family_2", doc="My family type", children=[option_9, optiondescription_10], properties=frozenset({"basic"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml', 'tests/types/structures/family_subfamily_add/00_structure.yml']})
option_13 = StrOption(name="a_first_variable", doc="a first variable", default="a value", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml'], 'type': 'string'})
option_15 = StrOption(name="a_second_variable", doc="a second variable", default="a modified value 2", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml', 'tests/types/structures/family_subfamily_add/00_structure.yml'], 'type': 'string'})
optiondescription_14 = OptionDescription(name="a_sub_family", doc="My subfamily", children=[option_15], properties=frozenset({"standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml', 'tests/types/structures/family_subfamily_add/00_structure.yml']})
option_16 = StrOption(name="a_new_variable", doc="a_new_variable", default="a modified value", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['tests/types/structures/family_subfamily_add/00_structure.yml'], 'type': 'string'})
optiondescription_12 = OptionDescription(name="my_family_3", doc="My family type", children=[option_13, optiondescription_14, option_16], properties=frozenset({"standard"}), informations={'ymlfiles': ['tests/types/types/family_subfamily_add/00_structure.yml', 'tests/types/structures/family_subfamily_add/00_structure.yml']})
optiondescription_1 = OptionDescription(name="rougail", doc="rougail", group_type=groups.namespace, children=[optiondescription_2, optiondescription_8, optiondescription_12], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,10 @@
{
"rougail.my_family_1.a_first_variable": "a modified value",
"rougail.my_family_1.a_sub_family.a_second_variable": "a modified value",
"rougail.my_family_1.a_new_subfamily.a_second_variable": "a modified value",
"rougail.my_family_2.a_first_variable": "a value",
"rougail.my_family_2.a_sub_family.a_second_variable": null,
"rougail.my_family_3.a_first_variable": "a value",
"rougail.my_family_3.a_sub_family.a_second_variable": "a modified value 2",
"rougail.my_family_3.a_new_variable": "a modified value"
}

View file

@ -0,0 +1,10 @@
{
"rougail.my_family_1.a_first_variable": "a modified value",
"rougail.my_family_1.a_sub_family.a_second_variable": "a modified value",
"rougail.my_family_1.a_new_subfamily.a_second_variable": "a modified value",
"rougail.my_family_2.a_first_variable": "a value",
"rougail.my_family_2.a_sub_family.a_second_variable": null,
"rougail.my_family_3.a_first_variable": "a value",
"rougail.my_family_3.a_sub_family.a_second_variable": "a modified value 2",
"rougail.my_family_3.a_new_variable": "a modified value"
}

View file

@ -0,0 +1,29 @@
%YAML 1.2
---
version: 1.1
my_family_1:
type: my_family_type
a_first_variable: a modified value
a_sub_family:
a_second_variable: a modified value
a_new_subfamily:
a_second_variable: a modified value
my_family_2:
type: my_family_type
my_family_3:
type: my_family_type
a_new_variable: a modified value
a_sub_family:
a_second_variable: a modified value 2
...

View file

@ -0,0 +1,12 @@
%YAML 1.2
---
version: 1.1
my_family_type: # My family type
a_first_variable: a value # a first variable
a_sub_family: # My subfamily
a_second_variable: # a second variable
...