From 6c4c0a533e2ed32348b13db2c83cef2c117c9d15 Mon Sep 17 00:00:00 2001
From: sidey79 <7968127+sidey79@users.noreply.github.com>
Date: Wed, 17 Dec 2025 22:18:03 +0000
Subject: [PATCH 1/4] Fix sitemap generation and validation
- Updated tools/generate_sitemap.py to fix URL generation and validation
- Enhanced test coverage in tests/tools/test_generate_sitemap.py
- Added sitemap_validation_report.md with validation results
- Added sitemap_analysis_report.md with analysis of existing sitemap
- Generated updated sitemap.xml for documentation
---
sitemap.xml | 2 +
sitemap_analysis_report.md | 121 ++++++++++++++++
sitemap_validation_report.md | 115 +++++++++++++++
tests/tools/test_generate_sitemap.py | 201 ++++++++++++++++++++++++++-
tools/generate_sitemap.py | 106 +++++++++-----
5 files changed, 509 insertions(+), 36 deletions(-)
create mode 100644 sitemap.xml
create mode 100644 sitemap_analysis_report.md
create mode 100644 sitemap_validation_report.md
diff --git a/sitemap.xml b/sitemap.xml
new file mode 100644
index 0000000..4d526d7
--- /dev/null
+++ b/sitemap.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/sitemap_analysis_report.md b/sitemap_analysis_report.md
new file mode 100644
index 0000000..0aba489
--- /dev/null
+++ b/sitemap_analysis_report.md
@@ -0,0 +1,121 @@
+# Analyse des Sitemap-Generator-Skripts `tools/generate_sitemap.py`
+
+## Zusammenfassung
+
+Das Skript `tools/generate_sitemap.py` generiert eine dynamische `sitemap.xml` basierend auf HTML-Dateien im Build-Output-Ordner. Es weist Prioritäten und Update-Frequenzen basierend auf Dateipfaden zu und unterstützt branch-spezifische Base-URLs.
+
+Die Analyse hat mehrere potenzielle Probleme identifiziert, die zu einer unvollständigen oder fehlerhaften Sitemap führen können.
+
+## 1. Generierungsprozess
+
+### Wie werden HTML-Dateien gefunden?
+- Die Funktion `scan_html_files` durchsucht rekursiv das Build-Verzeichnis (`build_dir`) nach Dateien mit der Endung `.html`.
+- Versteckte Dateien (beginnend mit `_` oder `.`) werden ignoriert.
+- Relative Pfade werden vom Build-Verzeichnis aus berechnet.
+
+### Welche Verzeichnisse werden durchsucht?
+- Standardmäßig `build/site/html`. Kann über `--build-dir` angepasst werden.
+- Das Skript erstellt ein minimales Build-Verzeichnis mit Beispiel-HTML, falls das Verzeichnis nicht existiert (Zeilen 355–363). Dies ist ein Test-Fallback, der in Produktion nicht auftreten sollte.
+
+### Wie werden URLs konstruiert?
+- Basis-URL wird aus `--base-url` oder Branch-Mapping (`BRANCH_URLS`) bestimmt.
+- Für jede HTML-Datei:
+ - Wenn `rel_path == 'index.html'` → `url_path = ''`
+ - Wenn `rel_path.endswith('/index.html')` → `url_path = rel_path[:-11]` (Entfernt `/index.html`)
+ - Sonst wird `.html`-Endung entfernt (`rel_path[:-5]`)
+- Vollständige URL: `{base_url}/{url_path}` (wenn `url_path` nicht leer)
+
+### Welche Metadaten werden gesetzt?
+- **Priority**: Aus `PRIORITY_MAP` (exakte Übereinstimmung oder Präfix) oder Fallback basierend auf Verzeichnis.
+- **Changefreq**: Aus `CHANGEFREQ_MAP` oder Fallback.
+- **Lastmod**: Git-Änderungsdatum (falls verfügbar), sonst Dateisystem-Modifikationszeit.
+
+## 2. Identifizierte Probleme
+
+### 2.1 Fehlende Build-Verzeichnis-Validierung
+- Das Skript erstellt bei fehlendem Build-Verzeichnis Beispiel-HTML-Dateien (`index.html`, `user-guide/installation.html`). Diese könnten in die Sitemap aufgenommen werden und falsche URLs erzeugen.
+- **Empfehlung**: Statt Beispielen zu erstellen, sollte das Skript mit einem Fehler abbrechen oder zumindest eine klare Warnung ausgeben.
+
+### 2.2 Git-Änderungszeitpunkt unzuverlässig
+- `get_lastmod_for_file` verwendet `cwd=file_path.parent`. Wenn die HTML-Datei außerhalb des Git-Repositories liegt (z.B. im Build-Ordner), schlägt `git log` fehl und es wird die Dateisystem-Modifikationszeit verwendet. Diese kann neuer sein als der tatsächliche Content-Änderungszeitpunkt.
+- **Empfehlung**: Das CWD sollte das Root-Repository sein (`Path.cwd()` oder über `git rev-parse --show-toplevel` ermitteln).
+
+### 2.3 Mapping-Tabellen unvollständig/inkonsistent
+- Die `PRIORITY_MAP` und `CHANGEFREQ_MAP` enthalten Einträge für Dateien, die im aktuellen Test-Build nicht vorhanden sind (z.B. `migration/asyncio-migration.html`, `readme.html`, `changelog.html`, `agents.html`, `devcontainer-environment.html`).
+- Diese Dateien könnten entweder nicht generiert werden oder unter anderen Pfaden liegen. Falls sie fehlen, erhalten sie Fallback-Werte, was nicht unbedingt falsch ist, aber die intendierten Prioritäten/Frequenzen werden nicht angewendet.
+- **Empfehlung**: Mapping-Tabellen mit der tatsächlichen Ausgabe des Dokumentations-Builds abgleichen und ggf. anpassen.
+
+### 2.4 Fehlende Index-HTML-Dateien
+- Im Test-Build fehlen `user-guide/index.html`, `developer-guide/index.html`, `protocol-reference/index.html`. Diese sind in den Mappings enthalten (`priority: 0.8` bzw. `0.7`). Wenn sie nicht generiert werden, fehlen entsprechende Sitemap-Einträge.
+- **Ursache**: Möglicherweise werden diese Index-Dateien nicht von AsciiDoc/Antora erzeugt, weil die entsprechenden `index.adoc`-Dateien existieren. Das Build-System muss überprüft werden.
+- **Empfehlung**: Sicherstellen, dass alle erwarteten HTML-Dateien tatsächlich generiert werden. Andernfalls Mapping-Tabellen bereinigen.
+
+### 2.5 Base-URL für Branches möglicherweise falsch
+- `BRANCH_URLS['main']` ist `https://pysignalduino.rfd-fhem.github.io`. Ist das die korrekte URL für die Hauptdokumentation? Möglicherweise sollte es `https://pysignalduino.github.io` sein.
+- **Empfehlung**: URLs mit den tatsächlichen Deployment-Zielen abgleichen.
+
+### 2.6 Pfadtrenner auf Windows
+- Das Skript verwendet `rel_str = str(rel_path).replace('\\', '/')`. Das ist robust, aber es könnte Probleme geben, wenn Pfade gemischte Schrägstriche enthalten (unwahrscheinlich).
+- Kein kritisches Problem.
+
+### 2.7 Doppelte Slashes in URLs
+- Die Base-URL wird mit `.rstrip('/')` bereinigt. Wenn `url_path` leer ist, wird `full_url = base_url` (ohne trailing slash) korrekt sein. Allerdings erwarten einige Webserver möglicherweise einen trailing slash für die Root-URL. Das ist jedoch kein Sitemap-Problem.
+- **Empfehlung**: Keine Änderung notwendig.
+
+### 2.8 Unvollständige Durchsuchung
+- Das Skript sucht nur nach `.html`-Dateien. Andere Ressourcen (PDF, Bilder) werden ignoriert, was korrekt ist, da Sitemaps typischerweise nur HTML-Seiten enthalten.
+- **Kein Problem**.
+
+### 2.9 Fehlerhafte URL-Konstruktion für "index.html" in Unterverzeichnissen
+- Die Logik `rel_path.endswith('/index.html')` erfasst auch `subdir/index.html`. Das Entfernen von `/index.html` (11 Zeichen) ist korrekt.
+- **Kein Problem**.
+
+## 3. Vergleich mit Dokumentationsstruktur (`docs/`)
+
+### Vorhandene `.adoc`-Dateien:
+- `docs/01_user_guide/index.adoc` → erwartet `user-guide/index.html`
+- `docs/02_developer_guide/index.adoc` → erwartet `developer-guide/index.html`
+- `docs/03_protocol_reference/index.adoc` → erwartet `protocol-reference/index.html`
+- `docs/ASYNCIO_MIGRATION.md` → könnte zu `migration/asyncio-migration.html` werden (wenn konvertiert)
+- `docs/MANCHESTER_MIGRATION.md` → ähnlich
+- `docs/METHODS_MIGRATION_COMPLETE.md` → ähnlich
+- `docs/MIGRATION.md` → ähnlich
+- `docs/SIGNALDUINO_MIGRATION_PLAN.md` → ähnlich
+- `docs/devcontainer_env.md` → `devcontainer-environment.html`
+- `docs/AGENTS.md` (existiert nicht als separate Datei, aber `AGENTS.md` im Root) → `agents.html`
+
+### Diskrepanzen:
+- Viele dieser Migrationsdateien sind `.md`, nicht `.adoc`. Ob sie in HTML umgewandelt werden, hängt vom Build-System ab. Im Test-Build sind sie nicht vorhanden.
+- Die Mapping-Tabellen enthalten Einträge für diese Dateien, aber sie werden möglicherweise nie generiert, was zu fehlenden Sitemap-Einträgen führt.
+
+## 4. Spezifische Probleme, die zur unvollständigen Sitemap führen
+
+1. **Fehlende HTML-Generierung**: Wenn das Build-System nicht alle erwarteten HTML-Dateien erzeugt, fehlen sie in der Sitemap. Das Skript kann nur vorhandene Dateien erfassen.
+
+2. **Falsche Prioritäten/Frequenzen für nicht gemappte Pfade**: Fallback-Logik weist pauschal `priority=0.5` und `changefreq='yearly'` zu, was für bestimmte Seiten unpassend sein könnte.
+
+3. **Git-Lastmod ungenau**: Wenn `git log` fehlschlägt, wird die Dateisystem-Modifikationszeit verwendet, die nicht dem letzten Content-Update entspricht (z.B. bei Neubuild).
+
+4. **Base-URL-Konfiguration**: Wenn die Base-URL falsch ist, sind alle URLs in der Sitemap ungültig.
+
+## 5. Vorschläge zur Behebung
+
+### Kurzfristig (Skript-Anpassungen):
+- **Validierung des Build-Verzeichnisses**: Statt Beispiel-HTML zu erstellen, sollte das Skript einen Fehler ausgeben und den Benutzer auffordern, das Build-Verzeichnis korrekt zu erstellen.
+- **Verbesserte Git-Lastmod**: CWD auf Repository-Root setzen; falls nicht möglich, Fallback auf `git log --all` oder den neuesten Commit, der die Quelldatei (`.adoc`) ändert.
+- **Bereinigung der Mapping-Tabellen**: Entferne Einträge für nicht existierende HTML-Dateien oder passe das Build-System an, damit diese Dateien generiert werden.
+- **Logging verbessern**: Warnung ausgeben, wenn eine Datei in den Mappings nicht gefunden wird.
+
+### Mittelfristig (Build-System-Koordination):
+- **Synchronisation mit Antora/AsciiDoc**: Sicherstellen, dass alle `.adoc`- und `.md`-Dateien in HTML umgewandelt werden und die Pfade mit den Mappings übereinstimmen.
+- **Automatische Generierung der Mapping-Tabellen**: Ein Skript, das die `docs/`-Struktur analysiert und Prioritäten/Frequenzen basierend auf Metadaten (z.B. Frontmatter) zuweist.
+
+### Langfristig (Robustheit):
+- **Integration in CI/CD**: Das Skript sollte nach dem Dokumentations-Build ausgeführt werden, mit korrekter Base-URL je nach Branch.
+- **Validierung der Sitemap**: Nach Generierung sollte die Sitemap auf XML-Konformität und gültige URLs geprüft werden (z.B. mit `validate_sitemap.py`).
+
+## 6. Fazit
+
+Das Sitemap-Generator-Skript ist grundsätzlich funktional, hat jedoch mehrere Schwachstellen, die zu unvollständigen oder fehlerhaften Sitemaps führen können. Die Hauptprobleme liegen in der Diskrepanz zwischen erwarteten und tatsächlich generierten HTML-Dateien sowie in der unzuverlässigen Ermittlung des `lastmod`-Datums.
+
+Durch die oben genannten Vorschläge kann die Zuverlässigkeit und Korrektheit der generierten Sitemap deutlich verbessert werden.
\ No newline at end of file
diff --git a/sitemap_validation_report.md b/sitemap_validation_report.md
new file mode 100644
index 0000000..875a972
--- /dev/null
+++ b/sitemap_validation_report.md
@@ -0,0 +1,115 @@
+# Sitemap-Validierungsbericht
+
+**Datum:** 2025-12-17
+**Sitemap-URL:** https://rfd-fhem.github.io/PySignalduino/sitemap.xml
+**Lokale Datei:** `current_sitemap.xml`
+
+## 1. Herunterladen und XML-Struktur
+
+Die Sitemap wurde erfolgreich heruntergeladen (267 Bytes). Die XML-Struktur ist wohlgeformt und entspricht dem Sitemap-Protokoll.
+
+- **XML-Deklaration:** `` ✓
+- **Root-Element:** `` ✓
+- **Namespace:** korrekt ✓
+
+## 2. Inhalt der Sitemap
+
+Die Sitemap enthält **nur einen einzigen URL-Eintrag**:
+
+```xml
+
+ https://pysignalduino.rfd-fhem.github.io
+ 2025-12-15
+ monthly
+ 1.0
+
+```
+
+### Validierung der einzelnen Felder:
+- ``: vorhanden, absolute URL ✓
+- ``: vorhanden, Format YYYY-MM-DD ✓
+- ``: vorhanden, gültiger Wert (`monthly`) ✓
+- ``: vorhanden, numerischer Wert zwischen 0.0 und 1.0 ✓
+
+**Technisch gesehen ist die Sitemap valide gemäß sitemaps.org.**
+
+## 3. Fehlende Seiten (Vergleich mit erwarteter Dokumentation)
+
+Basierend auf der Projektstruktur (`docs/`) und dem Sitemap-Generierungsskript (`tools/generate_sitemap.py`) werden folgende wichtige Seiten erwartet:
+
+| Kategorie | Erwartete URL (Beispiel) | In Sitemap? |
+|-----------|--------------------------|-------------|
+| Hauptseite | `https://pysignalduino.rfd-fhem.github.io` | ✓ |
+| Benutzerhandbuch | `https://pysignalduino.rfd-fhem.github.io/user-guide/installation` | ✗ |
+| | `https://pysignalduino.rfd-fhem.github.io/user-guide/usage` | ✗ |
+| Entwicklerhandbuch | `https://pysignalduino.rfd-fhem.github.io/developer-guide/architecture` | ✗ |
+| | `https://pysignalduino.rfd-fhem.github.io/developer-guide/contribution` | ✗ |
+| Protokollreferenz | `https://pysignalduino.rfd-fhem.github.io/protocol-reference/protocol-details` | ✗ |
+| Beispiele | `https://pysignalduino.rfd-fhem.github.io/examples/basic-usage` | ✗ |
+| Migrationsdokumente | `https://pysignalduino.rfd-fhem.github.io/migration/asyncio-migration` | ✗ |
+
+**Insgesamt fehlen mindestens 10–15 wichtige Unterseiten.**
+
+## 4. Ursachenanalyse
+
+### 4.1. Basis-URL-Konflikt
+Die Sitemap verwendet die Base-URL `https://pysignalduino.rfd-fhem.github.io`.
+Ein HTTP-Test ergibt jedoch **HTTP 404** für diese URL, was darauf hindeutet, dass die GitHub Pages-Dokumentation möglicherweise nicht unter dieser Adresse veröffentlicht ist.
+
+Die korrekte Dokumentations-URL könnte stattdessen `https://rfd-fhem.github.io/PySignalduino` sein (wie in der `preview`- und `develop`-Branch-Konfiguration des Skripts). Die Sitemap-Generierung für den `main`-Branch verwendet jedoch die oben genannte URL.
+
+### 4.2. Unvollständige Generierung
+Das Sitemap-Generierungsskript scannt das Build-Verzeichnis (`build/site/html`) nach HTML-Dateien. Wenn dieses Verzeichnis leer ist oder nur `index.html` enthält, wird die Sitemap entsprechend knapp.
+
+Möglicherweise wurde die Dokumentation nicht vollständig gebaut, oder der Build-Prozess hat nicht alle HTML-Dateien erzeugt.
+
+### 4.3. Branch-spezifische Unterschiede
+Laut `BRANCH_URLS` im Skript:
+- `main`: `https://pysignalduino.rfd-fhem.github.io`
+- `preview`: `https://preview.rfd-fhem.github.io/PySignalduino`
+- `develop`: `https://develop.rfd-fhem.github.io/PySignalduino`
+
+Die aktuell gehostete Sitemap stammt vom `main`-Branch, aber die Dokumentation könnte unter einer anderen URL liegen.
+
+## 5. Empfehlungen
+
+1. **Überprüfung der GitHub Pages-Konfiguration:**
+ Stellen Sie sicher, dass die Dokumentation unter `https://pysignalduino.rfd-fhem.github.io` tatsächlich erreichbar ist. Falls nicht, passen Sie die Base-URL in `BRANCH_URLS` an.
+
+2. **Vollständige Generierung der Sitemap:**
+ Führen Sie das Sitemap-Generierungsskript mit einem vollständigen Build-Verzeichnis aus, um alle HTML-Dateien zu erfassen:
+ ```bash
+ python3 tools/generate_sitemap.py --build-dir build/site/html --branch main --verbose
+ ```
+
+3. **Validierung der generierten Sitemap:**
+ Nach der Generierung sollten mindestens 15–20 URL-Einträge enthalten sein (entsprechend der Anzahl der `.adoc`-Dateien).
+
+4. **Automatische Integration in CI/CD:**
+ Sicherstellen, dass der GitHub Actions Workflow (`.github/workflows/docs.yml`) die Sitemap-Generierung nach jedem Dokumentations-Build ausführt und die `sitemap.xml` korrekt deployt.
+
+5. **Manuelle Ergänzung fehlender URLs:**
+ Falls bestimmte Seiten absichtlich nicht in der Sitemap erscheinen sollen, prüfen Sie die `PRIORITY_MAP` und `CHANGEFREQ_MAP` im Skript auf Vollständigkeit.
+
+## 6. Zusammenfassung
+
+| Kriterium | Status | Bemerkung |
+|-----------|--------|-----------|
+| XML wohlgeformt | ✓ | Keine Syntaxfehler |
+| Sitemap-Schema konform | ✓ | Korrekte Namespace und Elemente |
+| Anzahl URLs | ❌ | Nur 1 URL (erwartet: >10) |
+| Alle wichtigen Seiten enthalten | ❌ | Fehlen zahlreiche Unterseiten |
+| Absolute URLs | ✓ | `loc` ist absolut |
+| Optionale Felder vorhanden | ✓ | `lastmod`, `changefreq`, `priority` |
+
+**Gesamtbewertung:** Die Sitemap ist **technisch valide, aber inhaltlich unvollständig**. Sie erfüllt nicht den Zweck, Suchmaschinen über die gesamte Dokumentation zu informieren.
+
+## Anhang
+
+- `current_sitemap.xml`: Heruntergeladene Sitemap
+- `test_sitemap.xml`: Beispiel-Sitemap mit erwarteten URLs (generiert mit Test-Build)
+- `validate_sitemap.py`: Validierungsskript
+- `tools/generate_sitemap.py`: Generierungsskript
+
+---
+*Bericht generiert durch automatische Validierung.*
\ No newline at end of file
diff --git a/tests/tools/test_generate_sitemap.py b/tests/tools/test_generate_sitemap.py
index 95046fe..3d6eb8b 100644
--- a/tests/tools/test_generate_sitemap.py
+++ b/tests/tools/test_generate_sitemap.py
@@ -14,6 +14,7 @@
from pathlib import Path
from datetime import datetime
from xml.etree import ElementTree as ET
+from unittest.mock import patch, MagicMock
# Das zu testende Modul importieren
sys.path.insert(0, str(Path(__file__).parent.parent))
@@ -24,6 +25,9 @@
generate_sitemap_urls,
create_xml_sitemap,
BRANCH_URLS,
+ get_lastmod_for_file,
+ get_git_root,
+ main,
)
class TestPriorityMapping:
@@ -42,13 +46,13 @@ def test_protocol_reference_index(self):
assert get_priority_for_path('protocol-reference/index.html') == 0.8
def test_developer_guide_architecture(self):
- assert get_priority_for_path('developer-guide/architecture.html') == 0.7
+ assert get_priority_for_path('developer-guide/architecture.html') == 0.8
def test_examples_general(self):
- assert get_priority_for_path('examples/some-example.html') == 0.2
+ assert get_priority_for_path('examples/some-example.html') == 0.3
def test_migration_general(self):
- assert get_priority_for_path('migration/some-doc.html') == 0.1
+ assert get_priority_for_path('migration/some-doc.html') == 0.2
def test_unknown_path(self):
assert get_priority_for_path('unknown/path.html') == 0.5
@@ -239,6 +243,197 @@ def test_main_branch_url(self):
def test_preview_branch_url(self):
assert BRANCH_URLS['preview'] == 'https://preview.rfd-fhem.github.io/PySignalduino'
+
+class TestScanHtmlFilesEdgeCases:
+ """Tests für Edge Cases beim Scannen von HTML-Dateien."""
+
+ def test_nonexistent_directory(self):
+ """Teste, dass scan_html_files mit nicht existierendem Verzeichnis umgeht."""
+ non_existent = Path('/nonexistent/path')
+ files = scan_html_files(non_existent)
+ assert len(files) == 0
+
+ def test_non_html_files_ignored(self):
+ """Teste, dass nur .html-Dateien gescannt werden."""
+ with tempfile.TemporaryDirectory() as tmp:
+ build_dir = Path(tmp) / 'build' / 'site' / 'html'
+ build_dir.mkdir(parents=True)
+ (build_dir / 'index.txt').write_text('text')
+ (build_dir / 'image.png').write_text('png')
+ (build_dir / 'index.html').write_text('')
+
+ files = scan_html_files(build_dir)
+ paths = [f['path'] for f in files]
+ assert 'index.html' in paths
+ assert len(files) == 1
+
+
+class TestLastModFunction:
+ """Tests für die get_lastmod_for_file Funktion."""
+
+ @patch('tools.generate_sitemap.get_git_root')
+ @patch('tools.generate_sitemap.subprocess.run')
+ def test_get_lastmod_for_file_with_git(self, mock_run, mock_get_git_root):
+ """Teste, dass Git-Log verwendet wird, wenn verfügbar."""
+ with tempfile.NamedTemporaryFile(suffix='.html') as f:
+ file_path = Path(f.name)
+ # Mock get_git_root, um das Elternverzeichnis der Datei zurückzugeben
+ mock_get_git_root.return_value = file_path.parent
+ # Mock subprocess.run für git log
+ mock_git_log = MagicMock()
+ mock_git_log.returncode = 0
+ mock_git_log.stdout = '2025-12-14\n'
+ mock_run.return_value = mock_git_log
+
+ result = get_lastmod_for_file(file_path)
+
+ assert result == '2025-12-14'
+ # Überprüfe, dass get_git_root aufgerufen wurde
+ mock_get_git_root.assert_called_once()
+ # Überprüfe, dass subprocess.run für git log aufgerufen wurde
+ mock_run.assert_called_once()
+
+ @patch('tools.generate_sitemap.subprocess.run')
+ def test_get_lastmod_for_file_without_git(self, mock_run):
+ """Teste Fallback auf Dateisystem-Modifikationszeit."""
+ mock_run.return_value.returncode = 1 # Git nicht verfügbar
+
+ with tempfile.NamedTemporaryFile(suffix='.html') as f:
+ file_path = Path(f.name)
+ # Setze eine bekannte Modifikationszeit
+ import os
+ import time
+ test_time = time.mktime((2025, 12, 13, 12, 0, 0, 0, 0, 0))
+ os.utime(f.name, (test_time, test_time))
+
+ result = get_lastmod_for_file(file_path)
+
+ assert result == '2025-12-13'
+
+ @patch('tools.generate_sitemap.subprocess.run')
+ def test_get_lastmod_for_file_git_error(self, mock_run):
+ """Teste, dass Git-Fehler abgefangen werden."""
+ mock_run.side_effect = FileNotFoundError() # Git nicht installiert
+
+ with tempfile.NamedTemporaryFile(suffix='.html') as f:
+ file_path = Path(f.name)
+ import os
+ import time
+ test_time = time.mktime((2025, 12, 10, 12, 0, 0, 0, 0, 0))
+ os.utime(f.name, (test_time, test_time))
+
+ result = get_lastmod_for_file(file_path)
+
+ assert result == '2025-12-10'
+
+
+class TestMainFunction:
+ """Tests für die Hauptfunktion main()."""
+
+ @patch('tools.generate_sitemap.sys.exit')
+ @patch('tools.generate_sitemap.logger')
+ def test_main_missing_build_dir(self, mock_logger, mock_exit):
+ """Teste, dass main bei fehlendem Build-Verzeichnis mit Fehler beendet."""
+ import sys
+ sys.argv = ['generate_sitemap.py', '--build-dir', '/nonexistent']
+
+ main()
+
+ # Überprüfe, dass sys.exit(1) aufgerufen wurde
+ mock_exit.assert_called_with(1)
+ # Überprüfe, dass eine Fehlermeldung geloggt wurde
+ assert mock_logger.error.called
+
+ @patch('tools.generate_sitemap.scan_html_files')
+ @patch('tools.generate_sitemap.generate_sitemap_urls')
+ @patch('tools.generate_sitemap.create_xml_sitemap')
+ @patch('tools.generate_sitemap.write_sitemap')
+ @patch('tools.generate_sitemap.Path')
+ def test_main_with_branch_arg(self, mock_path, mock_write, mock_create, mock_generate, mock_scan):
+ """Teste, dass --branch korrekt verarbeitet wird."""
+ import sys
+ sys.argv = [
+ 'generate_sitemap.py',
+ '--branch', 'preview',
+ '--build-dir', 'build/site/html',
+ '--output', 'sitemap.xml'
+ ]
+
+ # Mock Path.exists() um True zurückzugeben
+ mock_path_instance = MagicMock()
+ mock_path_instance.exists.return_value = True
+ mock_path.return_value = mock_path_instance
+
+ # Mock die Abhängigkeiten
+ mock_scan.return_value = []
+ mock_generate.return_value = []
+ mock_create.return_value = MagicMock()
+
+ main()
+
+ # Überprüfe, dass generate_sitemap_urls mit der korrekten Base-URL aufgerufen wurde
+ mock_generate.assert_called_once()
+ # Die Base-URL sollte die für 'preview' sein
+ call_args = mock_generate.call_args
+ assert call_args[0][1] == 'https://preview.rfd-fhem.github.io/PySignalduino'
+
+ @patch('tools.generate_sitemap.scan_html_files')
+ @patch('tools.generate_sitemap.generate_sitemap_urls')
+ @patch('tools.generate_sitemap.create_xml_sitemap')
+ @patch('tools.generate_sitemap.write_sitemap')
+ @patch('tools.generate_sitemap.Path')
+ def test_main_with_base_url_arg(self, mock_path, mock_write, mock_create, mock_generate, mock_scan):
+ """Teste, dass --base-url Vorrang vor --branch hat."""
+ import sys
+ sys.argv = [
+ 'generate_sitemap.py',
+ '--branch', 'preview',
+ '--base-url', 'https://custom.example.com',
+ '--build-dir', 'build/site/html',
+ '--output', 'sitemap.xml'
+ ]
+
+ # Mock Path.exists()
+ mock_path_instance = MagicMock()
+ mock_path_instance.exists.return_value = True
+ mock_path.return_value = mock_path_instance
+
+ mock_scan.return_value = []
+ mock_generate.return_value = []
+ mock_create.return_value = MagicMock()
+
+ main()
+
+ call_args = mock_generate.call_args
+ assert call_args[0][1] == 'https://custom.example.com'
+
+
+class TestPriorityChangefreqMappingUpdates:
+ """Tests für aktualisierte Mapping-Tabellen."""
+
+ def test_new_priority_mappings(self):
+ """Teste neue Einträge in PRIORITY_MAP."""
+ # devcontainer-environment.html
+ assert get_priority_for_path('devcontainer-environment.html') == 0.3
+ # agents.html
+ assert get_priority_for_path('agents.html') == 0.3
+ # readme.html
+ assert get_priority_for_path('readme.html') == 0.3
+ # migration/asyncio-migration.html
+ assert get_priority_for_path('migration/asyncio-migration.html') == 0.2
+
+ def test_new_changefreq_mappings(self):
+ """Teste neue Einträge in CHANGEFREQ_MAP."""
+ # devcontainer-environment.html
+ assert get_changefreq_for_path('devcontainer-environment.html') == 'yearly'
+ # agents.html
+ assert get_changefreq_for_path('agents.html') == 'monthly'
+ # readme.html
+ assert get_changefreq_for_path('readme.html') == 'monthly'
+ # migration/asyncio-migration.html
+ assert get_changefreq_for_path('migration/asyncio-migration.html') == 'never'
+
+
def test_integration_with_cli(tmp_path):
"""Integrationstest: Führe das Skript mit einem temporären Build-Verzeichnis aus."""
import subprocess
diff --git a/tools/generate_sitemap.py b/tools/generate_sitemap.py
index a6f7f40..481bea0 100644
--- a/tools/generate_sitemap.py
+++ b/tools/generate_sitemap.py
@@ -31,46 +31,68 @@
logger = logging.getLogger(__name__)
# Mapping von Dateipfad-Mustern zu Prioritäten und Update-Frequenzen
+# Basierend auf der tatsächlichen Dokumentationsstruktur und erwarteten HTML-Dateien
PRIORITY_MAP = {
'index.html': 1.0,
'user-guide/installation.html': 0.9,
'user-guide/usage.html': 0.9,
'user-guide/index.html': 0.8,
+ 'developer-guide/architecture.html': 0.8,
+ 'developer-guide/contribution.html': 0.7,
+ 'developer-guide/index.html': 0.8,
+ 'protocol-reference/protocol-details.html': 0.7,
'protocol-reference/index.html': 0.8,
- 'developer-guide/architecture.html': 0.7,
- 'developer-guide/index.html': 0.7,
- 'migration/asyncio-migration.html': 0.6,
'examples/basic-usage.html': 0.6,
'examples/mqtt-integration.html': 0.6,
- 'readme.html': 0.5,
- 'developer-guide/contribution.html': 0.5,
- 'migration/manchester-migration.html': 0.4,
- 'migration/methods-migration-complete.html': 0.4,
- 'examples/index.html': 0.4,
- 'protocol-reference/protocol-details.html': 0.4,
- 'examples/bash/index.html': 0.3,
+ 'examples/command-api-example.html': 0.5,
+ 'examples/logging-callback.html': 0.5,
+ 'examples/logging-debug.html': 0.5,
+ 'examples/mocking-async.html': 0.5,
+ 'examples/mqtt-publisher-example.html': 0.5,
+ 'examples/nested-context-manager.html': 0.5,
+ 'examples/test-example.html': 0.5,
+ 'examples/bash/coverage-report.html': 0.4,
+ 'examples/bash/format-code.html': 0.4,
+ 'examples/bash/install-dev-deps.html': 0.4,
+ 'examples/bash/install-dev-requirements.html': 0.4,
+ 'examples/bash/install-requirements.html': 0.4,
+ 'examples/bash/install-via-pip.html': 0.4,
+ 'examples/bash/mosquitto-pub-example.html': 0.4,
+ 'examples/bash/run-pytest.html': 0.4,
+ 'examples/bash/run-specific-tests.html': 0.4,
+ 'examples/bash/update-dependencies.html': 0.4,
+ 'examples/bash/verify-installation.html': 0.4,
+ 'examples/': 0.3, # Allgemeine Beispiele
+ 'examples/bash/': 0.3,
+ 'migration/': 0.2, # Migrationsdokumente (falls generiert)
+ 'migration/asyncio-migration.html': 0.2,
+ 'migration/manchester-migration.html': 0.2,
+ 'migration/methods-migration-complete.html': 0.2,
+ 'migration/signalduino-migration-plan.html': 0.2,
+ 'migration/manchester-integration-complete.html': 0.2,
'devcontainer-environment.html': 0.3,
- 'agents.html': 0.2,
- 'changelog.html': 0.2,
- 'examples/': 0.2, # Allgemeine Beispiele
- 'migration/': 0.1, # Weitere Migrationsdokumente
+ 'agents.html': 0.3,
+ 'changelog.html': 0.3,
+ 'readme.html': 0.3,
}
CHANGEFREQ_MAP = {
'index.html': 'monthly',
'user-guide/installation.html': 'yearly',
'user-guide/usage.html': 'yearly',
- 'protocol-reference/index.html': 'monthly',
+ 'user-guide/index.html': 'yearly',
'developer-guide/architecture.html': 'yearly',
- 'readme.html': 'monthly',
- 'changelog.html': 'weekly',
- 'migration/asyncio-migration.html': 'never',
- 'migration/manchester-migration.html': 'never',
- 'migration/methods-migration-complete.html': 'never',
+ 'developer-guide/contribution.html': 'yearly',
+ 'developer-guide/index.html': 'yearly',
+ 'protocol-reference/protocol-details.html': 'monthly',
+ 'protocol-reference/index.html': 'monthly',
'examples/': 'yearly',
'examples/bash/': 'yearly',
+ 'migration/': 'never',
'devcontainer-environment.html': 'yearly',
'agents.html': 'monthly',
+ 'changelog.html': 'weekly',
+ 'readme.html': 'monthly',
}
# Branch-spezifische Base-URLs
@@ -135,20 +157,42 @@ def get_changefreq_for_path(file_path: str) -> str:
else:
return 'yearly'
-def get_lastmod_for_file(file_path: Path) -> str:
- """Ermittle das letzte Änderungsdatum einer Datei."""
+def get_git_root(start_path: Path) -> Path | None:
+ """Finde das Git-Repository-Root-Verzeichnis."""
try:
- # Versuche, den Git-Änderungszeitpunkt zu ermitteln (falls verfügbar)
result = subprocess.run(
- ['git', 'log', '-1', '--format=%cd', '--date=short', '--', str(file_path)],
+ ['git', 'rev-parse', '--show-toplevel'],
capture_output=True,
text=True,
- cwd=file_path.parent
+ cwd=start_path,
+ check=False
)
- if result.returncode == 0 and result.stdout.strip():
- return result.stdout.strip()
+ if result.returncode == 0:
+ return Path(result.stdout.strip())
except (subprocess.CalledProcessError, FileNotFoundError):
pass
+ return None
+
+def get_lastmod_for_file(file_path: Path) -> str:
+ """Ermittle das letzte Änderungsdatum einer Datei."""
+ # Versuche, den Git-Änderungszeitpunkt zu ermitteln (falls verfügbar)
+ # Zuerst das Git-Repository-Root finden
+ git_root = get_git_root(file_path.parent)
+ if git_root:
+ try:
+ # Pfad relativ zum Git-Root
+ rel_path = file_path.relative_to(git_root)
+ result = subprocess.run(
+ ['git', 'log', '-1', '--format=%cd', '--date=short', '--', str(rel_path)],
+ capture_output=True,
+ text=True,
+ cwd=git_root
+ )
+ if result.returncode == 0 and result.stdout.strip():
+ return result.stdout.strip()
+ except (ValueError, subprocess.CalledProcessError, FileNotFoundError):
+ # Datei nicht innerhalb des Git-Repos oder anderer Fehler
+ pass
# Fallback: Dateisystem-Modifikationszeit
mtime = file_path.stat().st_mtime
@@ -354,12 +398,8 @@ def main():
build_dir = Path(args.build_dir)
if not build_dir.exists():
logger.error(f"Build-Verzeichnis existiert nicht: {build_dir}")
- logger.info("Erstelle Beispiel-HTML-Dateien für Testzwecke...")
- # Für Testzwecke: Erstelle ein minimales Build-Verzeichnis
- build_dir.mkdir(parents=True, exist_ok=True)
- (build_dir / 'index.html').write_text('')
- (build_dir / 'user-guide').mkdir(exist_ok=True)
- (build_dir / 'user-guide' / 'installation.html').write_text('')
+ logger.error("Bitte führen Sie zuerst den Dokumentations-Build aus (z.B. 'make html' oder 'antora site.yml').")
+ sys.exit(1)
# HTML-Dateien scannen
html_files = scan_html_files(build_dir)
From d8b2b19e0c32c0b20ed65b7c3459f4e473928767 Mon Sep 17 00:00:00 2001
From: sidey79 <7968127+sidey79@users.noreply.github.com>
Date: Wed, 17 Dec 2025 22:23:44 +0000
Subject: [PATCH 2/4] fix: missng aiomqtt
---
requirements.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index eab83a4..dc6000f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,5 @@ requests
paho-mqtt
python-dotenv
asyncio-mqtt
-pyserial-asyncio
\ No newline at end of file
+pyserial-asyncio
+aiomqtt
\ No newline at end of file
From ba571f151bce85ba10f8984d74a31e78ffeb76a4 Mon Sep 17 00:00:00 2001
From: sidey79 <7968127+sidey79@users.noreply.github.com>
Date: Thu, 18 Dec 2025 21:31:36 +0000
Subject: [PATCH 3/4] fix: deploy docs only from main
---
.github/workflows/docs.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 973cd2d..8d900d9 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -87,8 +87,9 @@ jobs:
path: build/site/html
deploy:
+ if: github.ref == 'refs/heads/main'
permissions:
- contents: write
+ contents: write
pages: write
id-token: write
environment:
From 8f8c209bfe4b24dd6fef20f027869fcc12a21c1e Mon Sep 17 00:00:00 2001
From: sidey79 <7968127+sidey79@users.noreply.github.com>
Date: Thu, 18 Dec 2025 22:39:45 +0000
Subject: [PATCH 4/4] fix: generate_sitemap.py
---
README.md | 10 ++++++++++
tools/generate_sitemap.py | 11 +++++++++--
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 5ea0f9c..455ac65 100644
--- a/README.md
+++ b/README.md
@@ -158,6 +158,16 @@ Beiträge sind willkommen! Bitte erstelle einen Pull‑Request oder öffne ein I
* [Protokollreferenz](docs/03_protocol_reference/protocol_details.adoc)
* [Befehlsreferenz](docs/01_user_guide/usage.adoc#_command_interface)
+## SEO & Sitemap
+
+Die Dokumentation wird automatisch mit einer dynamischen Sitemap (`sitemap.xml`) und branch‑spezifischen `robots.txt`‑Dateien versehen, um die Auffindbarkeit in Suchmaschinen zu verbessern.
+
+* **Sitemap‑Generierung:** Das Skript `tools/generate_sitemap.py` scannt den Build‑Output, weist Prioritäten und Update‑Frequenzen zu und generiert eine valide XML‑Sitemap gemäß sitemaps.org.
+* **Branch‑spezifische URLs:** Für die Branches `main`, `preview` und `develop` werden unterschiedliche Base‑URLs verwendet.
+* **Integration in CI/CD:** Der GitHub Actions Workflow `.github/workflows/docs.yml` generiert die Sitemap automatisch nach jedem Build und passt die `robots.txt` entsprechend an.
+
+Weitere Details zur Architektur finden Sie im [Architektur‑Dokument](docs/02_developer_guide/architecture.adoc#dokumentations-infrastruktur-sitemap--seo).
+
## Lizenz
Dieses Projekt steht unter der MIT‑Lizenz – siehe [LICENSE](LICENSE) für Details.
diff --git a/tools/generate_sitemap.py b/tools/generate_sitemap.py
index 481bea0..4e67fb8 100644
--- a/tools/generate_sitemap.py
+++ b/tools/generate_sitemap.py
@@ -367,7 +367,6 @@ def main():
)
parser.add_argument(
'--branch',
- choices=list(BRANCH_URLS.keys()),
help='Git-Branch zur Bestimmung der Base-URL'
)
parser.add_argument(
@@ -386,7 +385,15 @@ def main():
if args.base_url:
base_url = args.base_url.rstrip('/')
elif args.branch:
- base_url = BRANCH_URLS.get(args.branch, BRANCH_URLS['main'])
+ if args.branch in BRANCH_URLS:
+ base_url = BRANCH_URLS[args.branch]
+ else:
+ # Fallback für unbekannte Branches (Feature-Branches)
+ base_url = BRANCH_URLS['preview']
+ logger.warning(
+ f"Unbekannter Branch '{args.branch}'. "
+ f"Verwende Preview-URL als Fallback: {base_url}"
+ )
else:
# Standard-URL für main-Branch
base_url = BRANCH_URLS['main']