Compare commits

..

No commits in common. "3acf7d36a4a9a9f4ac9c1291f910ba3a04220f36" and "00e0096f8a22d5021502cf7c332d319354c1f2eb" have entirely different histories.

6 changed files with 433 additions and 498 deletions

View File

@ -1,172 +1,137 @@
--- ---
accounts: accounts:
name: Online Accounts name: Accounts
description: Discover my online presence. description: My Online Accounts
icon: icon:
class: fa-solid fa-users class: fa-solid fa-users
children: subitems:
- name: Channels - name: Publications
description: Platforms where I share content. description: My Publications
icon: icon:
class: fas fa-newspaper class: fas fa-newspaper
children: subitems:
- name: Microblogs - name: Microblog
description: Stay updated with my microblog posts. description: Read my microblogs
icon:
class: fa-solid fa-pen-nib
children:
- name: Mastodon
description: Follow my updates on Mastodon.
icon: icon:
class: fa-brands fa-mastodon class: fa-brands fa-mastodon
url: https://microblog.veen.world/@kevinveenbirkenbach url: https://microblog.veen.world/@kevinveenbirkenbach
identifier: "@kevinveenbirkenbach@microblog.veen.world"
- name: Twitter
description: Follow me on Twitter (limited use).
icon:
class: fa-brands fa-twitter
url: https://s.veen.world/twitter
identifier: kevinbirkenbach
warning: I rarely use X/Twitter and recommend alternative platforms like Mastodon.
alternatives:
- link: accounts.channels.microblogs.mastodon
- name: Bluesky
description: Follow me on Bluesky (coming soon).
icon:
class: fa-brands fa-bluesky
alternatives:
- link: accounts.channels.microblogs.mastodon
- name: Pictures - name: Pictures
description: View my photography.
icon: icon:
class: fa-solid fa-images class: fa-solid fa-images
children: subitems:
- name: Pixelfed - name: Pixelfed
description: Explore my photo gallery on Pixelfed. description: View my photo gallery
icon: icon:
class: fa-solid fa-camera class: fa-solid fa-camera
url: https://s.veen.world/pictures url: https://s.veen.world/pictures
- name: Instagram - name: Instagram
description: Follow me on Instagram. description: Follow me on Instagram
icon: icon:
class: fa-brands fa-instagram class: fa-brands fa-instagram
url: https://www.instagram.com/kevinveenbirkenbach/ url: https://www.instagram.com/kevinveenbirkenbach/
identifier: kevinveenbirkenbach identifier: kevinveenbirkenbach
warning: Platforms by Meta (e.g., Instagram, Facebook) may compromise your data privacy. Consider using decentralized alternatives. warning: Using software and platforms from the Meta corporation (e.g., Facebook, Instagram, WhatsApp) may compromise your data privacy and digital freedom due to centralized control, extensive data collection practices, and inconsistent moderation policies. These platforms often fail to adequately address harmful content, misinformation, and abuse.
alternatives:
- link: accounts.channels.pictures.pixelfed
- name: Videos - name: Videos
description: Watch my video content. description: Watch my videos
icon:
class: fa-solid fa-video
children:
- name: Peertube
description: Discover my videos on Peertube.
icon: icon:
class: fa-solid fa-video class: fa-solid fa-video
url: https://s.veen.world/videos url: https://s.veen.world/videos
- name: YouTube
description: Follow me on YouTube (inactive).
icon:
class: fa-brands fa-youtube
url: https://s.veen.world/youtube
warning: I no longer publish videos on YouTube. Please visit my Peertube channel instead.
alternatives:
- link: accounts.channels.videos.peertube
- name: Blog - name: Blog
description: Read my articles and stories. description: Read my blog
icon: icon:
class: fa-solid fa-blog class: fa-solid fa-blog
url: https://blog.veen.world url: https://blog.veen.world
- name: Code - name: Code
description: Access my coding projects.
icon: icon:
class: fa-solid fa-laptop-code class: fa-solid fa-laptop-code
children: description: Check out my Code
- name: GitHub subitems:
description: View my GitHub repositories. - name: Github
description: View my GitHub profile
icon: icon:
class: bi bi-github class: bi bi-github
url: https://github.com/kevinveenbirkenbach url: https://github.com/kevinveenbirkenbach
- name: Gitea - name: Gitea
description: Explore my self-hosted repositories. description: Explore my code repositories
icon: icon:
class: fa-solid fa-code class: fa-solid fa-code
url: https://git.veen.world/kevinveenbirkenbach url: https://git.veen.world/kevinveenbirkenbach
- name: Social Media - name: Social Media
description: Social and developer platforms. description: Social and developer networks
icon: icon:
class: fa-brands fa-meta class: fa-brands fa-meta
children: url:
subitems:
- name: Facebook - name: Facebook
description: Visit my Facebook page. description: Like my Facebook page
icon: icon:
class: fa-brands fa-facebook class: fa-brands fa-facebook
url: https://www.facebook.com/kevinveenbirkenbach url: https://www.facebook.com/kevinveenbirkenbach
- link: navigation.header.contact.messenger - link: navigation.header.contact.messenger
- name: Carreer Profiles
- name: Career Profiles
description: Professional networking profiles.
icon: icon:
class: fa-solid fa-user-tie class: fa-solid fa-user-tie
children: subitems:
- name: XING - name: XING
description: View my XING profile. description: Visit my XING profile
icon: icon:
class: bi bi-building class: bi bi-building
url: https://www.xing.com/profile/Kevin_VeenBirkenbach url: https://www.xing.com/profile/Kevin_VeenBirkenbach
- name: LinkedIn - name: LinkedIn
description: Connect with me on LinkedIn. description: Connect on LinkedIn
icon: icon:
class: bi bi-linkedin class: bi bi-linkedin
url: https://www.linkedin.com/in/kevinveenbirkenbach url: https://www.linkedin.com/in/kevinveenbirkenbach
- name: Sports - name: Sports
description: My sports activities and logs. description: My sport activities
icon: icon:
class: fa-solid fa-running class: fa-solid fa-running
children: url:
subitems:
- name: Garmin - name: Garmin
description: Explore my Garmin activity records. description: My Garmin activities
icon: icon:
class: fa-solid fa-person-running class: fa-solid fa-person-running
url: https://s.veen.world/garmin url: https://s.veen.world/garmin
- name: Eversports - name: Eversports
description: View my Eversports sessions. description: My Eversports sessions
icon: icon:
class: fa-solid fa-dumbbell class: fa-solid fa-dumbbell
url: https://s.veen.world/eversports url: https://s.veen.world/eversports
- name: Duolingo - name: Duolingo
description: Join me in language learning. description: Learn with me on Duolingo
icon: icon:
class: fa-solid fa-language class: fa-solid fa-language
url: https://www.duolingo.com/profile/kevinbirkenbach url: https://www.duolingo.com/profile/kevinbirkenbach
- name: Spotify - name: Spotify
description: Listen to my playlists. description: Listen to my playlists
icon: icon:
class: fa-brands fa-spotify class: fa-brands fa-spotify
url: https://open.spotify.com/user/31vebfzbjf3p7oualis76qfpr5ty url: https://open.spotify.com/user/31vebfzbjf3p7oualis76qfpr5ty
- name: Patreon - name: Patreon
description: Support my work on Patreon. description: Support me on Patreon
icon: icon:
class: fa-brands fa-patreon class: fa-brands fa-patreon
url: https://patreon.com/kevinveenbirkenbach url: https://patreon.com/kevinveenbirkenbach
- name: Discourse - name: Discourse
description: Join discussions on my forum. description: Follow me on Discourse
icon: icon:
class: fa-brands fa-discourse class: fa-brands fa-discourse
url: https://forum.veen.world/u/kevinveenbirkenbach url: https://forum.veen.world/u/kevinveenbirkenbach
cards: cards:
- icon: - icon:
source: https://cloud.veen.world/s/logo_agile_coach_512x512/download source: https://cloud.veen.world/s/logo_agile_coach_512x512/download
@ -286,18 +251,16 @@ company:
imprint_url: https://s.veen.world/imprint imprint_url: https://s.veen.world/imprint
navigation: navigation:
header: header:
children:
- link: accounts.channels.children
- name: Contact - name: Contact
description: Get in touch description: Get in touch
icon: icon:
class: fa-solid fa-envelope class: fa-solid fa-envelope
children: subitems:
- name: Email - name: Email
description: Send me an email description: Send me an email
icon: icon:
class: fa-solid fa-envelope class: fa-solid fa-envelope
children: subitems:
- name: Email - name: Email
description: Send me an email description: Send me an email
icon: icon:
@ -335,7 +298,7 @@ navigation:
description: Social and developer networks description: Social and developer networks
icon: icon:
class: fa-solid fa-comments class: fa-solid fa-comments
children: subitems:
- name: Matrix - name: Matrix
description: Chat with me on Matrix description: Chat with me on Matrix
icon: icon:
@ -382,20 +345,20 @@ navigation:
- link: navigation.header.contact.messenger.matrix - link: navigation.header.contact.messenger.matrix
- link: navigation.header.contact.messenger.signal - link: navigation.header.contact.messenger.signal
- link: navigation.header.contact.messenger.telegram - link: navigation.header.contact.messenger.telegram
footer: footer:
children:
- link: accounts - link: accounts
- name: Solution Hub - name: Solution Hub
description: Curated collection of self hosted tools description: Curated collection of self hosted tools
icon: icon:
class: fa-solid fa-network-wired class: fa-solid fa-network-wired
url: url:
children: subitems:
- name: Community - name: Community
description: Tools to manage the community description: Tools to manage the community
icon: icon:
class: fa-solid fa-users class: fa-solid fa-users
children: subitems:
- name: Forum - name: Forum
description: Join the discussion description: Join the discussion
icon: icon:
@ -415,7 +378,7 @@ navigation:
description: Project Management Tools description: Project Management Tools
icon: icon:
class: fa-solid fa-chart-line class: fa-solid fa-chart-line
children: subitems:
- name: Open Project - name: Open Project
description: Explore my projects description: Explore my projects
icon: icon:
@ -431,7 +394,7 @@ navigation:
- name: Communication - name: Communication
icon: icon:
class: fa-solid fa-comments class: fa-solid fa-comments
children: subitems:
- name: Elements - name: Elements
description: Chat with me description: Chat with me
icon: icon:
@ -452,7 +415,7 @@ navigation:
- name: Tools - name: Tools
icon: icon:
class: fas fa-tools class: fas fa-tools
children: subitems:
- name: Matomo - name: Matomo
description: Analyze with Matomo description: Analyze with Matomo
icon: icon:
@ -480,12 +443,12 @@ navigation:
description: All information about me description: All information about me
icon: icon:
class: fa-solid fa-user class: fa-solid fa-user
children: subitems:
- name: Logbooks - name: Logbooks
description: Access my personal logbooks (diving, flying, sailing) description: Access my personal logbooks (diving, flying, sailing)
icon: icon:
class: fa-solid fa-book class: fa-solid fa-book
children: subitems:
- name: Skydiver - name: Skydiver
description: View my skydiving logs description: View my skydiving logs
icon: icon:
@ -520,21 +483,11 @@ navigation:
icon: icon:
class: fa-solid fa-file-lines class: fa-solid fa-file-lines
url: https://s.veen.world/lebenslauf url: https://s.veen.world/lebenslauf
- name: Languages
icon:
class: fa-solid fa-language
children:
- link: accounts.duolingo
- name: Languages Credentials
description: Check out which languages I speak
url: https://s.veen.world/languages
icon:
class: fa-solid fa-language
- name: Credentials - name: Credentials
description: Access my certifications, degrees, and professional records description: Access my certifications, degrees, and professional records
icon: icon:
class: fa-solid fa-id-card class: fa-solid fa-id-card
children: subitems:
- name: Degrees - name: Degrees
description: View my academic degrees description: View my academic degrees
icon: icon:
@ -550,15 +503,10 @@ navigation:
icon: icon:
class: fa-solid fa-scroll class: fa-solid fa-scroll
url: https://s.veen.world/certifications url: https://s.veen.world/certifications
- name: Skill Matrix
description: Checkout my skills
icon:
class: fa-solid fa-layer-group
url: https://s.veen.world/skillmatrix
- link: accounts - link: accounts
- name: Imprint - name: Imprint
description: Check out the imprint information description: Check out the imprint information
icon: icon:
class: fa-solid fa-scale-balanced class: fa-solid fa-scale-balanced
url: https://s.veen.world/imprint url: https://s.veen.world/imprint

