From 41caf6967e043396999aab08cf3fb7bc49ba2a38 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 10 Jun 2026 21:02:20 +0200 Subject: [PATCH] feat: leadership => sequence --- src/rougail/annotator/family.py | 28 +++---- src/rougail/annotator/property.py | 4 +- src/rougail/config/__init__.py | 4 +- src/rougail/convert/collect.py | 90 ++++++++++++---------- src/rougail/convert/convert.py | 26 +++---- src/rougail/convert/object_model.py | 2 +- src/rougail/convert/tiramisureflector.py | 2 +- src/rougail/structural_directory/config.py | 2 +- src/rougail/tiramisu.py | 4 +- 9 files changed, 87 insertions(+), 75 deletions(-) diff --git a/src/rougail/annotator/family.py b/src/rougail/annotator/family.py index d9421d0da..bd29ee433 100644 --- a/src/rougail/annotator/family.py +++ b/src/rougail/annotator/family.py @@ -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: diff --git a/src/rougail/annotator/property.py b/src/rougail/annotator/property.py index 605c4a5d5..9bd449791 100644 --- a/src/rougail/annotator/property.py +++ b/src/rougail/annotator/property.py @@ -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: diff --git a/src/rougail/config/__init__.py b/src/rougail/config/__init__.py index 1869ca044..d0105cbd6 100644 --- a/src/rougail/config/__init__.py +++ b/src/rougail/config/__init__.py @@ -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(): diff --git a/src/rougail/convert/collect.py b/src/rougail/convert/collect.py index b6a682e1e..91e3f0826 100644 --- a/src/rougail/convert/collect.py +++ b/src/rougail/convert/collect.py @@ -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): diff --git a/src/rougail/convert/convert.py b/src/rougail/convert/convert.py index 89dfe7c3d..a5f7d83f1 100644 --- a/src/rougail/convert/convert.py +++ b/src/rougail/convert/convert.py @@ -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: diff --git a/src/rougail/convert/object_model.py b/src/rougail/convert/object_model.py index 0b38e95d8..7a5e62f6a 100644 --- a/src/rougail/convert/object_model.py +++ b/src/rougail/convert/object_model.py @@ -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 diff --git a/src/rougail/convert/tiramisureflector.py b/src/rougail/convert/tiramisureflector.py index e7941b153..ebf0b5e56 100644 --- a/src/rougail/convert/tiramisureflector.py +++ b/src/rougail/convert/tiramisureflector.py @@ -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" diff --git a/src/rougail/structural_directory/config.py b/src/rougail/structural_directory/config.py index b088e502e..b471ec366 100644 --- a/src/rougail/structural_directory/config.py +++ b/src/rougail/structural_directory/config.py @@ -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 }}}} diff --git a/src/rougail/tiramisu.py b/src/rougail/tiramisu.py index 6de836a1a..12feb728f 100644 --- a/src/rougail/tiramisu.py +++ b/src/rougail/tiramisu.py @@ -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: