feat: leadership => sequence

This commit is contained in:
egarette@silique.fr 2026-06-10 21:02:20 +02:00
parent 2f9f263ae0
commit 41caf6967e
9 changed files with 87 additions and 75 deletions

View file

@ -64,7 +64,7 @@ class Annotator(Walk):
self.objectspace = objectspace
self.mode_auto = []
self.remove_empty_families()
self.check_leadership()
self.check_sequence()
self.families_description()
self.set_modes()
self.convert_help()
@ -76,15 +76,17 @@ class Annotator(Walk):
if not self.objectspace.parents[path]:
self.objectspace.del_family(path)
def check_leadership(self) -> None:
"""No subfamily in a leadership"""
def check_sequence(self) -> None:
"""No subfamily in a sequence"""
for family in self.get_families():
if family.type != "leadership":
if family.type == 'leadership':
family.type = "sequence"
if family.type != "sequence":
continue
for variable_path in self.objectspace.parents[family.path]:
if variable_path in self.objectspace.families:
variable = self.objectspace.paths[variable_path]
msg = f'the leadership "{family.path}" cannot have the "{variable.type}" "{variable.path}"'
msg = f'the sequence "{family.path}" cannot have the "{variable.type}" "{variable.path}"'
raise DictConsistencyError(msg, 24, variable.xmlfiles)
def families_description(self) -> None:
@ -191,14 +193,14 @@ class Annotator(Walk):
family_mode = family.mode
else:
family_mode = None
is_leadership = family.type == "leadership"
if is_leadership:
is_sequence = family.type == "sequence"
if is_sequence:
leader = None
for child_path in self.objectspace.parents[family.path]:
child = self.objectspace.paths[child_path]
if child.type == "symlink":
continue
if is_leadership and leader is None:
if is_sequence and leader is None:
leader = child
leader_mode = leader.mode
if child_path in self.objectspace.families:
@ -208,11 +210,11 @@ class Annotator(Walk):
else:
# set default mode to a variable
self.valid_mode(child)
if is_leadership and leader:
if is_sequence and leader:
self.set_default_mode_leader(leader, child)
self.set_default_mode_variable(child, family_mode)
if is_leadership and leader_mode is not None:
# here because follower can change leadership mode
if is_sequence and leader_mode is not None:
# here because follower can change sequence mode
self.set_auto_mode(family, leader_mode)
def has_mode(self, obj) -> bool:
@ -296,7 +298,7 @@ class Annotator(Walk):
else:
family_mode = self.default_family_mode
min_variable_mode = self.higher_mode
is_leadership = family.type == "leadership"
is_sequence = family.type == "sequence"
for child_path in self.objectspace.parents[family.path]:
child = self.objectspace.paths[child_path]
if child.type == "symlink":
@ -305,7 +307,7 @@ class Annotator(Walk):
if not child.mode:
child.mode = self.default_family_mode
else:
self.change_variable_mode(child, family_mode, is_leadership)
self.change_variable_mode(child, family_mode, is_sequence)
if self.modes[min_variable_mode] > self.modes[child.mode]:
min_variable_mode = child.mode
if not family.mode:

View file

@ -56,11 +56,11 @@ class Annotator(Walk):
return
self.frozen = {}
self.variables_default_transitive = []
self.convert_leadership()
self.convert_sequence()
self.convert_family()
self.convert_variable()
def convert_leadership(self) -> None:
def convert_sequence(self) -> None:
for variable_path in self.objectspace.leaders:
variable = self.objectspace.paths[variable_path]
if variable.hidden:

View file

@ -155,7 +155,7 @@ class _RougailConfig:
return getattr(self, key)
option = self.config.option(key)
if option.isoptiondescription() and option.isleadership():
return self.get_leadership(option)
return self.get_sequence(option)
ret = self.config.option(key).value.get()
return ret
@ -169,7 +169,7 @@ class _RougailConfig:
return False
return True
def get_leadership(self, option) -> dict:
def get_sequence(self, option) -> dict:
leader = None
followers = []
for opt, value in option.value.get().items():

View file