View File

@ -23,17 +23,13 @@ a {
flex: 1; flex: 1;
} }
/* Subtle shadow effect */
.navbar, .card, .dropdown-menu{
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
/* Card styles */ /* Card styles */
.navbar, .card { .navbar, .card {
flex: 1; /* Ensures cards fill the height of their container */ flex: 1; /* Ensures cards fill the height of their container */
border-radius: 5px; /* Rounded corners */ border-radius: 5px; /* Rounded corners */
border: 1px solid #ccc; /* Optional border color */ border: 1px solid #ccc; /* Optional border color */
padding: 10px; /* Inner spacing */ padding: 10px; /* Inner spacing */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); /* Subtle shadow effect */
color: #000000 !important; color: #000000 !important;
background-color: #f9f9f9; background-color: #f9f9f9;
} }

View File

@ -3,13 +3,34 @@
display: none; display: none;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
width: max-content !important; /* Passt die Breite an das breiteste Item an */ transition: opacity 0.3s ease, visibility 0.3s ease;
box-sizing: border-box; /* Berücksichtigt Innenabstand und Rahmen */ }
/* Dropdown-Menü beim Hover anzeigen */
.nav-item.dropdown:hover > .dropdown-menu,
.dropdown-submenu:hover > .dropdown-menu {
display: block;
opacity: 1;
visibility: visible;
}
/* Dropdown-Menü bei der Klasse "open" anzeigen */
.nav-item.dropdown.open > .dropdown-menu,
.dropdown-submenu.open > .dropdown-menu {
display: block;
opacity: 1;
visibility: visible;
} }
/* Positionierung von Submenüs */ /* Positionierung von Submenüs */
.dropdown-submenu > .dropdown-menu { .dropdown-submenu > .dropdown-menu {
position: absolute; position: absolute;
transition: opacity 0.3s ease, visibility 0.3s ease;
top: 0; top: 0;
left: 100%; /* Rechts ausklappen */
}
.dropdown-submenu.open > .dropdown-menu {
display: block;
opacity: 1;
visibility: visible;
} }

