fix: better path detection

This commit is contained in:
egarette@silique.fr 2026-04-30 21:49:10 +02:00
parent 5251554b50
commit a1ecc76e25
25 changed files with 328 additions and 36 deletions

View file

@ -1,6 +1,6 @@
""" """
Silique (https://www.silique.fr) Silique (https://www.silique.fr)
Copyright (C) 2024-2025 Copyright (C) 2024-2026
This program is free software: you can redistribute it and/or modify it 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 under the terms of the GNU Lesser General Public License as published by the
@ -119,7 +119,6 @@ class RougailOutputFormatter:
_('the "step.output" is not set to "{0}"').format(self.output_name) _('the "step.output" is not set to "{0}"').format(self.output_name)
) )
# yaml.top_level_colon_align = True # yaml.top_level_colon_align = True
self.main_namespace = normalize_family(self.rougailconfig["main_namespace"])
self.has_default_structural_format_version = ( self.has_default_structural_format_version = (
self.rougailconfig["default_structural_format_version"] is not None self.rougailconfig["default_structural_format_version"] is not None
) )
@ -135,7 +134,7 @@ class RougailOutputFormatter:
def run(self): def run(self):
self.upgrade() self.upgrade()
self.families = {self.main_namespace: CommentedMap()} self.families = {self.rougail.namespace: CommentedMap()}
self.parse() self.parse()
self.yaml.indent(mapping=2, sequence=4, offset=2) self.yaml.indent(mapping=2, sequence=4, offset=2)
self.yaml.version = "1.2" self.yaml.version = "1.2"
@ -143,7 +142,7 @@ class RougailOutputFormatter:
self.yaml.explicit_end = True self.yaml.explicit_end = True
self.default_flow_style = False self.default_flow_style = False
with BytesIO() as ymlfh: with BytesIO() as ymlfh:
families = self.families[self.main_namespace] families = self.families[self.rougail.namespace]
if not families: if not families:
self.yaml.dump("", ymlfh) self.yaml.dump("", ymlfh)
else: else:
@ -162,7 +161,14 @@ class RougailOutputFormatter:
return self.attributes[type_name] return self.attributes[type_name]
def upgrade(self) -> None: def upgrade(self) -> None:
namespace = self.rougailconfig["main_namespace"]
if namespace and self.rougailconfig["extra_namespaces.names"]:
namespace = self.rougailconfig["extra_namespaces.names"][0]
filenames = self.rougailconfig["extra_namespaces"]["directories"][0]
extra = True
else:
filenames = self.rougailconfig["main_structural_directories"] filenames = self.rougailconfig["main_structural_directories"]
extra = False
if len(filenames) > 1: if len(filenames) > 1:
raise ExtensionError(_('only one filename is allowed, not "{0}"').format(filenames)) raise ExtensionError(_('only one filename is allowed, not "{0}"').format(filenames))
filename = Path(filenames[0]) filename = Path(filenames[0])
@ -177,11 +183,11 @@ class RougailOutputFormatter:
self.rougail.load_config() self.rougail.load_config()
self.rougail.init() self.rougail.init()
self.filename_str = str(filename) self.filename_str = str(filename)
if self.main_namespace is None: if namespace is None:
self.rougail.namespace = None self.rougail.namespace = None
else: else:
self.rougail.namespace = normalize_family(self.main_namespace) self.rougail.namespace = normalize_family(namespace)
self.rougail.create_namespace(self.main_namespace) self.rougail.create_namespace(namespace)
self.rougail.validate_file_version( self.rougail.validate_file_version(
datas, datas,
self.filename_str, self.filename_str,
@ -199,26 +205,25 @@ class RougailOutputFormatter:
return ret return ret
def parse(self): def parse(self):
self.families[self.main_namespace][self.version_name] = float( self.families[self.rougail.namespace][self.version_name] = float(
self.rougail.version self.rougail.version
) )
self.remaining = len(self.rougail.paths._data) self.remaining = len(self.rougail.paths._data)
for path, obj in self.rougail.paths._data.items(): for path, obj in self.rougail.paths._data.items():
self.remaining -= 1 self.remaining -= 1
if path == self.rougail.namespace: if path == self.rougail.namespace:
# self.families[path] = self.families[None]
continue continue
if isinstance(obj, Family): if isinstance(obj, Family):
self.parse_family(path, obj) self.parse_family(path, obj)
elif isinstance(obj, Variable): elif isinstance(obj, Variable):
self.parse_variable(path, obj) self.parse_variable(path, obj)
if list(self.families[self.main_namespace]) != [self.version_name]: if list(self.families[self.rougail.namespace]) != [self.version_name]:
# just to add an empty line space after "version" # just to add an empty line space after "version"
self.families[self.main_namespace].yaml_value_comment_extend( self.families[self.rougail.namespace].yaml_value_comment_extend(
self.version_name, [CommentToken("\n\n", CommentMark(0)), None] self.version_name, [CommentToken("\n\n", CommentMark(0)), None]
) )
if self.has_default_structural_format_version: if self.has_default_structural_format_version:
del self.families[self.main_namespace][self.version_name] del self.families[self.rougail.namespace][self.version_name]
def parse_family(self, path, obj): def parse_family(self, path, obj):
children = [p.rsplit(".", 1)[-1] for p in self.rougail.parents[path]] children = [p.rsplit(".", 1)[-1] for p in self.rougail.parents[path]]
@ -355,6 +360,8 @@ class RougailOutputFormatter:
if attr not in force_keys and value == default_value: if attr not in force_keys and value == default_value:
continue continue
value = self.object_to_yaml(attr, type_, value, multi, path) value = self.object_to_yaml(attr, type_, value, multi, path)
if isinstance(value, dict) and "identifier" in value:
value["identifier"] = self.object_to_yaml("identifier", type_, value["identifier"], True, path)
variable[attr] = value variable[attr] = value
if variable.get("mandatory") is True and None not in variable.get( if variable.get("mandatory") is True and None not in variable.get(
"choices", [] "choices", []
@ -605,7 +612,7 @@ class RougailOutputFormatter:
def calc_variable_path(self, object_path, variable_path): def calc_variable_path(self, object_path, variable_path):
if not variable_path.startswith("_"): if not variable_path.startswith("_"):
common_path = get_common_path(object_path, variable_path) common_path = get_common_path(object_path, variable_path)
if not self.rougail.namespace or common_path: if not self.rougail.namespace:
if not common_path: if not common_path:
len_common_path = 0 len_common_path = 0
else: else:
@ -617,6 +624,15 @@ class RougailOutputFormatter:
+ "." + "."
+ variable_path[len_common_path:] + variable_path[len_common_path:]
) )
if common_path:
len_common_path = len(common_path) + 1
relative_object_path = object_path[len_common_path:]
final_path = "_" * (relative_object_path.count(".") + 1) + "."
return (
"_" * (relative_object_path.count(".") + 1)
+ "."
+ variable_path[len_common_path:]
)
return variable_path return variable_path
def get_parent_name(self, path): def get_parent_name(self, path):
@ -636,7 +652,7 @@ class RougailOutputFormatter:
name = search_name name = search_name
return _yaml(y[name]) return _yaml(y[name])
if self.main_namespace: if self.rougail.namespace:
subpath = path.split(".")[1:] subpath = path.split(".")[1:]
else: else:
subpath = path.split(".") subpath = path.split(".")

View file

@ -1,6 +1,6 @@
""" """
Silique (https://www.silique.fr) Silique (https://www.silique.fr)
Copyright (C) 2024-2025 Copyright (C) 2024-2026
This program is free software: you can redistribute it and/or modify it 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 under the terms of the GNU Lesser General Public License as published by the

View file

@ -1,6 +1,6 @@
"""Internationalisation utilities """Internationalisation utilities
Silique (https://www.silique.fr) Silique (https://www.silique.fr)
Copyright (C) 2025 Copyright (C) 2025-2026
This program is free software: you can redistribute it and/or modify it 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 under the terms of the GNU Lesser General Public License as published by the

View file

@ -4,7 +4,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2021 Copyright (C) 2021
Silique (https://www.silique.fr) Silique (https://www.silique.fr)
Copyright (C) 2022-2025 Copyright (C) 2022-2026
This program is free software: you can redistribute it and/or modify it 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 under the terms of the GNU Lesser General Public License as published by the

View file

@ -7,6 +7,6 @@ var1: # a first variable
var2: var2:
description: a second variable description: a second variable
default: default:
variable: _.var1
description: value of a variable! description: value of a variable!
variable: _.var1
... ...

View file

@ -7,12 +7,12 @@ var1: # a first variable
var2: var2:
description: a second variable description: a second variable
default: default:
variable: _.var1
description: |- description: |-
value value
of of
a a
variable! variable!
variable: _.var1
var3: # a new variable var3: # a new variable
... ...

View file

@ -10,7 +10,7 @@ var1:
var2: var2:
description: an IP in CIDR format description: an IP in CIDR format
examples: examples:
- 192.168.0.128/25 - 192.168.0.129/25
type: ip type: ip
params: params:
cidr: true cidr: true

View file

@ -4,6 +4,8 @@ version: 1.1
secret1: secret1:
description: the first variable description: the first variable
examples:
- ALongS4cr4t
type: secret type: secret
params: params:
min_len: 10 min_len: 10

View file

@ -0,0 +1,18 @@
%YAML 1.2
---
version: 1.1
var1:
description: A first variable
type: boolean
default:
jinja: >-
true
description: A description
hidden: true
var2:
description: A second variable
disabled:
variable: _.var1
...

View file

@ -0,0 +1,15 @@
%YAML 1.2
---
version: 1.1
condition:
description: a condition
default: true
disabled: true
variable:
description: a variable
disabled:
variable: _.condition
propertyerror: transitive
...

View file

@ -0,0 +1,17 @@
%YAML 1.2
---
version: 1.1
condition: true # a condition
variable1:
description: a variable
disabled:
variable: _.condition
variable2:
description: a second variable
disabled:
variable: _.variable1
propertyerror: transitive
...

View file

@ -0,0 +1,15 @@
%YAML 1.2
---
version: 1.1
condition:
description: a condition
default: true
disabled: true
variable:
description: a variable
disabled:
variable: _.condition
propertyerror: transitive
...

View file

@ -0,0 +1,19 @@
%YAML 1.2
---
version: 1.1
condition: false # a condition
variable1:
description: a variable
default: disabled
disabled:
variable: _.condition
variable2:
description: a second variable
disabled:
variable: _.variable1
propertyerror: transitive
when: disabled
...

View file

@ -0,0 +1,19 @@
%YAML 1.2
---
version: 1.1
condition: true # a condition
variable1:
description: a variable
default: not_disabled
disabled:
variable: _.condition
variable2:
description: a second variable
disabled:
variable: _.variable1
propertyerror: transitive
when: disabled
...

View file

@ -8,7 +8,7 @@ leadership:
leader: leader:
description: a leader description: a leader
test: examples:
- val1 - val1
- val2 - val2
default: default:

View file

@ -0,0 +1,29 @@
%YAML 1.2
---
version: 1.1
var:
description: a suffix variable
test:
- 1
- 2
type: integer
multi: true
mandatory: false
dyn{{ identifier }}:
description: a dynamic family
dynamic:
variable: _.var
var: val # a variable inside dynamic family from "{{ identifier }}"
var2:
description: a variable
default:
jinja: >-
{% if rougail.dyn1 is defined %}
{{ rougail.dyn1.var }}
{% endif %}
description: get the value of "rougail.dyn1.var"
...

View file

@ -3,21 +3,21 @@
version: 1.1 version: 1.1
dyn{{ identifier }}: dyn{{ identifier }}:
description: A dynamic famify for {{ identifier }} description: a dynamic famify for {{ identifier }}
dynamic: dynamic:
- val1 - val1
- val2 - val2
var: # A dynamic variable for {{ identifier }} var: # a dynamic variable for {{ identifier }}
var1: var1:
description: A new variable description: a new variable
disabled: disabled:
variable: _.dynval1.var variable: _.dynval1.var
when: val when: val
var2: var2:
description: A new variable description: a new variable
default: default:
variable: _.dyn{{ identifier }}.var variable: _.dyn{{ identifier }}.var
unique: false unique: false

View file

@ -0,0 +1,26 @@
%YAML 1.2
---
version: 1.1
var1: # A suffix variable
- val1
- val2
var2: val1 # A suffix variable2
dyn{{ identifier }}:
dynamic:
variable: _.var1
var:
description: A dynamic variable
default:
type: identifier
var3:
description: A variable calculated
default:
variable: _.dyn{{ identifier }}.var
identifier:
variable: _.var2
...

View file

@ -0,0 +1,24 @@
%YAML 1.2
---
version: 1.1
var1: # A suffix variable
- val1
- val2
var2: val1 # A suffix variable2
dyn{{ identifier }}:
dynamic:
variable: _.var1
var: # A dynamic variable
- type: identifier
var3:
description: A variable calculated
default:
variable: _.dyn{{ identifier }}.var
identifier:
variable: _.var2
...

View file

@ -7,7 +7,8 @@ var1:
test: test:
- val1 - val1
- val2 - val2
multi: true default:
- val1
mandatory: false mandatory: false
dyn{{ identifier }}: dyn{{ identifier }}:
@ -22,5 +23,5 @@ var2:
description: A variable calculated description: A variable calculated
default: default:
variable: _.dynval1.var variable: _.dynval1.var
optional: true propertyerror: false
... ...

View file

@ -0,0 +1,26 @@
%YAML 1.2
---
version: 1.1
var1:
description: A suffix variable
test:
- val1
- val2
multi: true
mandatory: false
dyn{{ identifier }}:
dynamic:
variable: _.var1
var:
description: A dynamic variable
disabled: true
var2:
description: A variable calculated
default:
variable: _.dynval1.var
optional: true
...

View file

@ -4,7 +4,7 @@ version: 1.1
var: var:
description: a suffix variable description: a suffix variable
test: examples:
- val1 - val1
- val2 - val2
- val3 - val3

View file

@ -0,0 +1,33 @@
%YAML 1.2
---
version: 1.1
var: # a suffix variable
- val1
- val2
'{{ identifier }}_dyn':
description: a dynamic family
dynamic:
variable: _.var
var1:
description: value is suffix for {{ identifier }}
default:
type: identifier
var2:
description: value is first variable
default:
variable: _.var1
var3:
description: value is relative first variable
default:
variable: _.var1
var4:
description: value is first variable of val1
default:
variable: __.val1_dyn.var1
...

View file

@ -0,0 +1,24 @@
%YAML 1.2
---
version: 1.1
var: # A suffix variable
- val1
- val2
dyn{{ identifier }}:
dynamic:
variable: _.var
dyn{{ identifier }}:
dynamic:
variable: __.var
var1: # A dynamic variable
var2:
description: A variable calculated
default:
variable: ___.dynval2.dyn{{ identifier }}.var1
unique: false
...

View file

@ -68,18 +68,26 @@ def test_structural_files_formatter_namespace(test_dir):
rougailconfig = get_rougail_config(test_dir, namespace) rougailconfig = get_rougail_config(test_dir, namespace)
if not rougailconfig: if not rougailconfig:
return return
if rougailconfig["extra_namespaces.names"]:
for n_idx, namespace in enumerate(rougailconfig['extra_namespaces.names']):
for dir_name in rougailconfig["extra_namespaces"]["directories"][n_idx]:
for file_name in Path(dir_name).iterdir():
if file_name.suffix in [".yml", ".yaml"]:
rougailconfig["extra_namespaces"].set_follower("directories", n_idx, [str(file_name)])
_test_structural_files(file_name, namespace, rougailconfig)
rougailconfig["extra_namespaces"].reset()
for dir_name in rougailconfig['main_structural_directories']: for dir_name in rougailconfig['main_structural_directories']:
for file_name in Path(dir_name).iterdir(): for file_name in Path(dir_name).iterdir():
if file_name.suffix in [".yml", ".yaml"]: if file_name.suffix in [".yml", ".yaml"]:
rougailconfig["main_structural_directories"] = [str(file_name)] rougailconfig["main_structural_directories"] = [str(file_name)]
_test_structural_files(file_name, namespace, rougailconfig) _test_structural_files(file_name, namespace, rougailconfig)
for namespace, dirs_name in rougailconfig['extra_namespaces'].items(): # for namespace, dirs_name in rougailconfig['extra_namespaces'].items():
rougailconfig["main_namespace"] = namespace # rougailconfig["main_namespace"] = namespace
for dir_name in dirs_name: # for dir_name in dirs_name:
for file_name in Path(dir_name).iterdir(): # for file_name in Path(dir_name).iterdir():
if file_name.suffix in [".yml", ".yaml"]: # if file_name.suffix in [".yml", ".yaml"]:
rougailconfig["main_structural_directories"] = [str(file_name)] # rougailconfig["main_structural_directories"] = [str(file_name)]
_test_structural_files(file_name, namespace, rougailconfig) # _test_structural_files(file_name, namespace, rougailconfig)
def test_structural_files_formatter_load(test_dir): def test_structural_files_formatter_load(test_dir):