fix: reorganise user_datas

This commit is contained in:
egarette@silique.fr 2024-12-11 20:54:03 +01:00
parent 8afb787c98
commit 00e0941f6e
12 changed files with 161 additions and 158 deletions

View file

@ -92,7 +92,7 @@ class Annotator(Walk): # pylint: disable=R0903
elif variable.multi: elif variable.multi:
msg = _( msg = _(
'the variable "{0}" is multi but has a non list default value' 'the variable "{0}" is multi but has a non list default value'
).format(variable.name) ).format(variable.path)
raise DictConsistencyError(msg, 12, variable.xmlfiles) raise DictConsistencyError(msg, 12, variable.xmlfiles)
elif variable.path in self.objectspace.followers: elif variable.path in self.objectspace.followers:
self.objectspace.default_multi[variable.path] = variable.default self.objectspace.default_multi[variable.path] = variable.default

1
src/rougail/cli Symbolic link
View file

@ -0,0 +1 @@
../../../rougail-cli/src/rougail/cli

View file

@ -189,6 +189,7 @@ class FakeRougailConvert(RougailConvert):
self.force_optional = False self.force_optional = False
self.plugins = ["structural_commandline"] self.plugins = ["structural_commandline"]
self.user_datas = [] self.user_datas = []
self.output = None
self.add_extra_options = self.add_extra_options self.add_extra_options = self.add_extra_options

View file

@ -395,6 +395,7 @@ class ParserVariable:
"structural_commandline.add_extra_options" "structural_commandline.add_extra_options"
] ]
self.user_datas = rougailconfig["step.user_data"] self.user_datas = rougailconfig["step.user_data"]
self.output = rougailconfig["step.output"]
self.plugins = rougailconfig["plugins"] self.plugins = rougailconfig["plugins"]
def _init(self): def _init(self):

View file

@ -81,6 +81,10 @@ class NotFoundError(Exception):
pass pass
class ExtentionError(Exception):
pass
## ---- specific exceptions ---- ## ---- specific exceptions ----

1
src/rougail/output_ansible Symbolic link
View file

@ -0,0 +1 @@
../../../rougail-output-ansible/src/rougail/output_ansible

1
src/rougail/output_console Symbolic link
View file

@ -0,0 +1 @@
../../../rougail-output-console/src/rougail/output_console

1
src/rougail/output_doc Symbolic link
View file

@ -0,0 +1 @@
../../../rougail-output-doc/src/rougail/output_doc

1
src/rougail/output_json Symbolic link
View file

@ -0,0 +1 @@
../../../rougail-output-json/src/rougail/output_json

View file

@ -0,0 +1 @@
../../../rougail-user-data-environment/src/rougail/user_data_environment

1
src/rougail/user_data_file Symbolic link
View file

@ -0,0 +1 @@
../../../rougail-user-data-file/src/rougail/user_data_file

View file

