mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2025-04-28 23:41:56 +02:00
Compare commits
10 Commits
11eccf2eca
...
dc11dc799b
Author | SHA1 | Date | |
---|---|---|---|
dc11dc799b | |||
8c7dc02bd5 | |||
9741da0495 | |||
0f8113974f | |||
a0664691e6 | |||
0360c443b7 | |||
954cff051a | |||
7f78e77a10 | |||
1c6b70d640 | |||
f664270b5d |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
app/static/cache/*
|
app/config.yaml
|
||||||
*__pycache__*
|
*__pycache__*
|
||||||
|
app/static/cache/*
|
169
README.md
169
README.md
@ -1,37 +1,182 @@
|
|||||||
# Landingpage
|
# Portfolio: Flask-based Portfolio 🚀
|
||||||
|
|
||||||
|
This software allows individuals and institutions to set up an easy portfolio/landingpage/homepage to showcase their projects and online presence. It is highly customizable via a YAML configuration file.
|
||||||
|
|
||||||
|
## Features ✨
|
||||||
|
|
||||||
|
- **Dynamic Navigation**: Easily create dropdown menus and nested links.
|
||||||
|
- **Customizable Cards**: Showcase your skills, projects, or services.
|
||||||
|
- **Cache Management**: Optimize your assets with automatic caching.
|
||||||
|
- **Responsive Design**: Beautiful on any device with Bootstrap.
|
||||||
|
- **Easy Configuration**: Update content using a YAML file.
|
||||||
|
|
||||||
|
## Access 🌐
|
||||||
|
|
||||||
## Access
|
|
||||||
### Locale
|
### Locale
|
||||||
[http://127.0.0.1:5000](http://127.0.0.1:5000)
|
Access the application locally at [http://127.0.0.1:5000](http://127.0.0.1:5000).
|
||||||
|
|
||||||
|
## Getting Started 🏁
|
||||||
|
|
||||||
|
### Prerequisites 📋
|
||||||
|
|
||||||
|
- Docker and Docker Compose installed on your system.
|
||||||
|
- Basic knowledge of Python and YAML for configuration.
|
||||||
|
|
||||||
|
### Installation 🛠️
|
||||||
|
|
||||||
|
1. **Clone the repository:**
|
||||||
|
```bash
|
||||||
|
git clone <repository_url>
|
||||||
|
cd <repository_directory>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Update the configuration:**
|
||||||
|
Create a `config.yaml` file. You can use `config.sample.yaml` as an example (see below for details on the configuration).
|
||||||
|
|
||||||
|
3. **Build and run the Docker container:**
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Access your portfolio:** Open your browser and navigate to `http://localhost:5000`.
|
||||||
|
|
||||||
|
## Configuration Guide 🔧
|
||||||
|
|
||||||
|
The portfolio is powered by a YAML configuration file (`config.yaml`). This file allows you to define the structure and content of your site, including cards, navigation, and company details.
|
||||||
|
|
||||||
|
### YAML Configuration Example 📄
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
accounts:
|
||||||
|
name: Online Accounts
|
||||||
|
description: Discover my online presence.
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-users
|
||||||
|
children:
|
||||||
|
- name: Channels
|
||||||
|
description: Platforms where I share content.
|
||||||
|
icon:
|
||||||
|
class: fas fa-newspaper
|
||||||
|
children:
|
||||||
|
- name: Microblogs
|
||||||
|
description: Stay updated with my microblog posts.
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-pen-nib
|
||||||
|
children:
|
||||||
|
- name: Mastodon
|
||||||
|
description: Follow my updates on Mastodon.
|
||||||
|
icon:
|
||||||
|
class: fa-brands fa-mastodon
|
||||||
|
url: https://microblog.veen.world/@kevinveenbirkenbach
|
||||||
|
identifier: "@kevinveenbirkenbach@microblog.veen.world"
|
||||||
|
cards:
|
||||||
|
- icon:
|
||||||
|
source: https://cloud.veen.world/s/logo_agile_coach_512x512/download
|
||||||
|
title: Agile Coach
|
||||||
|
text: I lead agile transformations and improve team dynamics through Scrum and Agile Coaching.
|
||||||
|
url: https://www.agile-coach.world
|
||||||
|
link_text: www.agile-coach.world
|
||||||
|
company:
|
||||||
|
titel: Kevin Veen-Birkenbach
|
||||||
|
subtitel: Consulting and Coaching Solutions
|
||||||
|
logo:
|
||||||
|
source: https://cloud.veen.world/s/logo_face_512x512/download
|
||||||
|
favicon:
|
||||||
|
source: https://cloud.veen.world/s/veen_world_favicon/download
|
||||||
|
address:
|
||||||
|
street: Afrikanische Straße 43
|
||||||
|
postal_code: DE-13351
|
||||||
|
city: Berlin
|
||||||
|
country: Germany
|
||||||
|
imprint_url: https://s.veen.world/imprint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Understanding the `children` Key 🔍
|
||||||
|
|
||||||
|
The `children` key allows hierarchical nesting of elements. Each child can itself have children, enabling the creation of multi-level navigation menus or grouped content. Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
children:
|
||||||
|
- name: Parent Item
|
||||||
|
description: Parent description.
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-folder
|
||||||
|
children:
|
||||||
|
- name: Child Item
|
||||||
|
description: Child description.
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-file
|
||||||
|
url: https://example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
This structure will render a parent menu or section containing nested child elements. Each child can be further customized with icons, descriptions, and links.
|
||||||
|
|
||||||
|
### Understanding the `link` Key 🔗
|
||||||
|
|
||||||
|
The `link` key allows you to reference another part of the YAML configuration by its path. This is useful for avoiding duplication and maintaining consistency. Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
children:
|
||||||
|
- name: Blog
|
||||||
|
description: My blog posts.
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-blog
|
||||||
|
url: https://example.com/blog
|
||||||
|
- name: Featured Blog
|
||||||
|
link: accounts.children[0].children[0] # References the "Blog" item above
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, `Featured Blog` will inherit all properties from the `Blog` item, including its name, description, and URL. This feature ensures that any updates to the `Blog` item are automatically reflected in all linked entries.
|
||||||
|
|
||||||
|
## Administrate Docker 🐳
|
||||||
|
|
||||||
## Administrate Docker
|
|
||||||
### Stop and Destroy
|
### Stop and Destroy
|
||||||
```bash
|
```bash
|
||||||
docker stop landingpage
|
docker stop portfolio; docker rm portfolio
|
||||||
docker rm landingpage
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
```bash
|
```bash
|
||||||
docker build -t application-landingpage .
|
docker build -t application-portfolio .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
|
||||||
#### Run Development Environment
|
#### Run Development Environment
|
||||||
```bash
|
```bash
|
||||||
docker run -d -p 5000:5000 --name landingpage -v $(pwd)/app/:/app -e FLASK_APP=app.py -e FLASK_ENV=development application-landingpage
|
docker run -d -p 5000:5000 --name portfolio -v $(pwd)/app/:/app -e FLASK_APP=app.py -e FLASK_ENV=development application-portfolio
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Run Production Environment
|
#### Run Production Environment
|
||||||
```bash
|
```bash
|
||||||
docker run -d -p 5000:5000 --name landingpage application-landingpage
|
docker run -d -p 5000:5000 --name portfolio application-portfolio
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debug
|
### Debug
|
||||||
```bash
|
```bash
|
||||||
docker logs -f landingpage
|
docker logs -f portfolio
|
||||||
```
|
```
|
||||||
## Author
|
|
||||||
This software was created from [Kevin Veen-Birkenbach](https://www.veen.world/) with the help of [ChatGPT]()
|
## Development Mode 🧑💻
|
||||||
|
|
||||||
|
To run the app in development mode with hot-reloading:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FLASK_ENV=development docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment 🚢
|
||||||
|
|
||||||
|
For production deployment, ensure to:
|
||||||
|
|
||||||
|
- Use a reverse proxy like NGINX or Apache.
|
||||||
|
- Secure your site with SSL/TLS.
|
||||||
|
- Use a production-ready database if required.
|
||||||
|
|
||||||
|
## Author ✍️
|
||||||
|
|
||||||
|
This software was created by [Kevin Veen-Birkenbach](https://www.veen.world/).
|
||||||
|
|
||||||
|
## License 📜
|
||||||
|
|
||||||
|
This project is licensed under the GNU Affero General Public License Version 3. See the [LICENSE](./LICENSE) file for details.
|
@ -1,8 +1,5 @@
|
|||||||
function openDynamicPopup(subitem) {
|
function openDynamicPopup(subitem) {
|
||||||
// Schließe alle offenen Modals
|
|
||||||
closeAllModals();
|
closeAllModals();
|
||||||
|
|
||||||
// Setze den Titel mit Icon, falls vorhanden
|
|
||||||
const modalTitle = document.getElementById('dynamicModalLabel');
|
const modalTitle = document.getElementById('dynamicModalLabel');
|
||||||
if (subitem.icon && subitem.icon.class) {
|
if (subitem.icon && subitem.icon.class) {
|
||||||
modalTitle.innerHTML = `<i class="${subitem.icon.class}"></i> ${subitem.name}`;
|
modalTitle.innerHTML = `<i class="${subitem.icon.class}"></i> ${subitem.name}`;
|
||||||
@ -10,7 +7,6 @@ function openDynamicPopup(subitem) {
|
|||||||
modalTitle.innerText = subitem.name;
|
modalTitle.innerText = subitem.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setze den Identifier, falls vorhanden
|
|
||||||
const identifierBox = document.getElementById('dynamicIdentifierBox');
|
const identifierBox = document.getElementById('dynamicIdentifierBox');
|
||||||
const modalContent = document.getElementById('dynamicModalContent');
|
const modalContent = document.getElementById('dynamicModalContent');
|
||||||
if (subitem.identifier) {
|
if (subitem.identifier) {
|
||||||
@ -21,25 +17,19 @@ function openDynamicPopup(subitem) {
|
|||||||
modalContent.value = '';
|
modalContent.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Konfiguriere die Warnbox mit Markdown
|
function toggleBox(boxId, textId, content) {
|
||||||
const warningBox = document.getElementById('dynamicModalWarning');
|
const box = document.getElementById(boxId);
|
||||||
if (subitem.warning) {
|
if (content) {
|
||||||
warningBox.classList.remove('d-none');
|
box.classList.remove('d-none');
|
||||||
document.getElementById('dynamicModalWarningText').innerHTML = marked.parse(subitem.warning);
|
document.getElementById(textId).innerHTML = marked.parse(content);
|
||||||
} else {
|
} else {
|
||||||
warningBox.classList.add('d-none');
|
box.classList.add('d-none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Konfiguriere die Infobox mit Markdown
|
toggleBox('dynamicModalWarning', 'dynamicModalWarningText', subitem.warning);
|
||||||
const infoBox = document.getElementById('dynamicModalInfo');
|
toggleBox('dynamicModalInfo', 'dynamicModalInfoText', subitem.info);
|
||||||
if (subitem.info) {
|
|
||||||
infoBox.classList.remove('d-none');
|
|
||||||
document.getElementById('dynamicModalInfoText').innerHTML = marked.parse(subitem.info);
|
|
||||||
} else {
|
|
||||||
infoBox.classList.add('d-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zeige die Beschreibung, falls keine URL vorhanden ist
|
|
||||||
const descriptionText = document.getElementById('dynamicDescriptionText');
|
const descriptionText = document.getElementById('dynamicDescriptionText');
|
||||||
if (!subitem.url && subitem.description) {
|
if (!subitem.url && subitem.description) {
|
||||||
descriptionText.classList.remove('d-none');
|
descriptionText.classList.remove('d-none');
|
||||||
@ -49,7 +39,6 @@ function openDynamicPopup(subitem) {
|
|||||||
descriptionText.innerText = '';
|
descriptionText.innerText = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Konfiguriere den Link oder die Beschreibung
|
|
||||||
const linkBox = document.getElementById('dynamicModalLink');
|
const linkBox = document.getElementById('dynamicModalLink');
|
||||||
const linkHref = document.getElementById('dynamicModalLinkHref');
|
const linkHref = document.getElementById('dynamicModalLinkHref');
|
||||||
if (subitem.url) {
|
if (subitem.url) {
|
||||||
@ -60,30 +49,33 @@ function openDynamicPopup(subitem) {
|
|||||||
linkBox.classList.add('d-none');
|
linkBox.classList.add('d-none');
|
||||||
linkHref.href = '#';
|
linkHref.href = '#';
|
||||||
}
|
}
|
||||||
|
function populateSection(sectionId, listId, items, onClickHandler) {
|
||||||
|
const section = document.getElementById(sectionId);
|
||||||
|
const list = document.getElementById(listId);
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
// Konfiguriere die Alternativen
|
if (items && items.length > 0) {
|
||||||
const alternativesSection = document.getElementById('dynamicAlternativesSection');
|
section.classList.remove('d-none');
|
||||||
const alternativesList = document.getElementById('dynamicAlternativesList');
|
items.forEach(item => {
|
||||||
alternativesList.innerHTML = ''; // Clear existing alternatives
|
const listItem = document.createElement('li');
|
||||||
if (subitem.alternatives && subitem.alternatives.length > 0) {
|
listItem.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'align-items-center');
|
||||||
alternativesSection.classList.remove('d-none');
|
listItem.innerHTML = `
|
||||||
subitem.alternatives.forEach(alt => {
|
<span>
|
||||||
const listItem = document.createElement('li');
|
<i class="${item.icon.class}"></i> ${item.name}
|
||||||
listItem.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'align-items-center');
|
</span>
|
||||||
listItem.innerHTML = `
|
<button class="btn btn-outline-secondary btn-sm">Open</button>
|
||||||
<span>
|
`;
|
||||||
<i class="${alt.icon.class}"></i> ${alt.name}
|
listItem.querySelector('button').addEventListener('click', () => onClickHandler(item));
|
||||||
</span>
|
list.appendChild(listItem);
|
||||||
<button class="btn btn-outline-secondary btn-sm">Open</button>
|
});
|
||||||
`;
|
} else {
|
||||||
listItem.querySelector('button').addEventListener('click', () => openDynamicPopup(alt));
|
section.classList.add('d-none');
|
||||||
alternativesList.appendChild(listItem);
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alternativesSection.classList.add('d-none');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kopierfunktion für den Identifier
|
populateSection('dynamicAlternativesSection', 'dynamicAlternativesList', subitem.alternatives, openDynamicPopup);
|
||||||
|
populateSection('dynamicChildrenSection', 'dynamicChildrenList', subitem.children, openDynamicPopup);
|
||||||
|
|
||||||
const copyButton = document.getElementById('dynamicCopyButton');
|
const copyButton = document.getElementById('dynamicCopyButton');
|
||||||
copyButton.onclick = () => {
|
copyButton.onclick = () => {
|
||||||
modalContent.select();
|
modalContent.select();
|
||||||
@ -92,25 +84,20 @@ function openDynamicPopup(subitem) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Modal anzeigen
|
|
||||||
const modal = new bootstrap.Modal(document.getElementById('dynamicModal'));
|
const modal = new bootstrap.Modal(document.getElementById('dynamicModal'));
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAllModals() {
|
function closeAllModals() {
|
||||||
const modals = document.querySelectorAll('.modal.show'); // Alle offenen Modals finden
|
const modals = document.querySelectorAll('.modal.show');
|
||||||
modals.forEach(modal => {
|
modals.forEach(modal => {
|
||||||
const modalInstance = bootstrap.Modal.getInstance(modal);
|
const modalInstance = bootstrap.Modal.getInstance(modal);
|
||||||
if (modalInstance) {
|
if (modalInstance) {
|
||||||
modalInstance.hide(); // Modal ausblenden
|
modalInstance.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Entferne die "modal-backdrop"-Elemente
|
|
||||||
const backdrops = document.querySelectorAll('.modal-backdrop');
|
const backdrops = document.querySelectorAll('.modal-backdrop');
|
||||||
backdrops.forEach(backdrop => backdrop.remove());
|
backdrops.forEach(backdrop => backdrop.remove());
|
||||||
|
|
||||||
// Entferne die Klasse, die den Hintergrund ausgraut
|
|
||||||
document.body.classList.remove('modal-open');
|
document.body.classList.remove('modal-open');
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
document.body.style.paddingRight = '';
|
document.body.style.paddingRight = '';
|
||||||
|
@ -1,3 +1,16 @@
|
|||||||
|
{% macro alert_box(id, alert_class, icon_class, title, text_id) %}
|
||||||
|
<div id="{{ id }}" class="alert {{ alert_class }} d-none" role="alert">
|
||||||
|
<h5><i class="{{ icon_class }}"></i> {{ title }} </h5><span id="{{ text_id }}"></span>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro list_section(id, title, list_id) %}
|
||||||
|
<div id="{{ id }}" class="mt-4 d-none">
|
||||||
|
<h6>{{ title }}:</h6>
|
||||||
|
<ul class="list-group" id="{{ list_id }}"></ul>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
<div class="modal fade" id="dynamicModal" tabindex="-1" aria-labelledby="dynamicModalLabel" aria-hidden="true">
|
<div class="modal fade" id="dynamicModal" tabindex="-1" aria-labelledby="dynamicModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@ -6,30 +19,31 @@
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<!-- Warnbox mit Markdown -->
|
<!-- Warning box with Markdown -->
|
||||||
<div id="dynamicModalWarning" class="alert alert-warning d-none" role="alert">
|
{{ alert_box('dynamicModalWarning', 'alert-warning', 'fa-solid fa-triangle-exclamation', 'Warning', 'dynamicModalWarningText') }}
|
||||||
<h5><i class="fa-solid fa-triangle-exclamation"></i> Warning </h5><span id="dynamicModalWarningText"></span>
|
|
||||||
</div>
|
<!-- Info box with Markdown -->
|
||||||
<!-- Infobox mit Markdown -->
|
{{ alert_box('dynamicModalInfo', 'alert-info', 'fa-solid fa-circle-info', 'Information', 'dynamicModalInfoText') }}
|
||||||
<div id="dynamicModalInfo" class="alert alert-info d-none" role="alert">
|
|
||||||
<h5><i class="fa-solid fa-circle-info"></i> Information</h5><span id="dynamicModalInfoText"></span>
|
|
||||||
</div>
|
|
||||||
<!-- Description text -->
|
<!-- Description text -->
|
||||||
<div id="dynamicDescriptionText" class="mt-2 d-none"></div>
|
<div id="dynamicDescriptionText" class="mt-2 d-none"></div>
|
||||||
<!-- Eingabebox für Identifier -->
|
|
||||||
|
<!-- Input box for Identifier -->
|
||||||
<div id="dynamicIdentifierBox" class="input-group mt-2 d-none">
|
<div id="dynamicIdentifierBox" class="input-group mt-2 d-none">
|
||||||
<input type="text" id="dynamicModalContent" class="form-control" readonly>
|
<input type="text" id="dynamicModalContent" class="form-control" readonly>
|
||||||
<button class="btn btn-outline-secondary" type="button" id="dynamicCopyButton">Copy</button>
|
<button class="btn btn-outline-secondary" type="button" id="dynamicCopyButton">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Link -->
|
<!-- Link -->
|
||||||
<div id="dynamicModalLink" class="mt-3 d-none">
|
<div id="dynamicModalLink" class="mt-3 d-none">
|
||||||
<a href="#" target="_blank" class="btn btn-primary w-100" id="dynamicModalLinkHref"></a>
|
<a href="#" target="_blank" class="btn btn-primary w-100" id="dynamicModalLinkHref"></a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Alternativen -->
|
|
||||||
<div id="dynamicAlternativesSection" class="mt-4 d-none">
|
<!-- Options -->
|
||||||
<h6>Alternatives:</h6>
|
{{ list_section('dynamicChildrenSection', 'Options', 'dynamicChildrenList') }}
|
||||||
<ul class="list-group" id="dynamicAlternativesList"></ul>
|
|
||||||
</div>
|
<!-- Alternatives -->
|
||||||
|
{{ list_section('dynamicAlternativesSection', 'Alternatives', 'dynamicAlternativesList') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="closeAllModals()">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="closeAllModals()">Close</button>
|
||||||
|
@ -46,7 +46,7 @@ class ConfigurationResolver:
|
|||||||
raise ValueError(f"Expected 'children' to be a list, but got {type(value).__name__} instead.")
|
raise ValueError(f"Expected 'children' to be a list, but got {type(value).__name__} instead.")
|
||||||
for item in value:
|
for item in value:
|
||||||
if "link" in item:
|
if "link" in item:
|
||||||
loaded_link = self._find_entry(root_config, item['link'].lower(), False)
|
loaded_link = self._find_entry(root_config, self._mapped_key(item['link']), False)
|
||||||
if isinstance(loaded_link, list):
|
if isinstance(loaded_link, list):
|
||||||
self._replace_in_list_by_list(value,item,loaded_link)
|
self._replace_in_list_by_list(value,item,loaded_link)
|
||||||
else:
|
else:
|
||||||
@ -55,15 +55,15 @@ class ConfigurationResolver:
|
|||||||
self._recursive_resolve(value, root_config)
|
self._recursive_resolve(value, root_config)
|
||||||
elif key == "link":
|
elif key == "link":
|
||||||
try:
|
try:
|
||||||
loaded = self._find_entry(root_config, value.lower(), True)
|
loaded = self._find_entry(root_config, self._mapped_key(value), True)
|
||||||
if isinstance(loaded, list) and len(loaded) > 2:
|
if isinstance(loaded, list) and len(loaded) > 2:
|
||||||
loaded = self._find_entry(root_config, value.lower(), False)
|
loaded = self._find_entry(root_config, self._mapped_key(value), False)
|
||||||
current_config.clear()
|
current_config.clear()
|
||||||
current_config.update(loaded)
|
current_config.update(loaded)
|
||||||
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 part: {key}, Current config: {current_config}" + (f", Loaded: {loaded}" if 'loaded' in locals() or 'loaded' in globals() else "")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._recursive_resolve(value, root_config)
|
self._recursive_resolve(value, root_config)
|
||||||
@ -76,9 +76,12 @@ class ConfigurationResolver:
|
|||||||
current = current["children"]
|
current = current["children"]
|
||||||
return current
|
return current
|
||||||
|
|
||||||
|
def _mapped_key(self,name):
|
||||||
|
return name.replace(" ", "").lower()
|
||||||
|
|
||||||
def _find_by_name(self,current, part):
|
def _find_by_name(self,current, part):
|
||||||
return next(
|
return next(
|
||||||
(item for item in current if isinstance(item, dict) and item.get("name", "").lower() == part),
|
(item for item in current if isinstance(item, dict) and self._mapped_key(item.get("name", "")) == part),
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -108,7 +111,8 @@ class ConfigurationResolver:
|
|||||||
)
|
)
|
||||||
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 self._mapped_key(k) == part), None)
|
||||||
|
# If no fitting key was found search in the children
|
||||||
if key is None:
|
if key is None:
|
||||||
current = self._find_by_name(current["children"],part)
|
current = self._find_by_name(current["children"],part)
|
||||||
if not current:
|
if not current:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user