From: Danilo M. Date: Sun, 3 May 2026 14:09:38 +0000 (+0200) Subject: feat: propagate tag renames to articles after taxonomy save X-Git-Tag: v1.3~2 X-Git-Url: https://git.danix.xyz/?a=commitdiff_plain;h=2319a6bf3c32407d387c45a522f20e23b7ba885f;p=publisher.git feat: propagate tag renames to articles after taxonomy save After saving TaxonomyView, detect renamed IT/EN tags by comparing old and new it_to_en dicts, then rewrite frontmatter of affected articles. Status bar shows count of updated articles. Taxonomy save failure aborts propagation. Adds pytest-qt and four unit tests for _detect_renames. Co-Authored-By: Claude Sonnet 4.6 --- diff --git a/requirements.txt b/requirements.txt index 112f3ac..1e1e770 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ PyQt6>=6.10.0,<6.11.0 tomlkit>=0.12.0 mistune>=3.0.0 +pytest-qt>=4.0.0 diff --git a/tests/test_taxonomy.py b/tests/test_taxonomy.py index a10531b..b5f48e9 100644 --- a/tests/test_taxonomy.py +++ b/tests/test_taxonomy.py @@ -2,6 +2,7 @@ import pytest from pathlib import Path from core.taxonomy import TaxonomyModel, load_taxonomy, save_taxonomy +from ui.taxonomy_view import TaxonomyView def _write_pair(tmp_path, it_lines, en_lines): it_file = tmp_path / "tags-it.txt" @@ -41,3 +42,35 @@ def test_save_taxonomy_sorted(tmp_path): save_taxonomy(model, it_f, en_f) lines = it_f.read_text().splitlines() assert lines == sorted(lines) + + +def test_detect_renames_en_change(qtbot): + old = {"linux": "linux", "vita": "life"} + new = {"linux": "linux", "vita": "living"} + it_r, en_r = TaxonomyView._detect_renames(old, new) + assert it_r == {} + assert en_r == {"life": "living"} + + +def test_detect_renames_it_change(qtbot): + old = {"linux": "linux", "vita": "life"} + new = {"linux": "linux", "living": "life"} + it_r, en_r = TaxonomyView._detect_renames(old, new) + assert it_r == {"vita": "living"} + assert en_r == {} + + +def test_detect_renames_no_change(qtbot): + old = {"linux": "linux"} + new = {"linux": "linux"} + it_r, en_r = TaxonomyView._detect_renames(old, new) + assert it_r == {} + assert en_r == {} + + +def test_detect_renames_addition_not_rename(qtbot): + old = {"linux": "linux"} + new = {"linux": "linux", "vita": "life"} + it_r, en_r = TaxonomyView._detect_renames(old, new) + assert it_r == {} + assert en_r == {} diff --git a/ui/taxonomy_view.py b/ui/taxonomy_view.py index 6efbffd..4862833 100644 --- a/ui/taxonomy_view.py +++ b/ui/taxonomy_view.py @@ -9,6 +9,7 @@ from PyQt6.QtCore import Qt from PyQt6.QtGui import QColor from core.taxonomy import TaxonomyModel, load_taxonomy, save_taxonomy, load_categories, save_categories from core.article_scanner import scan_articles +from core.frontmatter import parse_frontmatter, write_frontmatter class AddTermDialog(QDialog): def __init__(self, parent=None): @@ -210,9 +211,50 @@ class TaxonomyView(QWidget): save_categories(self._categories_path, self._categories) self._status.setText("Categorie salvate.") + @staticmethod + def _detect_renames( + old: dict[str, str], new: dict[str, str] + ) -> tuple[dict[str, str], dict[str, str]]: + it_renames: dict[str, str] = {} + en_renames: dict[str, str] = {} + old_en_to_it = {v: k for k, v in old.items()} + for new_it, new_en in new.items(): + if new_it not in old: + if new_en in old_en_to_it: + it_renames[old_en_to_it[new_en]] = new_it + else: + if old[new_it] != new_en: + en_renames[old[new_it]] = new_en + return it_renames, en_renames + + def _propagate_renames(self, it_renames: dict[str, str], en_renames: dict[str, str]) -> int: + if not it_renames and not en_renames: + return 0 + articles = scan_articles(self._blog_root) + count = 0 + for article in articles: + renames = it_renames if article.lang == "it" else en_renames + if not renames: + continue + current_tags = article.frontmatter.get("tags", []) + if not isinstance(current_tags, list): + continue + new_tags = [renames.get(str(t), str(t)) for t in current_tags] + if new_tags == [str(t) for t in current_tags]: + continue + try: + fm, body = parse_frontmatter(article.path) + fm["tags"] = new_tags + write_frontmatter(article.path, fm, body) + count += 1 + except Exception: + pass + return count + def _save(self): if not self._model: return + old_it_to_en = dict(self._model.it_to_en) updated: dict[str, str] = {} for row in range(self._tags_table.rowCount()): it_item = self._tags_table.item(row, 0) @@ -223,5 +265,14 @@ class TaxonomyView(QWidget): if it_val and en_val and en_val != "⚠ mancante": updated[it_val] = en_val self._model.it_to_en = updated - save_taxonomy(self._model, self._tags_it, self._tags_en) - self._status.setText("Salvato.") + try: + save_taxonomy(self._model, self._tags_it, self._tags_en) + except OSError as e: + QMessageBox.critical(self, "Errore", f"Impossibile salvare: {e}") + return + it_renames, en_renames = self._detect_renames(old_it_to_en, updated) + count = self._propagate_renames(it_renames, en_renames) + if count: + self._status.setText(f"Salvato · {count} articol{'o' if count == 1 else 'i'} aggiornati.") + else: + self._status.setText("Salvato.")