@ -21,7 +21,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from typing import List from typing import List
from re import findall from re import findall
from tiramisu import undefined from tiramisu import undefined, Calculation
from tiramisu.error import PropertiesOptionError, LeadershipError, ConfigError from tiramisu.error import PropertiesOptionError, LeadershipError, ConfigError
from .object_model import CONVERT_OPTION from .object_model import CONVERT_OPTION
@ -32,44 +32,148 @@ class UserDatas:
self.config = config self.config = config
def user_datas(self, user_datas: List[dict]): def user_datas(self, user_datas: List[dict]):
values = {} self.values = {}
errors = [] self.errors = []
warnings = [] self.warnings = []
self._populate_values(user_datas)
self._auto_configure_dynamics()
self._populate_config()
self._populate_error_warnings()
return {
"errors": self.errors,
"warnings": self.warnings,
}
def _populate_values(self, user_datas):
for datas in user_datas: for datas in user_datas:
options = datas.get("options", {}) options = datas.get("options", {})
for name, data in datas.get("values", {}).items(): for name, data in datas.get("values", {}).items():
values[name] = { self.values[name] = {
"values": data, "values": data,
"options": options.copy(), "options": options.copy(),
} }
errors.extend(datas.get("errors", [])) self.errors.extend(datas.get("errors", []))
warnings.extend(datas.get("warnings", [])) self.warnings.extend(datas.get("warnings", []))
self._auto_configure_dynamics(values)
while values: def _get_variable(self, config):
for subconfig in config:
if subconfig.isoptiondescription():
yield from self._get_variable(subconfig)
else:
yield subconfig
def _auto_configure_dynamics(self):
cache = {}
added = []
for path, data in list(self.values.items()):
value = data["values"]
try:
option = self.config.option(path)
option.name()
except (ConfigError, PropertiesOptionError):
pass
except AttributeError:
self._not_found_is_dynamic(self.config, path, cache, added)
def _not_found_is_dynamic(self, config, path, cache, added):
"""if path is not found, check if parent is a dynamic family
"""
current_path = ""
identifiers = []
# get parent
for name in path.split(".")[:-1]:
if current_path:
current_path += "."
current_path += name
if current_path in cache:
config, identifier = cache[current_path]
identifiers.append(identifier)
continue
tconfig = config.option(name)
try:
tconfig.group_type()
# object exists, so current config is the temporary config
config = tconfig
if config.isdynamic(only_self=True):
identifiers.append(config.identifiers()[-1])
except AttributeError:
# try to found the good dynamic family
for tconfig in config.list(uncalculated=True):
if not tconfig.isdynamic(only_self=True):
# it's not a dynamic variable
continue
identifier = self._get_identifier(
tconfig.name(), name
)
if identifier is None:
# it's a dynamic variable but doesn't match the current name
continue
dynamic_variable = tconfig.information.get(
"dynamic_variable",
None,
)
if not dynamic_variable:
# it's the good dynamic variable but it's not linked to a variable
# so cannot change the variable
continue
option_type = self.config.option(
dynamic_variable
).information.get("type")
dyn_options_values = self.config.option(dynamic_variable).get().impl_getdefault()
if "{{ identifier }}" in dynamic_variable:
for s in identifiers:
dynamic_variable = dynamic_variable.replace(
"{{ identifier }}", str(s), 1
)
if dynamic_variable not in self.values and not dyn_options_values:
self.values[dynamic_variable] = {"values": []}
added.append(dynamic_variable)
elif dynamic_variable not in added:
continue
config = tconfig
identifiers.append(identifier)
typ = CONVERT_OPTION.get(option_type, {}).get(
"func"
)
if typ:
identifier = typ(identifier)
if (
identifier
not in self.values[dynamic_variable]["values"]
):
self.values[dynamic_variable]["values"].append(
identifier
)
cache[current_path] = config, identifier
break
def _populate_config(self):
while self.values:
value_is_set = False value_is_set = False
for option in self._get_variable(self.config): for option in self._get_variable(self.config):
path = option.path() path = option.path()
if path not in values: if path not in self.values:
continue continue
options = values[path].get("options", {}) options = self.values[path].get("options", {})
value = values[path]["values"] value = self.values[path]["values"]
needs_convert = options.get("needs_convert", False)
# converted value # converted value
if option.ismulti(): if option.ismulti():
if options.get("multi_separator") and not isinstance(value, list): if options.get("multi_separator") and not isinstance(value, list):
value = value.split(options["multi_separator"]) value = value.split(options["multi_separator"])
values[path]["values"] = value self.values[path]["values"] = value
if option.issubmulti(): if option.issubmulti():
value = [[val] for val in value] value = [[val] for val in value]
if options.get("needs_convert"): if needs_convert:
if option.issubmulti(): if option.issubmulti():
for idx, val in enumerate(value): for idx, val in enumerate(value):
value[idx] = [convert_value(option, v) for v in val] value[idx] = [convert_value(option, v) for v in val]
else: else:
value = [convert_value(option, val) for val in value] value = [convert_value(option, val) for val in value]
values[path]["values"] = value self.values[path]["values"] = value
values[path]["options"]["needs_convert"] = False self.values[path]["options"]["needs_convert"] = False
elif options.get("needs_convert"): elif needs_convert:
value = convert_value(option, value) value = convert_value(option, value)
index = option.index() index = option.index()
if index is not None: if index is not None:
@ -82,18 +186,29 @@ class UserDatas:
# value is correctly set, remove variable to the set # value is correctly set, remove variable to the set
if index is not None: if index is not None:
# if it's a follower waiting for all followers are sets # if it's a follower waiting for all followers are sets
values[path]["values"][index] = undefined self.values[path]["values"][index] = undefined
if set(values[path]["values"]) == {undefined}: if set(self.values[path]["values"]) == {undefined}:
values.pop(path) self.values.pop(path)
else: else:
values.pop(path) self.values.pop(path)
except Exception: except Exception:
if path != option.path(): if path != option.path():
values[option.path()] = values.pop(path) self.values[option.path()] = self.values.pop(path)
if not value_is_set: if not value_is_set:
break break
def _get_identifier(self, true_name, name) -> str:
if true_name == "{{ identifier }}":
return name
regexp = true_name.replace("{{ identifier }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
return None
return finded[0]
def _populate_error_warnings(self):
# we don't find variable, apply value just to get error or warning messages # we don't find variable, apply value just to get error or warning messages
for path, data in values.items(): for path, data in self.values.items():
try: try:
option = self.config.option(path) option = self.config.option(path)
value = data["values"] value = data["values"]
@ -105,148 +220,23 @@ class UserDatas:
else: else:
option.value.set(value) option.value.set(value)
except AttributeError as err: except AttributeError as err:
errors.append(str(err)) self.errors.append(str(err))
except (ValueError, LeadershipError) as err: except (ValueError, LeadershipError) as err:
errors.append(str(err)) self.errors.append(str(err))
except PropertiesOptionError as err: except PropertiesOptionError as err:
warnings.append(str(err)) self.warnings.append(str(err))
return {
"errors": errors,
"warnings": warnings,
}
def _get_variable(self, config):
for subconfig in config:
if subconfig.isoptiondescription():
yield from self._get_variable(subconfig)
else:
yield subconfig
def _auto_configure_dynamics(
self,
values,
):
cache = {}
added = []
for path, data in list(values.items()):
value = data["values"]
try:
option = self.config.option(path)
option.name()
except (ConfigError, PropertiesOptionError):
pass
except AttributeError:
config = self.config
current_path = ""
for name in path.split(".")[:-1]:
if current_path:
current_path += "."
current_path += name
if current_path in cache:
config, identifier = cache[current_path]
else:
tconfig = config.option(name)
try:
tconfig.group_type()
config = tconfig
except AttributeError:
for tconfig in config.list(uncalculated=True):
if tconfig.isdynamic(only_self=True):
identifier = self._get_identifier(
tconfig.name(), name
)
if identifier is None:
continue
dynamic_variable = tconfig.information.get(
"dynamic_variable",
None,
)
if not dynamic_variable:
continue
option_type = self.config.option(
dynamic_variable
).information.get("type")
identifiers = tconfig.identifiers()
if identifiers:
for s in identifiers:
dynamic_variable = dynamic_variable.replace(
"{{ identifier }}", str(s), 1
)
if dynamic_variable not in values and not self.config.option(dynamic_variable).get().impl_getdefault():
values[dynamic_variable] = {"values": []}
added.append(dynamic_variable)
elif dynamic_variable not in added:
continue
config = tconfig
typ = CONVERT_OPTION.get(option_type, {}).get(
"func"
)
if typ:
identifier = typ(identifier)
if (
identifier
not in values[dynamic_variable]["values"]
):
values[dynamic_variable]["values"].append(
identifier
)
cache[current_path] = config, identifier
break
else:
if option.isdynamic():
parent_option = self.config.option(path.rsplit(".", 1)[0])
identifier = self._get_identifier(
parent_option.name(uncalculated=True),
parent_option.name(),
)
dynamic_variable = None
while True:
dynamic_variable = parent_option.information.get(
"dynamic_variable",
None,
)
if dynamic_variable:
break
parent_option = self.config.option(
parent_option.path().rsplit(".", 1)[0]
)
if "." not in parent_option.path():
parent_option = None
break
if not parent_option:
continue
identifiers = parent_option.identifiers()
for identifier in identifiers:
dynamic_variable = dynamic_variable.replace(
"{{ identifier }}", str(identifier), 1
)
if dynamic_variable not in values and not self.config.option(dynamic_variable).get().impl_getdefault():
values[dynamic_variable] = {"values": []}
added.append(dynamic_variable)
elif dynamic_variable not in added:
continue
option_type = option.information.get("type")
typ = CONVERT_OPTION.get(option_type, {}).get("func")
if typ:
identifier = typ(identifier)
if identifier not in values[dynamic_variable]["values"]:
values[dynamic_variable]["values"].append(identifier)
cache[option.path()] = option, identifier
def _get_identifier(self, true_name, name) -> str:
if true_name == "{{ identifier }}":
return name
regexp = true_name.replace("{{ identifier }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
return None
return finded[0]
def convert_value(option, value): def convert_value(option, value):
if value == "": if value == "":
return None return None
option_type = option.information.get("type") option_type = option.information.get("type")
if option_type == 'choice':
choices = option.value.list()
if value not in choices and isinstance(value, str):
# FIXME add other tests (boolean, float, ...)
if value.isnumeric() and int(value) in choices:
value = int(value)
func = CONVERT_OPTION.get(option_type, {}).get("func") func = CONVERT_OPTION.get(option_type, {}).get("func")
if func: if func:
return func(value) return func(value)