@ -53,17 +53,17 @@ class CollectFamily:
elif "variable" in self.parameters:
self.name += "{{ identifier }}"
self.path += "{{ identifier }}"
else:
elif self.raises:
msg = f'dynamic family name must have "{{{{ identifier }}}}" in his name for "{self.path}"'
raise DictConsistencyError(msg, 13, self.sources)
if self.version == "1.0":
if "variable" not in self.parameters:
if self.raises and "variable" not in self.parameters:
raise DictConsistencyError(
f'dynamic family must have "variable" attribute for "{self.path}"',
101,
self.sources,
)
if "dynamic" in self.parameters:
if self.raises and "dynamic" in self.parameters:
raise DictConsistencyError(
'variable and dynamic cannot be set together in the dynamic family "{self.path}"',
100,
@ -107,7 +107,7 @@ class CollectFamily:
# when an attribute starts with "_", the variable name without this "_" is a variable
sub_variables = [key[1:] for key in self.parameters if key.startswith('_') and key[1:] in self.parameters]
# and known variables
if self.path in self.objectspace.paths:
if self.test_exists and self.path in self.objectspace.paths:
sub_variables.extend([p.rsplit('.', 1)[-1] for p in self.objectspace.parents[self.path]])
attributes = self.object["attrs"]
attributes_types = self.object["attributes_types"]
@ -116,7 +116,7 @@ class CollectFamily:
else:
types_children = []
for key, value in list(children.items()):
if not isinstance(key, str):
if self.raises and not isinstance(key, str):
raise DictConsistencyError(
f'the key "{key}" is not in string format for family {self.path}',
103,
@ -152,7 +152,7 @@ class CollectVariable:
self.parameters = obj
self.parse_type_params()
self.parse_secret_manager()
if not self.check_no_extra_keys:
if self.raises and not self.check_no_extra_keys:
extra_attrs = set(obj) - self.object["attrs"]
if extra_attrs:
raise DictConsistencyError(
@ -174,14 +174,14 @@ class CollectVariable:
if isinstance(params, list):
# the conversion has already be done (in types purpose)
for param in params:
if not isinstance(param, AnyParam):
if self.raises and not isinstance(param, AnyParam):
raise DictConsistencyError(
_("params must be a dict for {0}").format(self.path),
55,
self.sources,
)
return
elif not isinstance(params, dict):
elif self.raises and not isinstance(params, dict):
raise DictConsistencyError(
_("params must be a dict for {0}").format(self.path),
55,
@ -204,13 +204,14 @@ class CollectVariable:
)
)
except ValidationError as err:
raise DictConsistencyError(
_('"{0}" has an invalid "params" for {1}: {2}').format(
key, self.path, err
),
54,
self.sources,
) from err
if self.raises:
raise DictConsistencyError(
_('"{0}" has an invalid "params" for {1}: {2}').format(
key, self.path, err
),
54,
self.sources,
) from err
self.parameters["params"] = new_params
def parse_secret_manager(self):
@ -232,7 +233,7 @@ class CollectType:
1/ self.option_type must be "variable" or "family" option type
needs to determinate if the option is a variable or a family
an option is a family if:
- user specified a know family type: family, leadership (user must set it explicitly) or dynamic)
- user specified a know family type: family, sequence (user must set it explicitly) or dynamic)
- has a family custom type
- has "dynamic" attribute (a variable could not has dynamic attribut)
- it's already a family (so we are certainly in redefine purpose
@ -247,7 +248,7 @@ class CollectType:
else:
self.short_hand_variable()
self.set_user_redefined()
self.variable_in_leadership()
self.variable_in_sequence()
self.family_or_variable()
if self.user_type:
logging.info("family_or_variable: %s is a %s (%s)", self.path, self.option_type, self.user_type)
@ -280,13 +281,13 @@ class CollectType:
self.set_object_user_type_family()
if not self.object:
self.set_object_user_type_variable()
if not self.object:
if self.raises and not self.object:
msg = _('cannot determine the type of the {0} "{1}"').format(self.user_type, self.path)
raise DictConsistencyError(msg, 43, self.sources)
break
def set_custom_type(self, custom: dict) -> None:
if self.path in self.objectspace.paths:
if self.raises and self.test_exists and self.path in self.objectspace.paths:
msg = f'cannot redefine "{self.path}" object to a custom type'
raise DictConsistencyError(msg, 64, self.sources)
self.types = custom
@ -324,12 +325,12 @@ class CollectType:
break
def set_user_redefined(self) -> None:
if self.path not in self.objectspace.paths:
if not self.test_exists or self.path not in self.objectspace.paths:
return
# it's already a variable or a family
old_option_type = self.option_type if self.user_type else None
self.option_type = "family" if self.path in self.objectspace.families else "variable"
if old_option_type and self.option_type != old_option_type:
if self.raises and old_option_type and self.option_type != old_option_type:
msg = f'the {old_option_type} "{self.path}" is redefine as a {self.option_type}, which is not allowed'
raise DictConsistencyError(msg, 11, self.sources)
if self.user_type:
@ -357,11 +358,11 @@ class CollectType:
self.option_type = "variable"
self.object = self.objectspace.variable_objects[0]
def variable_in_leadership(self):
# in a leadership there have only variables
if not self.option_type and self.family_is_leadership:
def variable_in_sequence(self):
# in a sequence there have only variables
if not self.option_type and self.family_is_sequence:
self.option_type = "variable"
if not self.find_variable_object():
if self.raises and not self.find_variable_object():
msg = f'"{self.path}" seems to be a family, which is not allowed in a family'
raise DictConsistencyError(msg, 17, self.sources)
@ -434,6 +435,9 @@ class Collect(CollectType, CollectFamily, CollectVariable):
parameters: dict,
comment: Optional[str],
parent_option: Optional[Collect],
*,
raises: bool=True,
test_exists: bool=True,
) -> None:
self.sources_types = None
self.types = None
@ -442,7 +446,9 @@ class Collect(CollectType, CollectFamily, CollectVariable):
path = name
else:
path = f"{subpath}.{name}"
if name.startswith("_"):
self.raises = raises
self.test_exists = test_exists
if self.raises and name.startswith("_"):
msg = f'the variable or family "{self.path}" is incorrect, it must not starts with "_" character'
raise DictConsistencyError(msg, 16, obj["sources"])
self.path = path
@ -457,15 +463,15 @@ class Collect(CollectType, CollectFamily, CollectVariable):
if parent_option is not None and parent_option.types is not None and self.name in parent_option.types.children:
self.set_custom_type(parent_option.types.children[self.name])
self.check_no_extra_keys = False
if path in objectspace.paths:
if self.test_exists and path in objectspace.paths:
for source in reversed(self.objectspace.paths[path].xmlfiles):
self.sources.insert(0, source)
if parent_option:
self.family_is_leadership = parent_option.user_type == "leadership"
self.family_is_sequence = parent_option.user_type in ["leadership", "sequence"]
self.family_is_dynamic = parent_option.family_is_dynamic
self.parent_dynamic = parent_option.parent_dynamic
else:
self.family_is_leadership = False
self.family_is_sequence = False
self.family_is_dynamic = False
self.parent_dynamic = None
self.version = self.objectspace.version
@ -497,11 +503,12 @@ class Collect(CollectType, CollectFamily, CollectVariable):
value,
)
except ValidationError as err:
raise DictConsistencyError(
_('the {0} "{1}" has an invalid "{2}": {3}').format(self.option_type, self.path, key, err),
84,
self.sources,
) from err
if self.raises:
raise DictConsistencyError(
_('the {0} "{1}" has an invalid "{2}": {3}').format(self.option_type, self.path, key, err),
84,
self.sources,
) from err
continue
if not isinstance(value, list):
continue
@ -601,7 +608,7 @@ class Collect(CollectType, CollectFamily, CollectVariable):
f'unknown "return_type" in {attribute} of variable "{self.path}"'
)
#
if typ == "identifier" and not self.family_is_dynamic:
if self.raises and typ == "identifier" and not self.family_is_dynamic:
msg = f'identifier calculation for "{attribute}" in "{self.path}" cannot be set variable is not in dynamic family'
raise DictConsistencyError(msg, 53, self.sources)
if attribute in PROPERTY_ATTRIBUTE:
@ -648,7 +655,7 @@ class Collect(CollectType, CollectFamily, CollectVariable):
value["attribute"] = attribute
value["namespace"] = self.objectspace.namespace
value["xmlfiles"] = self.sources
if param_typ not in PARAM_TYPES:
if self.raises and param_typ not in PARAM_TYPES:
raise DictConsistencyError(
f'unknown type "{param_typ}" for "{self.path}"',
52,
@ -657,11 +664,12 @@ class Collect(CollectType, CollectFamily, CollectVariable):
try:
params.append(PARAM_TYPES[param_typ](**value))
except ValidationError as err:
raise DictConsistencyError(
f'"{attribute}" has an invalid "{key}" for "{self.path}": {err}',
29,
self.sources,
) from err
if self.raises:
raise DictConsistencyError(
f'"{attribute}" has an invalid "{key}" for "{self.path}": {err}',
29,
self.sources,
) from err
calculation_object["params"] = params
def set_param_type(self, val):

View file

@ -203,8 +203,8 @@ class ParserVariable:
return
root = Path(__file__).parent.parent
self.walker = None
variable = Variable
family = Family
self.variable = Variable
self.family = Family
for structural_name in self.structurals:
structural = f"structural_{structural_name}"
module_path = root / structural / "__init__.py"
@ -212,26 +212,26 @@ class ParserVariable:
continue
module = load_modules(f"rougail.{structural}", str(module_path))
if "Variable" in module.__all__:
variable = type(
variable.__name__ + "_" + structural,
(variable, module.Variable),
self.variable = type(
self.variable.__name__ + "_" + structural,
(self.variable, module.Variable),
{},
)
if "Family" in module.__all__:
family = type(
family.__name__ + "_" + structural, (family, module.Family), {}
self.family = type(
self.family.__name__ + "_" + structural, (self.family, module.Family), {}
)
if not self.walker and "Walker" in module.__all__:
self.walker = module.Walker
dynamic = type(Dynamic.__name__, (Dynamic, family), {})
choices = type(Choices.__name__, (Choices, variable), {})
regexp = type(Regexp.__name__, (Regexp, variable), {})
self.dynamic = type(Dynamic.__name__, (Dynamic, self.family), {})
self.choices = type(Choices.__name__, (Choices, self.variable), {})
self.regexp = type(Regexp.__name__, (Regexp, self.variable), {})
variable_types = self.convert_options.copy()
variable_types.remove("choice")
variable_types.remove("regexp")
variable_types.remove("symlink")
self.variable_objects = [self.get_variable_object(obj, is_variable=True) for obj in [(variable, variable_types), SymLink, choices, regexp]]
self.family_objects = [self.get_variable_object(obj, is_variable=False) for obj in [dynamic, family]]
self.variable_objects = [self.get_variable_object(obj, is_variable=True) for obj in [(self.variable, variable_types), SymLink, self.choices, self.regexp]]
self.family_objects = [self.get_variable_object(obj, is_variable=False) for obj in [self.dynamic, self.family]]
self.is_init = True
def get_variable_object(self, obj, *, is_variable: bool) -> dict:
@ -520,7 +520,7 @@ class ParserVariable:
variable_obj,
"option_",
)
if option.family_is_leadership:
if option.family_is_sequence:
if first_variable:
self.leaders.append(path)
else:

View file

@ -1059,7 +1059,7 @@ class Family(BaseModel):
help: Optional[StrictStr] = None
mode: Optional[StrictStr] = None
# validation
type: Literal["family", "leadership"] = "family"
type: Literal["family", "leadership", "sequence"] = "family"
# properties
hidden: Union[bool, Calculation] = False
disabled: Union[bool, Calculation] = False

View file

@ -540,7 +540,7 @@ class Family(Common):
if self.elt.type == "dynamic":
self.tiramisu.objectspace.has_dyn_option = True
self.object_type = "ConvertDynOptionDescription"
elif self.elt.type == "leadership":
elif self.elt.type == "sequence":
self.object_type = "Leadership"
else:
self.object_type = "OptionDescription"

View file

@ -76,7 +76,7 @@ isolated_namespace:
extra_namespaces:
description: {_("Extra namespaces")}
type: leadership
type: sequence
disabled:
jinja: >-
{{{{ ('directory' not in _.step.structural and 'string' not in _.step.structural) or not _.main_namespace }}}}

View file

@ -218,7 +218,9 @@ CONVERT_OPTION = {
"symlink": dict(opttype="SymLinkOption"),
}
# only version 1.1
RENAME_TYPE = {"number": "integer"}
RENAME_TYPE = {"number": "integer",
"leadership": "sequence",
}
def get_identifier_from_dynamic_family(true_name, name) -> str: