Compare commits

..

9 Commits

6 changed files with 330 additions and 347 deletions

View File

@ -1,88 +1,4 @@
---
accounts:
name: Accounts
description: My Online Accounts
icon:
class: fa-solid fa-users
subitems:
- name: Meta
description: Social and developer networks
icon:
class: fa-brands fa-meta
url:
subitems:
- name: Instagram
description: Follow me on Instagram
icon:
class: fa-brands fa-instagram
url: https://www.instagram.com/kevinveenbirkenbach/
identifier: kevinveenbirkenbach
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.
- name: Facebook
description: Like my Facebook page
icon:
class: fa-brands fa-facebook
url: https://www.facebook.com/kevinveenbirkenbach
- link: navigation.header.contact.messenger
- name: Carreer Profiles
icon:
class: fa-solid fa-user-tie
subitems:
- name: XING
description: Visit my XING profile
icon:
class: bi bi-building
url: https://www.xing.com/profile/Kevin_VeenBirkenbach
- name: LinkedIn
description: Connect on LinkedIn
icon:
class: bi bi-linkedin
url: https://www.linkedin.com/in/kevinveenbirkenbach
- name: Sports
description: My sport activities
icon:
class: fa-solid fa-running
url:
subitems:
- name: Garmin
description: My Garmin activities
icon:
class: fa-solid fa-person-running
url: https://s.veen.world/garmin
- name: Eversports
description: My Eversports sessions
icon:
class: fa-solid fa-dumbbell
url: https://s.veen.world/eversports
- name: Duolingo
description: Learn with me on Duolingo
icon:
class: fa-solid fa-language
url: https://www.duolingo.com/profile/kevinbirkenbach
- name: Spotify
description: Listen to my playlists
icon:
class: fa-brands fa-spotify
url: https://open.spotify.com/user/31vebfzbjf3p7oualis76qfpr5ty
- name: Patreon
description: Support me on Patreon
icon:
class: fa-brands fa-patreon
url: https://patreon.com/kevinveenbirkenbach
- name: Discourse
description: Follow me on Discourse
icon:
class: fa-brands fa-discourse
url: https://forum.veen.world/u/kevinveenbirkenbach
cards:
- icon:
source: https://cloud.veen.world/s/logo_agile_coach_512x512/download
@ -159,7 +75,6 @@ cards:
I deliver expert consulting services. Currently training for my Private Pilot
License, I specialize in guiding clients through aviation regulations, safety
standards, and operational efficiency.
url:
link_text: Website under construction
- icon:
source: https://cloud.veen.world/s/logo_hunter_512x512/download
@ -168,7 +83,6 @@ cards:
walks, survival trainings, and photo expeditions, merging ecological knowledge
with nature respect. My goal is to foster sustainable conservation and enhance
appreciation for the natural world through responsible practices.
url:
link_text: Website under construction
- icon:
source: https://cloud.veen.world/s/logo_diver_512x512/download
@ -177,7 +91,6 @@ cards:
diving instruction, underwater photography, and guided dive tours. My experience
ensures safe and enriching underwater adventures, highlighting marine conservation
and the wonders of aquatic ecosystems.
url:
link_text: Website under construction
- icon:
source: https://cloud.veen.world/s/logo_massage_therapist_512x512/download
@ -185,8 +98,84 @@ cards:
text: Certified in Tantra Massage, I offer unique full-body rituals to awaken senses
and harmonize body and mind. My sessions, a blend of ancient Tantra and modern
relaxation, focus on energy flow, personal growth, and spiritual awakening.
url:
link_text: Website under construction
accounts:
name: Accounts
description: My Accounts
icon:
class: fa-solid fa-user-group
subitems:
- name: Meta
description: Social and developer networks
icon:
class: fa-brands fa-meta
subitems:
- name: Instagram
description: Follow me on Instagram
icon:
class: fa-brands fa-instagram
url: https://www.instagram.com/kevinveenbirkenbach/
identifier: kevinveenbirkenbach
link: navigation.header.contact.whatsapp.warning
- name: Facebook
description: Like my Facebook page
icon:
class: fa-brands fa-facebook
url: https://www.facebook.com/kevinveenbirkenbach
- name: Messengers
description: Messenger Applications
icon:
class: fas fa-comments
subitems:
- link: navigation.header.contact.whatsapp
- link: navigation.header.contact.signal
- link: navigation.header.contact.telegram
- name: Carreer Profiles
icon:
class: fa-solid fa-user-tie
subitems:
- name: XING
description: Visit my XING profile
icon:
class: bi bi-building
url: https://www.xing.com/profile/Kevin_VeenBirkenbach
- name: LinkedIn
description: Connect on LinkedIn
icon:
class: bi bi-linkedin
url: https://www.linkedin.com/in/kevinveenbirkenbach
- name: Sports
description: My sport activities
icon:
class: fa-solid fa-running
subitems:
- name: Garmin
description: My Garmin activities
icon:
class: fa-solid fa-person-running
url: https://s.veen.world/garmin
- name: Eversports
description: My Eversports sessions
icon:
class: fa-solid fa-dumbbell
url: https://s.veen.world/eversports
- name: Duolingo
description: Learn with me on Duolingo
icon:
class: fa-solid fa-language
url: https://www.duolingo.com/profile/kevinbirkenbach
- name: Spotify
description: Listen to my playlists
icon:
class: fa-brands fa-spotify
url: https://open.spotify.com/user/31vebfzbjf3p7oualis76qfpr5ty
- name: Patreon
description: Support me on Patreon
icon:
class: fa-brands fa-patreon
url: https://patreon.com/kevinveenbirkenbach
company:
titel: Kevin Veen-Birkenbach
subtitel: Consulting and Coaching Solutions
@ -200,6 +189,7 @@ company:
city: Berlin
country: Germany
imprint_url: https://s.veen.world/imprint
navigation:
header:
- name: Microblog
@ -207,25 +197,21 @@ navigation:
icon:
class: fa-brands fa-mastodon
url: https://microblog.veen.world/@kevinveenbirkenbach
- name: Pictures
description: View my photo gallery
icon:
class: fa-solid fa-camera
url: https://picture.veen.world/kevinveenbirkenbach
- name: Videos
description: Watch my videos
icon:
class: fa-solid fa-video
url: https://video.veen.world/a/kevinveenbirkenbach
- name: Blog
description: Read my blog
icon:
class: fa-solid fa-blog
url: https://blog.veen.world
- name: Code
icon:
class: fa-solid fa-laptop-code
@ -236,13 +222,11 @@ navigation:
icon:
class: bi bi-github
url: https://github.com/kevinveenbirkenbach
- name: Gitea
description: Explore my code repositories
icon:
class: fa-solid fa-code
url: https://git.veen.world/kevinveenbirkenbach
- name: Contact
description: Get in touch
icon:
@ -255,7 +239,25 @@ navigation:
url: mailto:kevin@veen.world
identifier: kevin@veen.world
alternatives:
- link: navigation.header.contact.messenger.matrix
#- link: navigation.header.contact.matrix
- name: Matrix
description: Chat with me on Matrix
icon:
class: fa-solid fa-cubes
identifier: "@kevinveenbirkenbach:veen.world"
info: |
#### Why Use Matrix?
Matrix is a secure, decentralized communication platform that ensures privacy and control over your data. Learn more about [Matrix](https://matrix.org/).
#### Privacy and Security
End-to-end encryption keeps your conversations private and secure.
#### Decentralized and Open
Matrix's federated network means you can host your own server or use any provider while staying connected.
#### A Movement for Digital Freedom
By using Matrix, you support open, transparent, and secure communication.
- name: Mobile
description: Call me
icon:
@ -281,28 +283,7 @@ navigation:
#### Stand for Security
Using PGP is more than a tool—it's a statement about valuing freedom, privacy, and the security of digital communication. Explore the principles of secure communication with [privacy guides](https://privacyguides.org/).
- name: Messenger
description: Social and developer networks
icon:
class: fa-solid fa-comments
subitems:
- name: Matrix
description: Chat with me on Matrix
icon:
class: fa-solid fa-cubes
identifier: "@kevinveenbirkenbach:veen.world"
info: |
#### Why Use Matrix?
Matrix is a secure, decentralized communication platform that ensures privacy and control over your data. Learn more about [Matrix](https://matrix.org/).
#### Privacy and Security
End-to-end encryption keeps your conversations private and secure.
#### Decentralized and Open
Matrix's federated network means you can host your own server or use any provider while staying connected.
#### A Movement for Digital Freedom
By using Matrix, you support open, transparent, and secure communication.
- name: Signal
description: Message me on Signal
icon:
@ -310,7 +291,7 @@ navigation:
identifier: "+491781798023"
warning: Signal is not hosted by me!
alternatives:
- link: navigation.header.contact.messenger.matrix
#- link: navigation.header.contact.matrix
- name: Telegram
description: Message me on Telegram
icon:
@ -320,29 +301,22 @@ navigation:
identifier: kevinveenbirkenbach
warning: Telegram is not hosted by me!
alternatives:
- link: navigation.header.contact.messenger.matrix
#- link: navigation.header.contact.matrix
- name: WhatsApp
description: Chat with me on WhatsApp
icon:
class: fa-brands fa-whatsapp
url: https://wa.me/491781798023
identifier: "+491781798023"
info: Consider using decentralized and privacy-respecting alternatives to maintain control over your data, improve security, and foster healthier online interactions.
alternatives:
- link: navigation.header.contact.messenger.matrix
- link: navigation.header.contact.messenger.signal
- link: navigation.header.contact.messenger.telegram
#- link: navigation.header.contact.matrix
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.
footer:
- link: accounts
- name: Solution Hub
description: Curated collection of self hosted tools
icon:
class: fa-solid fa-network-wired
url:
subitems:
- name: Community
description: Tools to manage the community
description: My presence in the Fediverse
icon:
class: fa-solid fa-users
subitems:
@ -350,83 +324,67 @@ navigation:
description: Join the discussion
icon:
class: fa-brands fa-discourse
url: https://forum.veen.world/
- name: Learning Platform
description: Learn with my academy
icon:
class: fa-solid fa-graduation-cap
url: https://academy.veen.world/
url: https://forum.veen.world/u/kevinveenbirkenbach
- name: Newsletter
description: Subscribe to my newsletter
icon:
class: fa-solid fa-envelope-open-text
url: https://newsletter.veen.world/subscription/form
- name: Project Management
description: Project Management Tools
- name: Work Hub
description: Curated collection of self hosted tools for work, organization, and learning.
icon:
class: fa-solid fa-chart-line
class: fa-solid fa-toolbox
subitems:
- name: Open Project
description: Explore my projects
icon:
class: fa-solid fa-tasks
class: fa-solid fa-chart-line
url: https://project.veen.world/
- name: Taiga
description: View my Kanban board
icon:
class: bi bi-clipboard2-check-fill
url: https://kanban.veen.world/
- name: Matomo
description: Analyze with Matomo
icon:
class: fa-solid fa-chart-simple
url: https://matomo.veen.world/
- name: Baserow
description: Organize with Baserow
icon:
class: fa-solid fa-table
url: https://baserow.veen.world/
- name: Communication
icon:
class: fa-solid fa-comments
subitems:
- name: Elements
description: Chat with me
icon:
class: fa-solid fa-comment
url: https://element.veen.world/
- name: Big Blue Button
description: Join live events
icon:
class: fa-solid fa-video
url: https://meet.veen.world/
- name: Mailu
description: Send me a mail
icon:
class: fa-solid fa-envelope
url: https://mail.veen.world/
- name: Moodel
description: Learn with my academy
icon:
class: fa-solid fa-graduation-cap
url: https://academy.veen.world/
- name: Yourls
description: Find my curated links
icon:
class: bi bi-link
url: https://s.veen.world/admin/
- name: Nextcloud
description: Access my cloud storage
icon:
class: fa-solid fa-cloud
url: https://cloud.veen.world/
- name: About
description: All information about me
icon:
class: fa-solid fa-user
subitems:
- name: Logbooks
description: My activity logs
icon:
@ -437,41 +395,31 @@ navigation:
icon:
class: fa-solid fa-parachute-box
url: https://s.veen.world/skydiverlog
- name: Skipper
description: See my sailing records
icon:
class: fa-solid fa-sailboat
url: https://s.veen.world/meilenbuch
- name: Diver
description: Check my diving logs
icon:
class: fa-solid fa-fish
url: https://s.veen.world/diverlog
- name: Pilot
description: Review my flight logs
icon:
class: fa-solid fa-plane
url: https://s.veen.world/pilotlog
- name: Nature
description: Explore my nature logs
icon:
class: fa-solid fa-tree
url: https://s.veen.world/naturejournal
- name: Vita
description: View my CV
description: View my CV and professional background
icon:
class: fa-solid fa-file-lines
url: https://s.veen.world/lebenslauf
- name: Certificates
description: View my certifications and degrees
icon:
class: fa-solid fa-graduation-cap
url: https://s.veen.world/lebenslauf
- name: Imprint
icon:
class: fa-solid fa-scale-balanced

View File

@ -1,10 +1,7 @@
/* General link styles */
a {
text-decoration: none;
color: #000000;
}
/* Header styles */
.header img {
float: right;
width: 100px;
@ -14,39 +11,39 @@ a {
.header h1 {
position: relative;
}
/* Equal-height container using flexbox */
.equal-height {
display: flex;
flex: 1;
}
/* Card styles */
.navbar, .card {
flex: 1; /* Ensures cards fill the height of their container */
border-radius: 5px; /* Rounded corners */
border: 1px solid #ccc; /* Optional border color */
padding: 10px; /* Inner spacing */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); /* Subtle shadow effect */
color: #000000 !important;
background-color: #f9f9f9;
}
.card-body {
display: flex;
flex-direction: column;
align-items: center; /* Center content horizontally */
text-align: center; /* Center text alignment */
align-items: center; /* Zentriert die Inhalte horizontal */
text-align: center; /* Zentriert den Text */
}
.card-icon {
display: flex;
justify-content: center; /* Center the icon horizontally */
justify-content: center; /* Zentriert das Icon horizontal */
}
.card-text,
.card ul {
text-align: left; /* Align text to the left */
text-align: left; /* Stellt sicher, dass der Text linksbündig ist */
}
.card{
flex: 1; /* Stellt sicher, dass die Karten die ganze Höhe ihrer Container ausfüllen */
border-width: 3px;
/*border-color: #000000;*/
}
h3.card-title{
font-size: 1.3em;
}
.card .stretched-link{
font-size: 0.7em;
}
.card-column{
@ -54,32 +51,21 @@ a {
padding-bottom: 12px;
}
.card .stretched-link {
font-size: 0.7em;
}
h3.card-title {
h3.footer-title{
font-size: 1.3em;
}
/* Footer styles */
.footer {
margin-top: 12px;
text-align: center;
font-size: 0.7em;
}
.footer p,
.footer h3 {
margin: 0;
padding: 0;
.footer p, h3{
margin: 0px;
padding: 0px;
}
h3.footer-title {
font-size: 1.3em;
}
/* Dropdown menu styles */
.dropdown-menu {
position: absolute !important;
}
@ -87,26 +73,40 @@ h3.footer-title {
.dropdown-menu-footer {
position: absolute !important;
top: auto !important;
bottom: 100%; /* Positions the menu above the trigger */
transform: translateY(-10px); /* Optional spacing for smoother appearance */
bottom: 100%; /* Positioniert das Menü über dem Auslöser */
transform: translateY(-10px); /* Optional: Sanfter Abstand */
}
/* Dropdown submenu styles */
.dropdown-submenu {
position: relative;
list-style: none;
}
.navbar, .card {
border-radius: 5px; /* Runde Ecken */
border: 1px solid #ccc; /* Optionale Rahmenfarbe */
padding: 10px; /* Optionaler Abstand innen */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); /* Optionale Schatteneffekte */
color: #000000 !important;
background-color: #f9f9f9;
}
.navbar-nav {
padding: 0;
margin: 0;
}
/* Stellt sicher, dass Submenüs korrekt positioniert sind */
.dropdown-submenu {
position: relative;
}
.dropdown-submenu > .dropdown-menu {
position: absolute;
top: 0;
left: 100%; /* Default position: open to the right */
left: 100%; /* Positioniert das Submenü rechts vom Hauptmenü */
margin-top: -1px;
z-index: 1050;
transition: opacity 0.3s ease-in-out; /* Smooth opacity transition */
}
/* Handle collapse behavior for dropdowns */
.dropdown-menu.collapse {
display: none;
}
@ -115,7 +115,7 @@ h3.footer-title {
display: block;
}
/* Ensure submenus are hidden by default */
/* Standardmäßig sind die Submenüs ausgeblendet */
.dropdown-submenu .dropdown-menu {
display: none;
opacity: 0;
@ -125,19 +125,15 @@ h3.footer-title {
top: 0;
}
/* Show submenu on hover */
/* Beim Hover auf das Submenü-Element wird das Menü angezeigt */
.dropdown-submenu:hover > .dropdown-menu {
display: block;
opacity: 1;
z-index: 1050;
}
/* Ensure submenu remains visible when hovered over */
/* Um sicherzustellen, dass es nicht sofort verschwindet */
.dropdown-submenu:hover > .dropdown-menu:hover {
display: block;
opacity: 1;
}
/* Handle dynamic submenu positioning */
.dropdown-submenu > .dropdown-menu[style*="right: 100%"] {
left: auto; /* Override left position for leftward opening */
}

View File

@ -9,19 +9,6 @@ document.addEventListener('DOMContentLoaded', () => {
clearTimeout(timeout);
const menu = submenu.querySelector('.dropdown-menu');
if (menu) {
// Dynamische Positionierung
const rect = menu.getBoundingClientRect();
const viewportWidth = window.innerWidth;
// Überprüfen, ob Platz nach rechts ist, sonst nach links öffnen
if (rect.right > viewportWidth) {
menu.style.left = 'auto';
menu.style.right = '100%';
} else {
menu.style.left = '100%';
menu.style.right = 'auto';
}
menu.style.display = 'block';
menu.style.opacity = '1';
}

View File

@ -7,8 +7,8 @@
<hr />
<h3 class="card-title">{{ card.title }}</h3>
<p class="card-text">{{ card.text }}</p>
{% if card.url %}
<a href="{{ card.url }}" class="mt-auto btn btn-light stretched-link" ><i class="fa-solid fa-globe"></i> {{ card.link_text }}</a>
{% if card.link %}
<a href="{{ card.link }}" class="mt-auto btn btn-light stretched-link" ><i class="fa-solid fa-globe"></i> {{ card.link_text }}</a>
{% else %}
<i class="fa-solid fa-hourglass"></i> {{ card.link_text }}
{% endif %}

View File

@ -4,11 +4,7 @@
{% if subitem.subitems %}
<li class="dropdown-submenu position-relative">
<a class="dropdown-item dropdown-toggle" href="#" title="{{ subitem.description }}">
{% 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>
<ul class="dropdown-menu">
{{ render_subitems(subitem.subitems) }}

View File

@ -1,101 +1,157 @@
from pprint import pprint
import inspect
class ConfigurationResolver:
"""
A class to resolve `link` entries in a nested configuration structure.
Supports navigation through dictionaries, lists, and `subitems`.
"""
def __init__(self, config):
"""
Initializes the ConfigurationResolver with a configuration dictionary.
Args:
config (dict): The configuration to resolve links in.
"""
self.config = config
def resolve_links(self):
"""
Resolves all `link` entries in the configuration.
Resolves all `link` entries in the configuration by replacing them with
the referenced configuration entry.
"""
self._recursive_resolve(self.config, self.config)
def _recursive_resolve(self, current_config, root_config):
def _recursive_resolve(self, current_config, root_config, path=""):
"""
Recursively resolves `link` entries in the configuration.
Recursively traverses the configuration to resolve all `link` entries.
Args:
current_config (dict or list): The current section of the configuration being processed.
root_config (dict): The root of the configuration to resolve links against.
path (str): The current path in the configuration for debugging purposes.
"""
if isinstance(current_config, dict):
self._debug(current_config,path,inspect.currentframe().f_lineno)
# Traverse all key-value pairs in the dictionary
for key, value in list(current_config.items()):
if key == "link":
if key == "subitems" and isinstance(value, list):
pass
#self._debug(value,path,inspect.currentframe().f_lineno)
#for index, item in enumerate(current_config[key]):
# #self._debug(value,path,inspect.currentframe().f_lineno)
# if "link" in item:
# self._debug(value,path,inspect.currentframe().f_lineno)
# self._recursive_resolve(item, root_config, path=f"{path}[{index}]")
elif key == "link":
# Found a `link` entry, attempt to resolve it
try:
target = self._find_entry(root_config, value.lower(), True)
if isinstance(target, list) and len(target) > 2:
target = self._find_entry(root_config, value.lower(), False)
target = self._find_entry(root_config, value.lower().replace(" ", "_"))
if isinstance(target, dict):
self._debug(value,path,inspect.currentframe().f_lineno)
# Replace the current dictionary with the resolved dictionary
current_config.clear()
current_config.update(target)
except Exception as e:
elif isinstance(target, str):
self._debug(value,path,inspect.currentframe().f_lineno)
# Replace the `link` entry with the resolved string
current_config[key] = target
else:
raise ValueError(
f"Error resolving link '{value}': {str(e)}. "
f"Current path: {key}, Current config: {current_config}"
f"Expected a dictionary or string for link '{value}', got {type(target)}"
)
except KeyError as e:
# Handle unresolved links
raise ValueError(
f"Key error while resolving link '{value}': {str(e)}. "
f"Current path: {path}, Current config: {current_config}"
)
else:
self._recursive_resolve(value, root_config)
self._debug(value,path,inspect.currentframe().f_lineno)
# Recursively resolve non-`link` entries
self._recursive_resolve(value, root_config, path=f"{path}.{key}")
elif isinstance(current_config, list):
for item in current_config:
self._recursive_resolve(item, root_config)
# Traverse all items in the list
for index, item in enumerate(current_config):
self._recursive_resolve(item, root_config, path=f"{path}[{index}]")
def _get_subitems(self,current):
if isinstance(current, dict) and ("subitems" in current and current["subitems"]):
current = current["subitems"]
return current
def _debug(self, value, path, line, condition="accounts"):
if condition in path:
print("LINE:" + str(line))
print("PATH:" + path)
print("VALUE:")
pprint(value)
def _find_by_name(self,current, part):
return next(
(item for item in current if isinstance(item, dict) and item.get("name", "").lower() == part),
def _find_entry(self, config, path):
"""
Finds an entry in the configuration by navigating a dot-separated path.
Args:
config (dict or list): The configuration to search within.
path (str): The dot-separated path to the desired entry.
Returns:
dict or str: The resolved entry.
Raises:
KeyError: If the path cannot be resolved.
ValueError: If the resolved entry is not of the expected type.
"""
parts = path.split('.') # Split the path into segments
current = config
for part in parts:
part = part.replace(" ", "_") # Normalize part name
if isinstance(current, list):
# Search for a matching entry in a list
found = next(
(
item
for item in current
if isinstance(item, dict) and item.get("name", "").lower().replace(" ", "_") == part
),
None
)
def _find_entry(self, config, path, subitems):
"""
Finds an entry in the configuration by a dot-separated path.
Supports both dictionaries and lists with `subitems` navigation.
"""
parts = path.split('.')
current = config
for part in parts:
if isinstance(current, list):
# Look for a matching name in the list
found = self._find_by_name(current,part)
if found:
print(
f"Matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
f"Current list: {current}"
)
else:
raise ValueError(
if not found:
raise KeyError(
f"No matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
f"Current list: {current}"
)
current = found
elif isinstance(current, dict):
# Case-insensitive dictionary lookup
key = next((k for k in current if k.lower() == part), None)
# Search for a key match in a dictionary
key = next((k for k in current if k.lower().replace(" ", "_") == part), None)
if key is None:
current = self._find_by_name(current["subitems"],part)
if not current:
raise KeyError(
f"Key '{part}' not found in dictionary. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
f"Current dictionary: {current}"
)
else:
current = current[key]
else:
# Invalid path segment
raise ValueError(
f"Invalid path segment '{part}'. Current type: {type(current)}. "
f"Path so far: {' > '.join(parts[:parts.index(part)+1])}"
)
if subitems:
current = self._get_subitems(current)
# Stop navigating into `subitems` unless explicitly required by the path
if isinstance(current, dict) and "subitems" in current and isinstance(current["subitems"], list) and part != "subitems":
break # Stop navigation if `subitems` is not explicitly in the path
# Ensure the resolved target is a dictionary or string
if not isinstance(current, (dict, str)):
raise ValueError(
f"Expected a dictionary or string for path '{path}', got {type(current)}. Current value: {current}"
)
return current
def get_config(self):
"""
Returns the resolved configuration.
Returns the fully resolved configuration.
Returns:
dict: The resolved configuration.
"""
return self.config