mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2026-04-07 05:12:19 +00:00
- Replace requirements.txt with pyproject.toml for modern Python packaging - Add unit, integration, lint and security test suites under tests/ - Add utils/export_runtime_requirements.py and utils/check_hadolint_sarif.py - Split monolithic CI into reusable lint.yml, security.yml and tests.yml - Refactor ci.yml to orchestrate reusable workflows; publish on semver tag only - Modernize Dockerfile: pin python:3.12-slim, install via pyproject.toml - Expand Makefile with lint, security, test and CI targets - Add test-e2e via act with portfolio container stop/start around run - Fix navbar_logo_visibility.spec.js: win.fullscreen() → win.enterFullscreen() - Set use_reloader=False in app.run() to prevent double-start in CI - Add app/core.* and build artifacts to .gitignore - Fix apt-get → sudo apt-get in tests.yml e2e job - Fix pip install --ignore-installed to handle stale act cache Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
155 lines
6.3 KiB
Python
155 lines
6.3 KiB
Python
class ConfigurationResolver:
|
|
"""
|
|
A class to resolve `link` entries in a nested configuration structure.
|
|
Supports navigation through dictionaries, lists, and `children`.
|
|
"""
|
|
|
|
def __init__(self, config):
|
|
self.config = config
|
|
|
|
def resolve_links(self):
|
|
"""
|
|
Resolves all `link` entries in the configuration.
|
|
"""
|
|
self._recursive_resolve(self.config, self.config)
|
|
|
|
def _replace_in_list_by_list(self, list_origine, old_element, new_elements):
|
|
index = list_origine.index(old_element)
|
|
list_origine[index : index + 1] = new_elements
|
|
|
|
def _replace_element_in_list(self, list_origine, old_element, new_element):
|
|
index = list_origine.index(old_element)
|
|
list_origine[index] = new_element
|
|
|
|
def _recursive_resolve(self, current_config, root_config):
|
|
"""
|
|
Recursively resolves `link` entries in the configuration.
|
|
"""
|
|
if isinstance(current_config, dict):
|
|
for key, value in list(current_config.items()):
|
|
if key == "children":
|
|
if value is None or not isinstance(value, list):
|
|
raise ValueError(
|
|
"Expected 'children' to be a list, but got "
|
|
f"{type(value).__name__} instead."
|
|
)
|
|
for item in value:
|
|
if "link" in item:
|
|
loaded_link = self._find_entry(
|
|
root_config,
|
|
self._mapped_key(item["link"]),
|
|
False,
|
|
)
|
|
if isinstance(loaded_link, list):
|
|
self._replace_in_list_by_list(value, item, loaded_link)
|
|
else:
|
|
self._replace_element_in_list(value, item, loaded_link)
|
|
else:
|
|
self._recursive_resolve(value, root_config)
|
|
elif key == "link":
|
|
try:
|
|
loaded = self._find_entry(
|
|
root_config, self._mapped_key(value), False
|
|
)
|
|
if isinstance(loaded, list) and len(loaded) > 2:
|
|
loaded = self._find_entry(
|
|
root_config, self._mapped_key(value), False
|
|
)
|
|
current_config.clear()
|
|
current_config.update(loaded)
|
|
except Exception as e:
|
|
raise ValueError(
|
|
f"Error resolving link '{value}': {str(e)}. "
|
|
f"Current part: {key}, Current config: {current_config}"
|
|
+ (
|
|
f", Loaded: {loaded}"
|
|
if "loaded" in locals() or "loaded" in globals()
|
|
else ""
|
|
)
|
|
)
|
|
else:
|
|
self._recursive_resolve(value, root_config)
|
|
elif isinstance(current_config, list):
|
|
for item in current_config:
|
|
self._recursive_resolve(item, root_config)
|
|
|
|
def _get_children(self, current):
|
|
if isinstance(current, dict) and (
|
|
"children" in current and current["children"]
|
|
):
|
|
current = current["children"]
|
|
return current
|
|
|
|
def _mapped_key(self, name):
|
|
return name.replace(" ", "").lower()
|
|
|
|
def _find_by_name(self, current, part):
|
|
return next(
|
|
(
|
|
item
|
|
for item in current
|
|
if isinstance(item, dict)
|
|
and self._mapped_key(item.get("name", "")) == part
|
|
),
|
|
None,
|
|
)
|
|
|
|
def _find_entry(self, config, path, children):
|
|
"""
|
|
Finds an entry in the configuration by a dot-separated path.
|
|
Supports both dictionaries and lists with `children` navigation.
|
|
"""
|
|
parts = path.split(".")
|
|
current = config
|
|
for part in parts:
|
|
if isinstance(current, list):
|
|
if part != "children":
|
|
found = self._find_by_name(current, part)
|
|
if found:
|
|
current = found
|
|
print(
|
|
f"Matching entry for '{part}' in list. Path so far: "
|
|
f"{' > '.join(parts[: parts.index(part) + 1])}. "
|
|
f"Current list: {current}"
|
|
)
|
|
else:
|
|
raise ValueError(
|
|
f"No matching entry for '{part}' in list. Path so far: "
|
|
f"{' > '.join(parts[: parts.index(part) + 1])}. "
|
|
f"Current list: {current}"
|
|
)
|
|
elif isinstance(current, dict):
|
|
key = next((k for k in current if self._mapped_key(k) == part), None)
|
|
if key is None:
|
|
if "children" not in current:
|
|
raise KeyError(
|
|
"No 'children' found in current dictionary. Path so far: "
|
|
f"{' > '.join(parts[: parts.index(part) + 1])}. "
|
|
f"Current dictionary: {current}"
|
|
)
|
|
current = self._find_by_name(current["children"], part)
|
|
|
|
if not current:
|
|
raise KeyError(
|
|
f"Key '{part}' not found in dictionary. Path so far: "
|
|
f"{' > '.join(parts[: parts.index(part) + 1])}. "
|
|
f"Current dictionary: {current}"
|
|
)
|
|
else:
|
|
current = current[key]
|
|
else:
|
|
raise ValueError(
|
|
f"Invalid path segment '{part}'. Current type: {type(current)}. "
|
|
f"Path so far: {' > '.join(parts[: parts.index(part) + 1])}"
|
|
)
|
|
if children:
|
|
current = self._get_children(current)
|
|
|
|
return current
|
|
|
|
def get_config(self):
|
|
"""
|
|
Returns the resolved configuration.
|
|
"""
|
|
return self.config
|