View File

@ -2,47 +2,57 @@ document.addEventListener('DOMContentLoaded', () => {
const menuItems = document.querySelectorAll('.nav-item.dropdown'); const menuItems = document.querySelectorAll('.nav-item.dropdown');
const subMenuItems = document.querySelectorAll('.dropdown-submenu'); const subMenuItems = document.querySelectorAll('.dropdown-submenu');
function addMenuEventListeners(items, isTopLevel) { menuItems.forEach(item => {
items.forEach(item => {
let timeout; let timeout;
function onMouseEnter() {
clearTimeout(timeout);
openMenu(item, isTopLevel);
}
function onMouseLeave() {
timeout = setTimeout(() => {
closeMenu(item);
}, 500);
}
// Öffnen beim Hovern // Öffnen beim Hovern
item.addEventListener('mouseenter', onMouseEnter); item.addEventListener('mouseenter', () => {
clearTimeout(timeout);
openMenu(item, true);
});
// Verzögertes Schließen beim Verlassen // Verzögertes Schließen beim Verlassen
item.addEventListener('mouseleave', onMouseLeave); item.addEventListener('mouseleave', () => {
timeout = setTimeout(() => closeMenu(item), 500);
});
// Öffnen und Position anpassen beim Klicken // Öffnen und Position anpassen beim Klicken
item.addEventListener('click', (e) => { item.addEventListener('click', (e) => {
e.preventDefault(); // Verhindert die Standardaktion
e.stopPropagation(); // Verhindert das Schließen von Menüs bei Klick e.stopPropagation(); // Verhindert das Schließen von Menüs bei Klick
if (item.classList.contains('open')) { if (item.classList.contains('open')) {
closeMenu(item); closeMenu(item);
} else { } else {
openMenu(item, isTopLevel); openMenu(item, true);
} }
}); });
}); });
}
function addAllMenuEventListeners() { subMenuItems.forEach(item => {
const updatedMenuItems = document.querySelectorAll('.nav-item.dropdown'); let timeout;
const updatedSubMenuItems = document.querySelectorAll('.dropdown-submenu');
addMenuEventListeners(updatedMenuItems, true);
addMenuEventListeners(updatedSubMenuItems, false);
}
addAllMenuEventListeners(); // Öffnen beim Hovern
item.addEventListener('mouseenter', () => {
clearTimeout(timeout);
openMenu(item, false);
});
// Verzögertes Schließen beim Verlassen
item.addEventListener('mouseleave', () => {
timeout = setTimeout(() => closeMenu(item), 500);
});
// Öffnen und Position anpassen beim Klicken
item.addEventListener('click', (e) => {
e.preventDefault(); // Verhindert die Standardaktion
e.stopPropagation(); // Verhindert das Schließen von Menüs bei Klick
if (item.classList.contains('open')) {
closeMenu(item);
} else {
openMenu(item, false);
}
});
});
// Globale Klick-Listener, um Menüs zu schließen, wenn außerhalb geklickt wird // Globale Klick-Listener, um Menüs zu schließen, wenn außerhalb geklickt wird
document.addEventListener('click', () => { document.addEventListener('click', () => {
@ -53,10 +63,10 @@ document.addEventListener('DOMContentLoaded', () => {
item.classList.add('open'); item.classList.add('open');
const submenu = item.querySelector('.dropdown-menu'); const submenu = item.querySelector('.dropdown-menu');
if (submenu) { if (submenu) {
adjustMenuPosition(submenu, item, isTopLevel);
submenu.style.display = 'block'; submenu.style.display = 'block';
submenu.style.opacity = '1'; submenu.style.opacity = '1';
submenu.style.visibility = 'visible'; submenu.style.visibility = 'visible';
adjustMenuPosition(submenu, item, isTopLevel);
} }
} }
@ -89,7 +99,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (isTopLevel) { if (isTopLevel) {
// Top-Level-Menüs öffnen nur nach oben oder unten // Top-Level-Menüs öffnen nur nach oben oder unten
if (spaceBelow < rect.height && spaceAbove > rect.height) { if (spaceBelow < rect.height && spaceAbove > rect.height) {
submenu.style.bottom = `${window.innerHeight - parentRect.bottom - parentRect.height}px`; submenu.style.bottom = '100%';
submenu.style.top = 'auto'; submenu.style.top = 'auto';
} else { } else {
submenu.style.top = `${parentRect.height}px`; submenu.style.top = `${parentRect.height}px`;
@ -101,14 +111,9 @@ document.addEventListener('DOMContentLoaded', () => {
submenu.style.left = prefersRight ? '100%' : 'auto'; submenu.style.left = prefersRight ? '100%' : 'auto';
submenu.style.right = prefersRight ? 'auto' : '100%'; submenu.style.right = prefersRight ? 'auto' : '100%';
// Öffnen nach oben, wenn unten kein Platz ist const prefersBelow = spaceBelow >= spaceAbove;
if (spaceBelow < rect.height && spaceAbove > rect.height) { submenu.style.top = prefersBelow ? '0' : 'auto';
submenu.style.top = 'auto'; submenu.style.bottom = prefersBelow ? 'auto' : '100%';
submenu.style.bottom = `${parentRect.bottom - parentRect.top - rect.height}px`; // Höhe des Submenüs wird berücksichtigt
} else {
submenu.style.top = '0';
submenu.style.bottom = 'auto';
}
} }
} }
}); });

View File

@ -1,33 +1,33 @@
{% macro render_icon_and_name(item) %} <!-- Template for Subitems -->
<i class="{{ item.icon.class if item.icon is defined and item.icon.class is defined else 'fa-solid fa-link' }}"></i> {% macro render_subitems(subitems) %}
{% if item.name is defined %} {% for subitem in subitems %}
{{ item.name }} {% if subitem.subitems %}
{% else %}
Unnamed Item: {{item}}
{% endif %}
{% endmacro %}
<!-- Template for children -->
{% macro render_children(children) %}
{% for children in children %}
{% if children.children %}
<li class="dropdown-submenu position-relative"> <li class="dropdown-submenu position-relative">
<a class="dropdown-item dropdown-toggle" title="{{ children.description }}"> <a class="dropdown-item dropdown-toggle" href="#" title="{{ subitem.description }}">
{{ render_icon_and_name(children) }} {% if subitem.icon is defined and subitem.icon.class is defined %}
<i class="{{ subitem.icon.class }}"></i> {{ subitem.name }}
{% else %}
<p>Missing icon in subitem: {{ subitem }}</p>
{% endif %}
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{ render_children(children.children) }} {{ render_subitems(subitem.subitems) }}
</ul> </ul>
</li> </li>
{% elif children.identifier or children.warning or children.info %} {% elif subitem.identifier or subitem.warning or subitem.info %}
<li> <li>
<a class="dropdown-item" onclick='openDynamicPopup({{ children|tojson|safe }})' data-bs-toggle="tooltip" title="{{ children.description }}"> <a class="dropdown-item" onclick='openDynamicPopup({{ subitem|tojson|safe }})' data-bs-toggle="tooltip" title="{{ subitem.description }}">
{{ render_icon_and_name(children) }} <i class="{{ subitem.icon.class }}"></i> {{ subitem.name }}
</a> </a>
</li> </li>
{% else %} {% else %}
<li> <li>
<a class="dropdown-item" href="{{ children.url }}" target="{{ children.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ children.description }}"> <a class="dropdown-item" href="{{ subitem.url }}" target="{{ subitem.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ subitem.description }}">
{{ render_icon_and_name(children) }} {% if subitem.icon is defined and subitem.icon.class is defined %}
<i class="{{ subitem.icon.class }}"></i> {{ subitem.name }}
{% else %}
<p>Missing icon in subitem: {{ subitem }}</p>
{% endif %}
</a> </a>
</li> </li>
{% endif %} {% endif %}
@ -42,26 +42,26 @@
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav{{menu_type}}"> <div class="collapse navbar-collapse" id="navbarNav{{menu_type}}">
<ul class="navbar-nav {% if menu_type == 'header' %}ms-auto{% endif %}"> <ul class="navbar-nav {% if menu_type == 'header' %}ms-auto{% endif %}">
{% for item in navigation[menu_type].children %} {% for item in navigation[menu_type] %}
{% if item.url %} {% if item.url %}
<!-- Single Item --> <!-- Single Item -->
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ item.url }}" target="{{ item.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ item.description }}"> <a class="nav-link" href="{{ item.url }}" target="{{ item.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ item.description }}">
{{ render_icon_and_name(item) }} <i class="{{ item.icon.class }}"></i> {{ item.name }}
</a> </a>
</li> </li>
{% else %} {% else %}
<!-- Dropdown Menu --> <!-- Dropdown Menu -->
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdown{{ loop.index }}" role="button" data-bs-toggle="dropdown" data-bs-display="dynamic" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown{{ loop.index }}" role="button" data-bs-toggle="dropdown" data-bs-display="dynamic" aria-expanded="false">
{% if item.icon is defined and item.icon.class is defined %} {% if item.icon is defined and item.icon.class is defined %}
{{ render_icon_and_name(item) }} <i class="{{ item.icon.class }}"></i> {{ item.name }}
{% else %} {% else %}
<p>Missing icon in item: {{ item }}</p> <p>Missing icon in item: {{ item }}</p>
{% endif %} {% endif %}
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{ render_children(item.children) }} {{ render_subitems(item.subitems) }}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}

