v0.6.1
Fix gui_schemes
This commit is contained in:
+1
-1
@@ -655,7 +655,7 @@ Rel_Triggering(investigate, close)
|
||||
- Полная Appendix B в sample отсутствует — матрица пар здесь практическая, не нормативная таблица B.5.
|
||||
- PDF спецификации в git не хранятся.
|
||||
- Старый PlantUML/Kroki без stdlib 3.2: fallback — `` ```uml `` + keyword `archimate`.
|
||||
- Пользовательский `md2gost.schemes.json`: встроенные схемы с `author: md2gost` синхронизируются с бандлом; свои правки (другой author) не затираются.
|
||||
- Пользовательский `md2gost.schemes.json` при запуске только дополняется отсутствующими встроенными схемами. Уже записанные правки не затираются.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ Rel(user, app, "логин")
|
||||
|
||||
## Встроенные схемы
|
||||
|
||||
Файл шаблона: [`md2gost/diagrams/schemes.json`](../md2gost/diagrams/schemes.json). При первом запуске копируется в `md2gost.schemes.json` рядом с приложением. При каждом запуске встроенные схемы (`author: md2gost`) синхронизируются с шаблоном: новые id добавляются, устаревшие (например удалённый `bpmn`) убираются, содержимое обновляется. Схемы с пустым или другим `author` не трогаются. Если правите встроенную в GUI и сохраняете — `author` сбрасывается, чтобы миграция не затёрла правки.
|
||||
Файл шаблона: [`md2gost/diagrams/schemes.json`](../md2gost/diagrams/schemes.json). При первом запуске копируется в `md2gost.schemes.json` рядом с приложением. При каждом запуске в файл добавляются только отсутствующие встроенные схемы. Уже записанные схемы не перезаписываются и не удаляются: правки и свои шаблоны сохраняются. Файл, который не удалось разобрать, не затирается. Если правите встроенную схему в GUI и сохраняете — `author` сбрасывается, схема помечается как ваша.
|
||||
|
||||
| id | Название | Оградка |
|
||||
|----|----------|---------|
|
||||
@@ -291,4 +291,4 @@ id схемы: латиница, цифры, `_`; начинается с бук
|
||||
2. Или править `md2gost.schemes.json` вручную («Открыть JSON»).
|
||||
3. CLI: `--schemes путь.json`.
|
||||
|
||||
Чтобы правка встроенной схемы не сбрасывалась при обновлении приложения, оставьте пустой `author` (GUI делает это сама при сохранении изменённой встроенной схемы).
|
||||
Запуск программы не сбрасывает правки в `md2gost.schemes.json`. В GUI кнопка «Добавить» сразу записывает новую схему (`custom1`, `custom2`, …) в этот файл.
|
||||
|
||||
+95
-55
@@ -114,67 +114,48 @@ def is_app_owned_scheme(scheme: DiagramScheme) -> bool:
|
||||
return scheme.author.strip().lower() == APP_SCHEME_AUTHOR
|
||||
|
||||
|
||||
def _schemes_payload(schemes: dict[str, DiagramScheme]) -> dict[str, Any]:
|
||||
return {sid: scheme.to_dict() for sid, scheme in sorted(schemes.items())}
|
||||
|
||||
|
||||
def migrate_user_schemes(base: Path | None = None) -> SchemesMigrationResult:
|
||||
"""
|
||||
Sync app-owned schemes in the user file with the bundled template.
|
||||
Add bundled schemes that are missing from the user file.
|
||||
|
||||
- Bundled ids: always present; overwrite user entries with author=md2gost.
|
||||
- User overrides of bundled ids (empty/other author): kept as-is.
|
||||
- Orphaned author=md2gost ids no longer in the template: removed.
|
||||
- Other custom schemes: kept.
|
||||
Existing entries are kept as written: edits of built-in ids, custom
|
||||
schemes, and schemes with author=md2gost are not overwritten or removed.
|
||||
A file that cannot be parsed is left untouched.
|
||||
"""
|
||||
result = SchemesMigrationResult()
|
||||
path = user_schemes_path(base)
|
||||
if not path.is_file():
|
||||
return result
|
||||
|
||||
bundled = _load_schemes_file(bundled_schemes_path())
|
||||
user = _load_schemes_file(path)
|
||||
out: dict[str, DiagramScheme] = {}
|
||||
|
||||
for sid, scheme in user.items():
|
||||
if sid in bundled:
|
||||
if is_app_owned_scheme(scheme):
|
||||
continue # step below writes bundled
|
||||
out[sid] = scheme
|
||||
continue
|
||||
if is_app_owned_scheme(scheme):
|
||||
result.removed.append(sid)
|
||||
_log.info("Миграция схем: удалена устаревшая встроенная «%s»", sid)
|
||||
continue
|
||||
out[sid] = scheme
|
||||
|
||||
for sid, scheme in bundled.items():
|
||||
if sid in out:
|
||||
continue # user-owned override
|
||||
prev = user.get(sid)
|
||||
if prev is None:
|
||||
result.added.append(sid)
|
||||
_log.info("Миграция схем: добавлена «%s»", sid)
|
||||
elif prev.to_dict() != scheme.to_dict():
|
||||
result.updated.append(sid)
|
||||
_log.info("Миграция схем: обновлена «%s»", sid)
|
||||
out[sid] = scheme
|
||||
|
||||
if _schemes_payload(out) == _schemes_payload(user):
|
||||
result.added.clear()
|
||||
result.updated.clear()
|
||||
result.removed.clear()
|
||||
raw, err = _read_json_object(path)
|
||||
if err or raw is None:
|
||||
_log.warning("Миграция схем пропущена, файл не изменён (%s): %s", path, err)
|
||||
return result
|
||||
|
||||
save_schemes(out, path)
|
||||
bundled = _load_schemes_file(bundled_schemes_path())
|
||||
existing = {str(key).lower().strip() for key in raw}
|
||||
for sid, scheme in bundled.items():
|
||||
if sid in existing:
|
||||
continue
|
||||
raw[sid] = scheme.to_dict()
|
||||
result.added.append(sid)
|
||||
_log.info("Миграция схем: добавлена «%s»", sid)
|
||||
|
||||
if not result.added:
|
||||
return result
|
||||
|
||||
path.write_text(
|
||||
json.dumps(raw, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
result.written = True
|
||||
return result
|
||||
|
||||
|
||||
def ensure_user_schemes(base: Path | None = None) -> Path:
|
||||
"""
|
||||
Ensure md2gost.schemes.json exists next to the app, then sync app-owned
|
||||
schemes from the bundled template (add / update / remove orphans).
|
||||
Ensure md2gost.schemes.json exists next to the app, then add any
|
||||
bundled schemes that are not already in the user file.
|
||||
"""
|
||||
dest = user_schemes_path(base)
|
||||
if not dest.is_file():
|
||||
@@ -190,16 +171,21 @@ def ensure_user_schemes(base: Path | None = None) -> Path:
|
||||
return dest
|
||||
|
||||
|
||||
def _load_schemes_file(path: Path) -> dict[str, DiagramScheme]:
|
||||
def _read_json_object(path: Path) -> tuple[dict[str, Any] | None, str | None]:
|
||||
"""Return (object, None). Missing file is ({}, None). Unreadable is (None, error)."""
|
||||
if not path.is_file():
|
||||
return {}
|
||||
return {}, None
|
||||
try:
|
||||
raw = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
_log.warning("Не удалось прочитать схемы %s: %s", path, exc)
|
||||
return {}
|
||||
# utf-8-sig: Notepad on Windows often writes a BOM, which json.loads rejects.
|
||||
raw = json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
||||
return None, str(exc)
|
||||
if not isinstance(raw, dict):
|
||||
return {}
|
||||
return None, "корень JSON должен быть объектом"
|
||||
return raw, None
|
||||
|
||||
|
||||
def _schemes_from_raw(raw: dict[str, Any]) -> dict[str, DiagramScheme]:
|
||||
out: dict[str, DiagramScheme] = {}
|
||||
for key, val in raw.items():
|
||||
sid = str(key).lower().strip()
|
||||
@@ -212,16 +198,70 @@ def _load_schemes_file(path: Path) -> dict[str, DiagramScheme]:
|
||||
return out
|
||||
|
||||
|
||||
def _load_schemes_file(path: Path) -> dict[str, DiagramScheme]:
|
||||
raw, err = _read_json_object(path)
|
||||
if err or raw is None:
|
||||
if path.is_file():
|
||||
_log.warning("Не удалось прочитать схемы %s: %s", path, err)
|
||||
return {}
|
||||
return _schemes_from_raw(raw)
|
||||
|
||||
|
||||
def load_schemes_from_path(path: Path) -> dict[str, DiagramScheme]:
|
||||
return _load_schemes_file(path)
|
||||
|
||||
|
||||
def save_schemes(schemes: dict[str, DiagramScheme], path: Path | None = None) -> Path:
|
||||
def load_schemes_report(path: Path) -> tuple[dict[str, DiagramScheme], str | None]:
|
||||
"""Load schemes. Error string is set when the file exists but cannot be parsed."""
|
||||
raw, err = _read_json_object(path)
|
||||
if err or raw is None:
|
||||
if path.is_file():
|
||||
_log.warning("Не удалось прочитать схемы %s: %s", path, err)
|
||||
return {}, err
|
||||
return _schemes_from_raw(raw), None
|
||||
|
||||
|
||||
def save_schemes(
|
||||
schemes: dict[str, DiagramScheme],
|
||||
path: Path | None = None,
|
||||
*,
|
||||
removed_ids: set[str] | None = None,
|
||||
) -> Path:
|
||||
"""
|
||||
Write schemes, keeping JSON entries the loader does not understand
|
||||
(unknown ids, extra fields). ``removed_ids`` are normalized ids the user
|
||||
deleted or renamed away.
|
||||
"""
|
||||
dest = path or user_schemes_path()
|
||||
payload = _schemes_payload(schemes)
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
raw, err = _read_json_object(dest)
|
||||
if err or raw is None:
|
||||
raw = {}
|
||||
removed = {sid.lower() for sid in (removed_ids or set())}
|
||||
norm_to_key: dict[str, str] = {}
|
||||
for key in list(raw):
|
||||
sid = str(key).lower().strip()
|
||||
if sid in removed:
|
||||
del raw[key]
|
||||
continue
|
||||
norm_to_key.setdefault(sid, str(key))
|
||||
|
||||
for sid in sorted(schemes):
|
||||
scheme = schemes[sid]
|
||||
data = scheme.to_dict()
|
||||
key = norm_to_key.get(sid)
|
||||
if key is None:
|
||||
raw[sid] = data
|
||||
continue
|
||||
base = raw.get(key)
|
||||
merged = dict(base) if isinstance(base, dict) else {}
|
||||
merged.update(data)
|
||||
if not scheme.theme:
|
||||
merged.pop("theme", None)
|
||||
raw[key] = merged
|
||||
|
||||
dest.write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
|
||||
json.dumps(raw, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return dest
|
||||
@@ -234,7 +274,7 @@ def claim_user_scheme_if_diverged(
|
||||
) -> DiagramScheme:
|
||||
"""
|
||||
If scheme id is in the app template and content differs while still
|
||||
author=md2gost, clear author so migration will not overwrite user edits.
|
||||
author=md2gost, clear author so the copy is marked as the user's.
|
||||
"""
|
||||
stock = (bundled if bundled is not None else _load_schemes_file(bundled_schemes_path())).get(
|
||||
scheme.id
|
||||
|
||||
+69
-17
@@ -16,7 +16,7 @@ from .diagram_schemes import (
|
||||
claim_user_scheme_if_diverged,
|
||||
ensure_user_schemes,
|
||||
get_schemes,
|
||||
load_schemes_from_path,
|
||||
load_schemes_report,
|
||||
reload_schemes,
|
||||
save_schemes,
|
||||
user_schemes_path,
|
||||
@@ -263,7 +263,9 @@ class Md2GostApp:
|
||||
self._last_md = ""
|
||||
self._schemes: dict[str, DiagramScheme] = {}
|
||||
self._scheme_ids: list[str] = []
|
||||
self._scheme_file_keys: set[str] = set()
|
||||
self._scheme_selecting = False
|
||||
self._schemes_unreadable: str | None = None
|
||||
self._debug_next = False
|
||||
|
||||
self._win_files: tk.Toplevel | None = None
|
||||
@@ -1336,12 +1338,23 @@ class Md2GostApp:
|
||||
if not hasattr(self, "_scheme_list"):
|
||||
return
|
||||
path = ensure_user_schemes()
|
||||
self._schemes = load_schemes_from_path(path)
|
||||
self._schemes, err = load_schemes_report(path)
|
||||
self._schemes_unreadable = err
|
||||
self._scheme_file_keys = set() if err else set(self._schemes)
|
||||
self._scheme_ids = sorted(self._schemes.keys())
|
||||
self._scheme_list.delete(0, tk.END)
|
||||
for sid in self._scheme_ids:
|
||||
title = self._schemes[sid].title or sid
|
||||
self._scheme_list.insert(tk.END, title)
|
||||
if err:
|
||||
self._clear_scheme_form()
|
||||
self._scheme_fence_hint.configure(
|
||||
text=f"Файл не разобран и не изменён: {err}"
|
||||
)
|
||||
self._append_log(
|
||||
f"Файл схем не прочитан, он не перезаписан: {path} ({err})"
|
||||
)
|
||||
return
|
||||
if not self._scheme_ids:
|
||||
self._clear_scheme_form()
|
||||
return
|
||||
@@ -1352,6 +1365,22 @@ class Md2GostApp:
|
||||
self._scheme_list.see(idx)
|
||||
self._fill_scheme_form(self._schemes[target])
|
||||
|
||||
def _persist_schemes(self, select_id: str | None = None) -> str | None:
|
||||
"""Write self._schemes to disk. Refuses to overwrite a file that did not parse."""
|
||||
if self._schemes_unreadable:
|
||||
messagebox.showerror(
|
||||
"md2gost",
|
||||
"Файл схем не разобран, поэтому он не перезаписан.\n"
|
||||
f"{self._schemes_unreadable}",
|
||||
)
|
||||
return None
|
||||
path = user_schemes_path()
|
||||
removed = set(self._scheme_file_keys) - set(self._schemes)
|
||||
save_schemes(self._schemes, path, removed_ids=removed)
|
||||
reload_schemes()
|
||||
self._reload_schemes_ui(select_id=select_id)
|
||||
return str(path)
|
||||
|
||||
def _clear_scheme_form(self) -> None:
|
||||
self._scheme_selecting = True
|
||||
self._sch_id.set("")
|
||||
@@ -1396,6 +1425,9 @@ class Md2GostApp:
|
||||
return None
|
||||
includes_raw = self._text_get(self._sch_includes)
|
||||
includes = [ln.strip() for ln in includes_raw.splitlines() if ln.strip()]
|
||||
theme = ""
|
||||
if old_id and old_id in self._schemes:
|
||||
theme = self._schemes[old_id].theme
|
||||
return DiagramScheme(
|
||||
id=sid,
|
||||
title=self._sch_title.get().strip() or sid,
|
||||
@@ -1406,6 +1438,7 @@ class Md2GostApp:
|
||||
includes=includes,
|
||||
prefix=self._text_get(self._sch_prefix),
|
||||
postfix=self._text_get(self._sch_postfix),
|
||||
theme=theme,
|
||||
)
|
||||
|
||||
def _scheme_commit_current(self) -> str | None:
|
||||
@@ -1422,6 +1455,15 @@ class Md2GostApp:
|
||||
return scheme.id
|
||||
|
||||
def _scheme_add(self) -> None:
|
||||
if self._schemes_unreadable:
|
||||
messagebox.showerror(
|
||||
"md2gost",
|
||||
"Файл схем не разобран, новая схема не записана.\n"
|
||||
f"{self._schemes_unreadable}",
|
||||
)
|
||||
return
|
||||
if self._scheme_ids and self._scheme_commit_current() is None:
|
||||
return
|
||||
n = 1
|
||||
while f"custom{n}" in self._schemes:
|
||||
n += 1
|
||||
@@ -1437,13 +1479,24 @@ class Md2GostApp:
|
||||
prefix="@startuml\n",
|
||||
postfix="\n@enduml",
|
||||
)
|
||||
self._reload_schemes_ui(select_id=sid)
|
||||
path = self._persist_schemes(select_id=sid)
|
||||
if path:
|
||||
self._append_log(f"Схема добавлена: {sid} ({path})")
|
||||
|
||||
def _scheme_dup(self) -> None:
|
||||
sel = self._scheme_list.curselection()
|
||||
if not sel:
|
||||
if not self._scheme_list.curselection():
|
||||
return
|
||||
src = self._schemes[self._scheme_ids[sel[0]]]
|
||||
if self._schemes_unreadable:
|
||||
messagebox.showerror(
|
||||
"md2gost",
|
||||
"Файл схем не разобран, копия не записана.\n"
|
||||
f"{self._schemes_unreadable}",
|
||||
)
|
||||
return
|
||||
src_id = self._scheme_commit_current()
|
||||
if src_id is None:
|
||||
return
|
||||
src = self._schemes[src_id]
|
||||
n = 1
|
||||
while f"{src.id}_copy{n}" in self._schemes:
|
||||
n += 1
|
||||
@@ -1452,7 +1505,7 @@ class Md2GostApp:
|
||||
id=sid,
|
||||
title=src.title,
|
||||
version=src.version,
|
||||
author=src.author,
|
||||
author="",
|
||||
docs=src.docs,
|
||||
ai_prompt=src.ai_prompt,
|
||||
includes=list(src.includes),
|
||||
@@ -1460,7 +1513,9 @@ class Md2GostApp:
|
||||
postfix=src.postfix,
|
||||
theme=src.theme,
|
||||
)
|
||||
self._reload_schemes_ui(select_id=sid)
|
||||
path = self._persist_schemes(select_id=sid)
|
||||
if path:
|
||||
self._append_log(f"Схема скопирована: {sid} ({path})")
|
||||
|
||||
def _scheme_del(self) -> None:
|
||||
sel = self._scheme_list.curselection()
|
||||
@@ -1470,20 +1525,17 @@ class Md2GostApp:
|
||||
if not messagebox.askyesno("md2gost", f"Удалить схему «{sid}» из файла пользователя?"):
|
||||
return
|
||||
del self._schemes[sid]
|
||||
path = user_schemes_path()
|
||||
save_schemes(self._schemes, path)
|
||||
reload_schemes()
|
||||
self._reload_schemes_ui()
|
||||
self._append_log(f"Схема удалена: {sid}")
|
||||
path = self._persist_schemes()
|
||||
if path:
|
||||
self._append_log(f"Схема удалена: {sid}")
|
||||
|
||||
def _scheme_save_all(self) -> None:
|
||||
sid = self._scheme_commit_current()
|
||||
if sid is None and self._scheme_ids:
|
||||
return
|
||||
path = user_schemes_path()
|
||||
save_schemes(self._schemes, path)
|
||||
reload_schemes()
|
||||
self._reload_schemes_ui(select_id=sid)
|
||||
path = self._persist_schemes(select_id=sid)
|
||||
if not path:
|
||||
return
|
||||
self._append_log(f"Схемы сохранены: {path}")
|
||||
messagebox.showinfo("md2gost", f"Сохранено:\n{path}")
|
||||
|
||||
|
||||
@@ -205,11 +205,11 @@ SCHEMES_HELP = """СХЕМЫ ДИАГРАММ (PlantUML)
|
||||
Первый запуск
|
||||
Рядом с программой (рядом с md2gost.exe или в текущей папке при python -m)
|
||||
создаётся md2gost.schemes.json из встроенного шаблона.
|
||||
При каждом запуске схемы с author=md2gost синхронизируются с шаблоном приложения
|
||||
(добавление новых id, обновление, удаление исчезнувших вроде bpmn).
|
||||
Схемы с пустым или другим author не трогаются.
|
||||
При каждом запуске в файл добавляются только те встроенные схемы, которых ещё нет.
|
||||
Уже записанные схемы не перезаписываются и не удаляются: правки и свои шаблоны
|
||||
сохраняются. Файл с ошибкой JSON или с BOM не затирается.
|
||||
Если правите встроенную схему в GUI и сохраняете — author сбрасывается,
|
||||
чтобы следующая миграция не затёрла ваши правки.
|
||||
схема помечается как ваша.
|
||||
|
||||
Оградка в markdown
|
||||
```uml — обычный PlantUML (или ```plantuml)
|
||||
|
||||
@@ -39,6 +39,7 @@ from md2gost.diagram_schemes import (
|
||||
ensure_user_schemes,
|
||||
load_schemes_from_path,
|
||||
migrate_user_schemes,
|
||||
save_schemes,
|
||||
prepare_with_schemes,
|
||||
scheme_id_from_lang,
|
||||
user_schemes_path,
|
||||
@@ -125,7 +126,7 @@ def test_ensure_user_schemes_once(tmp_path):
|
||||
assert path == user_schemes_path(tmp_path)
|
||||
|
||||
|
||||
def test_migrate_removes_orphaned_builtin_and_adds_missing(tmp_path, monkeypatch):
|
||||
def test_migrate_keeps_custom_schemes_and_adds_missing(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
path = user_schemes_path(tmp_path)
|
||||
path.write_text(
|
||||
@@ -147,10 +148,11 @@ def test_migrate_removes_orphaned_builtin_and_adds_missing(tmp_path, monkeypatch
|
||||
)
|
||||
result = migrate_user_schemes(tmp_path)
|
||||
assert result.written
|
||||
assert "bpmn" in result.removed
|
||||
assert result.removed == []
|
||||
assert "c4" in result.added
|
||||
schemes = load_schemes_from_path(path)
|
||||
assert "bpmn" not in schemes
|
||||
assert schemes["bpmn"].title == "BPMN 2.0"
|
||||
assert schemes["bpmn"].author == "md2gost"
|
||||
assert "mine" in schemes
|
||||
assert schemes["mine"].title == "x"
|
||||
bundled = load_schemes_from_path(bundled_schemes_path())
|
||||
@@ -186,7 +188,7 @@ def test_migrate_preserves_user_override_of_builtin(tmp_path, monkeypatch):
|
||||
assert "usecase" in schemes # other bundled ids still added
|
||||
|
||||
|
||||
def test_migrate_updates_app_owned_builtin(tmp_path, monkeypatch):
|
||||
def test_migrate_keeps_edited_builtin(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
path = user_schemes_path(tmp_path)
|
||||
path.write_text(
|
||||
@@ -208,10 +210,83 @@ def test_migrate_updates_app_owned_builtin(tmp_path, monkeypatch):
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = migrate_user_schemes(tmp_path)
|
||||
assert "c4" in result.updated
|
||||
assert "c4" not in result.updated
|
||||
assert "c4" not in result.added
|
||||
schemes = load_schemes_from_path(path)
|
||||
bundled = load_schemes_from_path(bundled_schemes_path())
|
||||
assert schemes["c4"].to_dict() == bundled["c4"].to_dict()
|
||||
assert schemes["c4"].title == "Old C4"
|
||||
assert schemes["c4"].docs == "stale"
|
||||
assert schemes["c4"].author == "md2gost"
|
||||
assert "usecase" in schemes
|
||||
|
||||
|
||||
def test_migrate_does_not_overwrite_broken_json(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
path = user_schemes_path(tmp_path)
|
||||
original = "{\n \"c4\": {\"title\": \"x\",}\n}\n"
|
||||
path.write_text(original, encoding="utf-8")
|
||||
result = migrate_user_schemes(tmp_path)
|
||||
assert not result.written
|
||||
assert not result.changed
|
||||
assert path.read_text(encoding="utf-8") == original
|
||||
|
||||
|
||||
def test_migrate_reads_utf8_bom(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
path = user_schemes_path(tmp_path)
|
||||
body = json.dumps(
|
||||
{
|
||||
"c4": {
|
||||
"title": "My C4",
|
||||
"author": "md2gost",
|
||||
"docs": "mine",
|
||||
"prefix": "",
|
||||
"postfix": "",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
path.write_bytes(b"\xef\xbb\xbf" + body.encode("utf-8"))
|
||||
result = migrate_user_schemes(tmp_path)
|
||||
assert "usecase" in result.added
|
||||
schemes = load_schemes_from_path(path)
|
||||
assert schemes["c4"].title == "My C4"
|
||||
assert schemes["c4"].docs == "mine"
|
||||
text = path.read_text(encoding="utf-8-sig")
|
||||
assert "mine" in text
|
||||
|
||||
|
||||
def test_save_schemes_keeps_unknown_entries(tmp_path):
|
||||
path = user_schemes_path(tmp_path)
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"my-scheme": {"title": "hyphen", "note": "keep"},
|
||||
"mine": {"title": "x", "author": "", "extra": "field", "prefix": "A"},
|
||||
}
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
loaded = load_schemes_from_path(path)
|
||||
assert "mine" in loaded
|
||||
assert "my-scheme" not in loaded
|
||||
loaded["mine"] = DiagramScheme(
|
||||
id="mine",
|
||||
title="y",
|
||||
author="",
|
||||
prefix="A",
|
||||
)
|
||||
save_schemes(loaded, path, removed_ids=set())
|
||||
raw = json.loads(path.read_text(encoding="utf-8"))
|
||||
assert raw["my-scheme"]["title"] == "hyphen"
|
||||
assert raw["my-scheme"]["note"] == "keep"
|
||||
assert raw["mine"]["title"] == "y"
|
||||
assert raw["mine"]["extra"] == "field"
|
||||
del loaded["mine"]
|
||||
save_schemes(loaded, path, removed_ids={"mine"})
|
||||
raw = json.loads(path.read_text(encoding="utf-8"))
|
||||
assert "mine" not in raw
|
||||
assert raw["my-scheme"]["note"] == "keep"
|
||||
|
||||
|
||||
def test_migrate_idempotent(tmp_path, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user