diff --git a/src/rougail/path.py b/src/rougail/path.py new file mode 100644 index 000000000..cdfb24e01 --- /dev/null +++ b/src/rougail/path.py @@ -0,0 +1,240 @@ +""" +Copyright (C) 2024 + +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 +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +""" + +from typing import ( + Any, + Dict, + List, + Union, +) +import logging +from re import compile, findall + +from .i18n import _ +from .object_model import Family, Variable +from .utils import normalize_family + + +class Paths: + regexp_relative = compile(r"^_*\.(.*)$") + + def __init__( + self, + default_namespace: str, + ) -> None: + self._data: Dict[str, Union[Variable, Family]] = {} + self._dynamics: Dict[str:str] = {} + if default_namespace is not None: + default_namespace = normalize_family(default_namespace) + self.default_namespace = default_namespace + + def has_value(self) -> bool: + return self._data != {} + + def add( + self, + path: str, + data: Any, + is_dynamic: bool, + dynamic: str, + *, + force: bool = False, + ) -> None: + self._data[path] = data + if not force and is_dynamic: + self._dynamics[path] = dynamic + + def get_full_path( + self, + path: str, + current_path: str, + ): + relative, subpath = path.split(".", 1) + relative_len = len(relative) + path_len = current_path.count(".") + if path_len + 1 == relative_len: + return subpath + parent_path = current_path.rsplit(".", relative_len)[0] + return parent_path + "." + subpath + + def get_with_dynamic( + self, + path: str, + # identifier_path: str, + current_path: str, + version: str, + namespace: str, + xmlfiles: List[str], + ) -> Any: + identifier = None + if version != "1.0" and self.regexp_relative.search(path): + path = self.get_full_path( + path, + current_path, + ) + # elif identifier_path: + # path = f"{identifier_path}.{path}" + dynamic = None + # version 1.0 + if version == "1.0": + if not path in self._data and "{{ suffix }}" not in path: + new_path = None + current_path = None + identifiers = [] + for name in path.split("."): + parent_path = current_path + if current_path: + current_path += "." + name + else: + current_path = name + if current_path in self._data: + if new_path: + new_path += "." + name + else: + new_path = name + continue + for dynamic_path in self._dynamics: + if "." in dynamic_path: + parent_dynamic, name_dynamic = dynamic_path.rsplit(".", 1) + else: + parent_dynamic = None + name_dynamic = dynamic_path + if ( + parent_dynamic == parent_path + and name_dynamic.endswith("{{ identifier }}") + and name == name_dynamic.replace("{{ identifier }}", "") + ): + new_path += "." + name_dynamic + break + regexp = "^" + name_dynamic.replace("{{ identifier }}", "(.*)") + finded = findall(regexp, name) + if len(finded) != 1 or not finded[0]: + continue + if finded[0] == "{{ identifier }}": + identifiers.append(None) + else: + identifiers.append(finded[0]) + if new_path is None: + new_path = name_dynamic + else: + new_path += "." + name_dynamic + parent_path = dynamic_path + break + else: + if new_path: + new_path += "." + name + else: + new_path = name + path = new_path + else: + identifiers = None + if "{{ suffix }}" in path: + path = path.replace("{{ suffix }}", "{{ identifier }}") + elif not path in self._data: + current_path = None + parent_path = None + new_path = current_path + identifiers = [] + for name in path.split("."): + if current_path: + current_path += "." + name + else: + current_path = name + # parent_path, name_path = path.rsplit('.', 1) + if current_path in self._data: + if new_path: + new_path += "." + name + else: + new_path = name + parent_path = current_path + continue + for dynamic_path in self._dynamics: + if "." in dynamic_path: + parent_dynamic, name_dynamic = dynamic_path.rsplit(".", 1) + else: + parent_dynamic = None + name_dynamic = dynamic_path + if ( + "{{ identifier }}" not in name_dynamic + or parent_path != parent_dynamic + ): + continue + regexp = "^" + name_dynamic.replace("{{ identifier }}", "(.*)") + finded = findall(regexp, name) + if len(finded) != 1 or not finded[0]: + continue + if finded[0] == "{{ identifier }}": + identifiers.append(None) + else: + identifiers.append(finded[0]) + if new_path is None: + new_path = name_dynamic + else: + new_path += "." + name_dynamic + parent_path = dynamic_path + break + else: + if new_path: + new_path += "." + name + else: + new_path = name + if "{{ identifier }}" in name: + identifiers.append(None) + parent_path = current_path + path = new_path + else: + identifiers = None + if path not in self._data: + return None, None + option = self._data[path] + option_namespace = option.namespace + if ( + self.default_namespace not in [namespace, option_namespace] + and namespace != option_namespace + ): + msg = _( + 'A variable or a family located in the "{0}" namespace shall not be used in the "{1}" namespace' + ).format(option_namespace, namespace) + raise DictConsistencyError(msg, 38, xmlfiles) + return option, identifiers + + def __getitem__( + self, + path: str, + ) -> Union[Family, Variable]: + if not path in self._data: + raise AttributeError(f"cannot find variable or family {path}") + return self._data[path] + + def __contains__( + self, + path: str, + ) -> bool: + return path in self._data + + def __delitem__( + self, + path: str, + ) -> None: + logging.info("remove empty family %s", path) + del self._data[path] + + def is_dynamic(self, path: str) -> bool: + return path in self._dynamics + + def get(self): + return self._data.values()