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']