View File

@ -2,7 +2,7 @@ from pprint import pprint
class ConfigurationResolver: class ConfigurationResolver:
""" """
A class to resolve `link` entries in a nested configuration structure. A class to resolve `link` entries in a nested configuration structure.
Supports navigation through dictionaries, lists, and `children`. Supports navigation through dictionaries, lists, and `subitems`.
""" """
def __init__(self, config): def __init__(self, config):
@ -14,56 +14,23 @@ class ConfigurationResolver:
""" """
self._recursive_resolve(self.config, self.config) self._recursive_resolve(self.config, self.config)
def __load_children(self,path):
"""
Check if explicitly children should be loaded and not parent
"""
return path.split('.').pop() == "children"
def _replace_in_dict_by_dict(self, dict_origine, old_key, new_dict):
if old_key in dict_origine:
# Entferne den alten Key
old_value = dict_origine.pop(old_key)
# Füge die neuen Key-Value-Paare hinzu
dict_origine.update(new_dict)
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): def _recursive_resolve(self, current_config, root_config):
""" """
Recursively resolves `link` entries in the configuration. Recursively resolves `link` entries in the configuration.
""" """
if isinstance(current_config, dict): if isinstance(current_config, dict):
for key, value in list(current_config.items()): for key, value in list(current_config.items()):
if key == "children": if key == "link":
if value is None or not isinstance(value, list):
raise ValueError(f"Expected 'children' to be a list, but got {type(value).__name__} instead.")
for item in value:
if "link" in item:
loaded_link = self._find_entry(root_config, item['link'].lower(), 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: try:
loaded = self._find_entry(root_config, value.lower(), True) target = self._find_entry(root_config, value.lower(), True)
if isinstance(loaded, list) and len(loaded) > 2: if isinstance(target, list) and len(target) > 2:
loaded = self._find_entry(root_config, value.lower(), False) target = self._find_entry(root_config, value.lower(), False)
current_config.clear() current_config.clear()
current_config.update(loaded) current_config.update(target)
except Exception as e: except Exception as e:
raise ValueError( raise ValueError(
f"Error resolving link '{value}': {str(e)}. " f"Error resolving link '{value}': {str(e)}. "
f"Current path: {key}, Current config: {current_config}" + (f", Loaded: {loaded}" if 'loaded' in locals() or 'loaded' in globals() else "") f"Current path: {key}, Current config: {current_config}"
) )
else: else:
self._recursive_resolve(value, root_config) self._recursive_resolve(value, root_config)
@ -71,9 +38,9 @@ class ConfigurationResolver:
for item in current_config: for item in current_config:
self._recursive_resolve(item, root_config) self._recursive_resolve(item, root_config)
def _get_children(self,current): def _get_subitems(self,current):
if isinstance(current, dict) and ("children" in current and current["children"]): if isinstance(current, dict) and ("subitems" in current and current["subitems"]):
current = current["children"] current = current["subitems"]
return current return current
def _find_by_name(self,current, part): def _find_by_name(self,current, part):
@ -82,21 +49,18 @@ class ConfigurationResolver:
None None
) )
def _find_entry(self, config, path, children): def _find_entry(self, config, path, subitems):
""" """
Finds an entry in the configuration by a dot-separated path. Finds an entry in the configuration by a dot-separated path.
Supports both dictionaries and lists with `children` navigation. Supports both dictionaries and lists with `subitems` navigation.
""" """
parts = path.split('.') parts = path.split('.')
current = config current = config
for part in parts: for part in parts:
if isinstance(current, list): if isinstance(current, list):
# If children explicit declared just load children
if part != "children":
# Look for a matching name in the list # Look for a matching name in the list
found = self._find_by_name(current,part) found = self._find_by_name(current,part)
if found: if found:
current = found
print( print(
f"Matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. " f"Matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
f"Current list: {current}" f"Current list: {current}"
@ -106,11 +70,12 @@ class ConfigurationResolver:
f"No matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. " f"No matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
f"Current list: {current}" f"Current list: {current}"
) )
current = found
elif isinstance(current, dict): elif isinstance(current, dict):
# Case-insensitive dictionary lookup # Case-insensitive dictionary lookup
key = next((k for k in current if k.lower() == part), None) key = next((k for k in current if k.lower() == part), None)
if key is None: if key is None:
current = self._find_by_name(current["children"],part) current = self._find_by_name(current["subitems"],part)
if not current: if not current:
raise KeyError( raise KeyError(
f"Key '{part}' not found in dictionary. Path so far: {' > '.join(parts[:parts.index(part)+1])}. " f"Key '{part}' not found in dictionary. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
@ -124,8 +89,8 @@ class ConfigurationResolver:
f"Invalid path segment '{part}'. Current type: {type(current)}. " f"Invalid path segment '{part}'. Current type: {type(current)}. "
f"Path so far: {' > '.join(parts[:parts.index(part)+1])}" f"Path so far: {' > '.join(parts[:parts.index(part)+1])}"
) )
if children: if subitems:
current = self._get_children(current) current = self._get_subitems(current)
return current return current