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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -76,7 +76,7 @@ isolated_namespace:
extra_namespaces: extra_namespaces:
description: {_("Extra namespaces")} description: {_("Extra namespaces")}
type: leadership type: sequence
disabled: disabled:
jinja: >- jinja: >-
{{{{ ('directory' not in _.step.structural and 'string' not in _.step.structural) or not _.main_namespace }}}} {{{{ ('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"), "symlink": dict(opttype="SymLinkOption"),
} }
# only version 1.1 # only version 1.1
RENAME_TYPE = {"number": "integer"} RENAME_TYPE = {"number": "integer",
"leadership": "sequence",
}
def get_identifier_from_dynamic_family(true_name, name) -> str: def get_identifier_from_dynamic_family(true_name, name) -> str: