v0.5.2
Python application / build (push) Waiting to run

Что то сделал
This commit is contained in:
Igor20264
2026-09-08 19:37:54 +03:00
parent 1a5b35eb54
commit 510f7e7adf
90 changed files with 11720 additions and 5547 deletions
+142 -15
View File
@@ -21,10 +21,33 @@ _log = logging.getLogger(__name__)
SCHEMES_FILENAME = "md2gost.schemes.json"
SCHEME_ID_RE = re.compile(r"^[a-z][a-z0-9_]*$")
APP_SCHEME_AUTHOR = "md2gost"
BUILTIN_DIAGRAM_LANGS = frozenset({"uml", "plantuml"})
# Rendered via Kroki only (not PlantUML jar / schemes).
KROKI_DIAGRAM_LANGS = frozenset({"mermaid", "mmd"})
KROKI_TYPE_BY_LANG = {"mermaid": "mermaid", "mmd": "mermaid"}
# Local native renderers (not PlantUML / Kroki / schemes.json).
NATIVE_DIAGRAM_LANGS = {
"idef0": "idef0",
"uml-idef0": "idef0",
"dfd": "dfd",
"uml-dfd": "dfd",
"data-flow-diagram": "dfd",
"yourdon": "dfd",
"uml-yourdon": "dfd",
}
@dataclass
class SchemesMigrationResult:
added: list[str] = field(default_factory=list)
updated: list[str] = field(default_factory=list)
removed: list[str] = field(default_factory=list)
written: bool = False
@property
def changed(self) -> bool:
return bool(self.added or self.updated or self.removed)
@dataclass
@@ -82,22 +105,84 @@ def user_schemes_path(base: Path | None = None) -> Path:
return (base or app_dir()) / SCHEMES_FILENAME
def is_app_owned_scheme(scheme: DiagramScheme) -> bool:
"""True if scheme is managed by the app template (author=md2gost)."""
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.
- 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.
"""
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()
return result
save_schemes(out, path)
result.written = True
return result
def ensure_user_schemes(base: Path | None = None) -> Path:
"""
On first run copy bundled template next to the app.
Never overwrite an existing user file.
Ensure md2gost.schemes.json exists next to the app, then sync app-owned
schemes from the bundled template (add / update / remove orphans).
"""
dest = user_schemes_path(base)
if dest.is_file():
return dest
src = bundled_schemes_path()
dest.parent.mkdir(parents=True, exist_ok=True)
if src.is_file():
shutil.copyfile(src, dest)
_log.info("Создан файл схем: %s", dest)
else:
dest.write_text("{}\n", encoding="utf-8")
_log.warning("Шаблон схем не найден (%s), создан пустой %s", src, dest)
if not dest.is_file():
src = bundled_schemes_path()
dest.parent.mkdir(parents=True, exist_ok=True)
if src.is_file():
shutil.copyfile(src, dest)
_log.info("Создан файл схем: %s", dest)
else:
dest.write_text("{}\n", encoding="utf-8")
_log.warning("Шаблон схем не найден (%s), создан пустой %s", src, dest)
migrate_user_schemes(base)
return dest
@@ -129,7 +214,7 @@ def load_schemes_from_path(path: Path) -> dict[str, DiagramScheme]:
def save_schemes(schemes: dict[str, DiagramScheme], path: Path | None = None) -> Path:
dest = path or user_schemes_path()
payload = {sid: scheme.to_dict() for sid, scheme in sorted(schemes.items())}
payload = _schemes_payload(schemes)
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
@@ -138,12 +223,50 @@ def save_schemes(schemes: dict[str, DiagramScheme], path: Path | None = None) ->
return dest
def claim_user_scheme_if_diverged(
scheme: DiagramScheme,
*,
bundled: dict[str, DiagramScheme] | None = None,
) -> 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.
"""
stock = (bundled if bundled is not None else _load_schemes_file(bundled_schemes_path())).get(
scheme.id
)
if stock is None:
return scheme
if not is_app_owned_scheme(scheme):
return scheme
if scheme.to_dict() == stock.to_dict():
return scheme
return DiagramScheme(
id=scheme.id,
title=scheme.title,
version=scheme.version,
author="",
docs=scheme.docs,
ai_prompt=scheme.ai_prompt,
includes=list(scheme.includes),
prefix=scheme.prefix,
postfix=scheme.postfix,
theme=scheme.theme,
)
def native_diagram_type(lang: str) -> str | None:
"""Kroki/PlantUML-independent diagram type, or None."""
lang = (lang or "").lower().strip()
return NATIVE_DIAGRAM_LANGS.get(lang)
def scheme_id_from_lang(lang: str) -> str | None:
"""Normalize fence lang to scheme id, or None for bare uml/plantuml."""
lang = (lang or "").lower().strip()
if not lang:
return None
if lang in BUILTIN_DIAGRAM_LANGS:
if lang in BUILTIN_DIAGRAM_LANGS or lang in KROKI_DIAGRAM_LANGS or lang in NATIVE_DIAGRAM_LANGS:
return None
if lang.startswith("uml-"):
sid = lang[4:]
@@ -224,7 +347,7 @@ def get_scheme(scheme_id: str) -> DiagramScheme | None:
def is_diagram_lang(lang: str) -> bool:
"""True if fence should render as a diagram (not a plain listing)."""
lang = (lang or "").lower().strip()
if lang in BUILTIN_DIAGRAM_LANGS or lang in KROKI_DIAGRAM_LANGS:
if lang in BUILTIN_DIAGRAM_LANGS or lang in KROKI_DIAGRAM_LANGS or lang in NATIVE_DIAGRAM_LANGS:
return True
if lang.startswith("uml-"):
# Force diagram path so missing scheme becomes an error, not a listing.
@@ -316,6 +439,10 @@ def prepare_with_schemes(lang: str, source: str, *, base: Path | None = None) ->
if lang in KROKI_DIAGRAM_LANGS:
return text, KROKI_TYPE_BY_LANG[lang]
native = native_diagram_type(lang)
if native:
return text, native
sid = scheme_id_from_lang(lang)
if sid is None: