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
+395 -20
View File
@@ -13,6 +13,7 @@ from .diagram_renderer import diagram_engine_status
from .diagram_schemes import (
DiagramScheme,
SCHEME_ID_RE,
claim_user_scheme_if_diverged,
ensure_user_schemes,
get_schemes,
load_schemes_from_path,
@@ -20,7 +21,7 @@ from .diagram_schemes import (
save_schemes,
user_schemes_path,
)
from .dnd import enable_file_drop, first_markdown
from .dnd import enable_file_drop, first_docx, first_markdown
from .help_content import (
SCHEMES_HELP,
USAGE_HELP,
@@ -200,6 +201,8 @@ class Md2GostApp:
self._win_files: tk.Toplevel | None = None
self._win_diagrams: tk.Toplevel | None = None
self._win_schemes: tk.Toplevel | None = None
self._win_student: tk.Toplevel | None = None
self._win_meta: tk.Toplevel | None = None
self._win_help: dict[str, tk.Toplevel] = {}
ensure_user_schemes()
@@ -209,6 +212,7 @@ class Md2GostApp:
self._build()
self._apply_request(self.req)
self.root.after(300, self._setup_dnd)
self.root.after(400, self._ensure_student_profile)
if self.req.filename:
self._set_markdown(self.req.filename, from_user_output=bool(self.req.output))
@@ -229,6 +233,7 @@ class Md2GostApp:
style.configure("Title.TLabel", font=font_title, foreground=NAVY, background=PAPER)
style.configure("Hint.TLabel", foreground=MUTED, background=PAPER)
style.configure("TCheckbutton", background=PAPER, font=font)
style.configure("TRadiobutton", background=PAPER, foreground=INK, font=font)
style.configure("TButton", font=font, padding=(10, 4))
style.configure("TEntry", padding=3)
style.configure("TCombobox", padding=3)
@@ -242,6 +247,7 @@ class Md2GostApp:
self.listing_var = tk.StringVar()
self.emdash_var = tk.BooleanVar(value=False)
self.hr_pagebreak_var = tk.BooleanVar(value=False)
self.auto_title_var = tk.BooleanVar(value=False)
self.open_var = tk.BooleanVar(value=True)
self.check_var = tk.BooleanVar(value=False)
self.check_only_var = tk.BooleanVar(value=False)
@@ -268,6 +274,8 @@ class Md2GostApp:
settings = tk.Menu(menubar, tearoff=0)
settings.add_command(label="Файлы…", command=self._open_files_window)
settings.add_command(label="Студент…", command=self._open_student_window)
settings.add_command(label="Метаданные…", command=self._open_metadata_window)
settings.add_command(label="Диаграммы…", command=self._open_diagrams_window)
menubar.add_cascade(label="Настройки", menu=settings)
@@ -340,6 +348,8 @@ class Md2GostApp:
hint = ttk.Label(
frame,
text="Пустой шаблон — встроенный Template.docx. Титул и задание вставляются перед телом отчёта.\n"
"Автотитул (info_conv.yaml / ```title) — галочка «Автотитул» на главном окне "
"(по умолчанию выкл.; ФИО/группа — Настройки → Студент…).\n"
"Стили JSON — опционально; также подхватывается md2gost.styles.json рядом с .md.",
style="Hint.TLabel",
wraplength=560,
@@ -348,6 +358,225 @@ class Md2GostApp:
win.protocol("WM_DELETE_WINDOW", lambda: self._close_toplevel(win, "_win_files"))
return win
def _ensure_student_profile(self) -> None:
from .user_profile import load_user_profile
if load_user_profile().is_complete():
return
self._open_student_window(first_run=True)
def _open_student_window(self, first_run: bool = False) -> None:
existing = getattr(self, "_win_student", None)
if existing is not None and existing.winfo_exists():
existing.lift()
existing.focus_force()
return
self._win_student = self._create_student_window(first_run=first_run)
def _create_student_window(self, *, first_run: bool = False) -> tk.Toplevel:
from .user_profile import load_user_profile, save_user_profile
profile = load_user_profile()
win = tk.Toplevel(self.root)
win.title("Данные студента" if first_run else "Настройки — Студент")
win.configure(bg=PAPER)
win.minsize(420, 180)
win.geometry("480x200")
win.transient(self.root)
if first_run:
win.grab_set()
frame = ttk.Frame(win, padding=12)
frame.pack(fill=tk.BOTH, expand=True)
frame.columnconfigure(1, weight=1)
ttk.Label(
frame,
text="ФИО и группа нужны для автотитульника (info_conv.yaml / ```title).",
style="Hint.TLabel",
wraplength=440,
).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 8))
student_var = tk.StringVar(value=profile.student)
group_var = tk.StringVar(value=profile.group)
ttk.Label(frame, text="ФИО").grid(row=1, column=0, sticky="w", pady=4)
ttk.Entry(frame, textvariable=student_var).grid(row=1, column=1, sticky="ew", pady=4, padx=(8, 0))
ttk.Label(frame, text="Группа").grid(row=2, column=0, sticky="w", pady=4)
ttk.Entry(frame, textvariable=group_var).grid(row=2, column=1, sticky="ew", pady=4, padx=(8, 0))
def _save(close: bool = True) -> None:
student = student_var.get().strip()
group = group_var.get().strip()
if not student or not group:
messagebox.showwarning(
"Данные студента",
"Заполните ФИО и группу.",
parent=win,
)
return
current = load_user_profile()
current.student = student
current.group = group
save_user_profile(current)
if close:
self._close_toplevel(win, "_win_student")
btns = ttk.Frame(frame)
btns.grid(row=3, column=0, columnspan=2, sticky="e", pady=(12, 0))
ttk.Button(btns, text="Сохранить", command=lambda: _save(True)).pack(side=tk.RIGHT)
def _on_close() -> None:
if first_run and not load_user_profile().is_complete():
# Allow closing only after save; remind once
if not student_var.get().strip() or not group_var.get().strip():
messagebox.showinfo(
"Данные студента",
"Можно заполнить позже: Настройки → Студент…\n"
"Без ФИО/группы автотитул не соберётся.",
parent=win,
)
self._close_toplevel(win, "_win_student")
win.protocol("WM_DELETE_WINDOW", _on_close)
return win
def _open_metadata_window(self) -> None:
self._win_meta = self._raise_or_create(self._win_meta, self._create_metadata_window)
def _create_metadata_window(self) -> tk.Toplevel:
from .doc_metadata import (
AUTHOR_SOURCES,
DEFAULT_AUTHOR_SOURCE,
DEFAULT_DOC_COMMENTS,
DocumentMetadata,
os_user,
)
from .user_profile import load_user_profile, save_user_profile
profile = load_user_profile()
meta = profile.metadata or DocumentMetadata()
win = tk.Toplevel(self.root)
win.title("Настройки — Метаданные")
win.configure(bg=PAPER)
win.minsize(520, 420)
win.geometry("580x500")
win.transient(self.root)
frame = ttk.Frame(win, padding=12)
frame.pack(fill=tk.BOTH, expand=True)
frame.columnconfigure(1, weight=1)
ttk.Label(
frame,
text=(
"Свойства файла Word (Файл → сведения): автор, название, тема, теги, примечание. "
"Хранятся в md2gost.user.json рядом с приложением."
),
style="Hint.TLabel",
wraplength=540,
).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 8))
src_value = meta.author_source if meta.author_source in AUTHOR_SOURCES else DEFAULT_AUTHOR_SOURCE
author_src = tk.StringVar(value=src_value)
author_var = tk.StringVar(value=meta.author)
last_mod_var = tk.StringVar(value=meta.last_modified_by)
title_var = tk.StringVar(value=meta.title)
subject_var = tk.StringVar(value=meta.subject)
keywords_var = tk.StringVar(value=meta.keywords)
category_var = tk.StringVar(value=meta.category)
comments_var = tk.StringVar(value=meta.comments)
ttk.Label(frame, text="Автор").grid(row=1, column=0, sticky="nw", pady=(4, 0))
src_box = ttk.Frame(frame)
src_box.grid(row=1, column=1, sticky="ew", pady=(4, 0), padx=(8, 0))
os_name = os_user() or ""
student_name = (profile.student or "").strip()
student_text = (
f"ФИО студента ({student_name})"
if student_name
else "ФИО студента (сначала Настройки → Студент…)"
)
ttk.Radiobutton(
src_box, text=f"Имя пользователя Windows ({os_name})",
variable=author_src, value="os",
).pack(anchor="w")
ttk.Radiobutton(
src_box, text=student_text, variable=author_src, value="student",
).pack(anchor="w")
ttk.Radiobutton(
src_box, text="Указать вручную", variable=author_src, value="custom",
).pack(anchor="w")
author_entry = ttk.Entry(frame, textvariable=author_var)
author_entry.grid(row=2, column=1, sticky="ew", pady=(2, 4), padx=(8, 0))
_tip(author_entry, "Используется, если выбран пункт «Указать вручную»")
def _sync_author_entry(*_a) -> None:
state = "normal" if author_src.get() == "custom" else "disabled"
try:
author_entry.configure(state=state)
except tk.TclError:
pass
author_src.trace_add("write", _sync_author_entry)
_sync_author_entry()
def _row(row: int, label: str, var: tk.StringVar, tip: str) -> None:
ttk.Label(frame, text=label).grid(row=row, column=0, sticky="w", pady=4)
entry = ttk.Entry(frame, textvariable=var)
entry.grid(row=row, column=1, sticky="ew", pady=4, padx=(8, 0))
_tip(entry, tip)
_row(3, "Кем изменён", last_mod_var, "Пусто — то же значение, что у автора")
_row(4, "Название", title_var, "dc:title в свойствах файла; пусто — не задавать")
_row(5, "Тема", subject_var, "Тема документа в свойствах файла")
_row(6, "Ключевые слова", keywords_var, "Теги в свойствах файла Word")
_row(7, "Категория", category_var, "Категория в свойствах файла")
_row(
8,
"Примечание",
comments_var,
f"По умолчанию: «{DEFAULT_DOC_COMMENTS}». Пусто — не писать примечание.",
)
ttk.Label(
frame,
text="Пустое примечание убирает и текст md2gost, и «generated by python-docx» из шаблона.",
style="Hint.TLabel",
wraplength=540,
).grid(row=9, column=0, columnspan=2, sticky="w", pady=(4, 0))
def _save() -> None:
src = author_src.get() if author_src.get() in AUTHOR_SOURCES else DEFAULT_AUTHOR_SOURCE
current = load_user_profile()
current.metadata = DocumentMetadata(
author_source=src,
author=author_var.get().strip(),
last_modified_by=last_mod_var.get().strip(),
title=title_var.get().strip(),
subject=subject_var.get().strip(),
keywords=keywords_var.get().strip(),
comments=comments_var.get().strip(),
category=category_var.get().strip(),
)
save_user_profile(current)
self._close_toplevel(win, "_win_meta")
def _reset() -> None:
author_src.set(DEFAULT_AUTHOR_SOURCE)
author_var.set("")
last_mod_var.set("")
title_var.set("")
subject_var.set("")
keywords_var.set("")
category_var.set("")
comments_var.set(DEFAULT_DOC_COMMENTS)
btns = ttk.Frame(frame)
btns.grid(row=10, column=0, columnspan=2, sticky="e", pady=(12, 0))
ttk.Button(btns, text="Сбросить", command=_reset).pack(side=tk.LEFT, padx=(0, 8))
ttk.Button(btns, text="Сохранить", command=_save).pack(side=tk.RIGHT)
win.protocol("WM_DELETE_WINDOW", lambda: self._close_toplevel(win, "_win_meta"))
return win
def _create_diagrams_window(self) -> tk.Toplevel:
win = tk.Toplevel(self.root)
win.title("Настройки — Диаграммы")
@@ -362,10 +591,10 @@ class Md2GostApp:
hint = ttk.Label(
extra,
text=(
"Блоки ```uml / ```uml-c4 / ```bpmn / ```mermaid сами станут рисунками.\n"
"Блоки ```uml / ```uml-c4 / ```mermaid / ```idef0 / ```dfd сами станут рисунками.\n"
"Схемы редактируются в меню «Шаблоны UML» (файл md2gost.schemes.json).\n"
"UML: PlantUML + Java, иначе Kroki. Mermaid: системный браузер → "
"Chromium → QuickJS → Kroki (kroki.io остаётся запасным)."
"Chromium → QuickJS → Kroki. IDEF0 — Pillow. DFD — Graphviz / Kroki."
),
style="Hint.TLabel",
wraplength=640,
@@ -377,14 +606,17 @@ class Md2GostApp:
status_row.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(0, 8))
status_row.columnconfigure(0, weight=1)
self.diag_status = ttk.Label(status_row, text="", style="Hint.TLabel", wraplength=480, justify=tk.LEFT)
self.diag_status.grid(row=0, column=0, sticky="w", rowspan=2)
self.diag_status.grid(row=0, column=0, sticky="w", rowspan=3)
btn_dl = ttk.Button(status_row, text="Скачать PlantUML", command=self._download_plantuml)
btn_dl.grid(row=0, column=1, sticky="e", padx=(8, 0))
_tip(btn_dl, "Скачать plantuml.jar для локальной отрисовки (нужна Java)")
btn_gv = ttk.Button(status_row, text="Скачать Graphviz", command=self._download_graphviz)
btn_gv.grid(row=1, column=1, sticky="e", padx=(8, 0), pady=(4, 0))
_tip(btn_gv, "Скачать portable Graphviz (dot/neato) для локальных DFD")
btn_cr = ttk.Button(
status_row, text="Скачать headless Chromium", command=self._download_chromium,
)
btn_cr.grid(row=1, column=1, sticky="e", padx=(8, 0), pady=(4, 0))
btn_cr.grid(row=2, column=1, sticky="e", padx=(8, 0), pady=(4, 0))
_tip(
btn_cr,
"Скачать Chromium для Mermaid в %LOCALAPPDATA%\\md2gost "
@@ -629,7 +861,7 @@ class Md2GostApp:
def _build_schemes_help(self, parent) -> None:
ttk.Label(
parent,
text="Настраиваемые схемы диаграмм (uml-c4, bpmn, …)",
text="Настраиваемые схемы диаграмм (uml-c4, usecase, …)",
style="Hint.TLabel",
).pack(anchor=tk.W, pady=(0, 6))
box = self._readonly_text(parent, SCHEMES_HELP.strip() + "\n")
@@ -641,7 +873,7 @@ class Md2GostApp:
parent,
text="Скопируйте промпт и вставьте в ChatGPT / Cursor / Copilot. "
"Кнопки схем дописывают макросы из md2gost.schemes.json — "
"когда ИИ должен знать C4/BPMN/свои шаблоны. Следующим сообщением — тип, тема, черновик.",
"когда ИИ должен знать C4/свои шаблоны. Следующим сообщением — тип, тема, черновик.",
style="Hint.TLabel",
wraplength=640,
).pack(anchor=tk.W, pady=(0, 8))
@@ -800,7 +1032,7 @@ class Md2GostApp:
self.zone.pack(fill=tk.X, pady=(0, 8))
self.zone_label = tk.Label(
self.zone,
text="Перетащите .md сюда · или нажмите, чтобы выбрать файл",
text="Перетащите .md или .docx сюда · или нажмите, чтобы выбрать файл",
bg=ZONE, fg=NAVY, font=("Segoe UI", 11) if os.name == "nt" else ("Helvetica", 11),
justify=tk.CENTER, pady=14,
)
@@ -812,8 +1044,8 @@ class Md2GostApp:
self.zone_path.pack(fill=tk.X, pady=(0, 8))
for w in (self.zone, self.zone_label, self.zone_path):
w.bind("<Button-1>", lambda _e: self._browse_md())
_tip(self.zone, "Перетащите .md или кликните, чтобы выбрать файл")
_tip(self.zone_label, "Перетащите .md или кликните, чтобы выбрать файл")
_tip(self.zone, "Перетащите .md / .docx или кликните, чтобы выбрать файл")
_tip(self.zone_label, "Перетащите .md / .docx или кликните, чтобы выбрать файл")
grid = ttk.Frame(outer)
grid.pack(fill=tk.X)
@@ -857,6 +1089,11 @@ class Md2GostApp:
checks = [
("Заменять «—» на «-»", self.emdash_var, "Типографское тире → дефис (методичка обычно требует «—»)"),
("--- → разрыв страницы", self.hr_pagebreak_var, "Строка --- станет page break в Word"),
(
"Автотитул",
self.auto_title_var,
"Собрать титул из info_conv.yaml / ```title (нужны ФИО и группа). По умолчанию выкл.",
),
("Открыть после конвертации", self.open_var, "Открыть DOCX после успешной сборки"),
("Проверить по ТЗ", self.check_var, "Замечания по структуре, «рис.», источникам"),
("Только проверка", self.check_only_var, "Проверить без записи DOCX"),
@@ -886,7 +1123,17 @@ class Md2GostApp:
relief=tk.FLAT, padx=22, pady=7, cursor="hand2",
)
self.convert_btn.pack(side=tk.LEFT)
_tip(self.convert_btn, "Собрать DOCX из выбранного markdown")
_tip(self.convert_btn, "Собрать DOCX из markdown или импортировать DOCX → Markdown")
self.import_btn = tk.Button(
actions, text="Импорт DOCX→MD",
command=self._import_docx,
bg="#3d5a40", fg="white", activebackground="#2d4430", activeforeground="white",
disabledforeground="#d0d5dc",
font=("Segoe UI", 11) if os.name == "nt" else ("Helvetica", 11),
relief=tk.FLAT, padx=14, pady=7, cursor="hand2",
)
self.import_btn.pack(side=tk.LEFT, padx=(8, 0))
_tip(self.import_btn, "Выбрать .docx и сохранить диалект md2gost (.md + медиа)")
self.progress = ttk.Progressbar(actions, mode="indeterminate", length=180)
self.progress.pack(side=tk.LEFT, padx=8, fill=tk.X, expand=True)
@@ -901,7 +1148,7 @@ class Md2GostApp:
self.log.configure(yscrollcommand=scroll.set)
self.log.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.log.insert("1.0", "Готово к работе. Перетащите markdown-файл в верхнюю область.\n")
self.log.insert("1.0", "Готово к работе. Перетащите .md (сборка DOCX) или .docx (импорт в Markdown).\n")
self.log.configure(state=tk.DISABLED)
def _build_schemes_tab(self, parent) -> None:
@@ -1067,6 +1314,7 @@ class Md2GostApp:
scheme = self._scheme_from_form(old_id)
if scheme is None:
return None
scheme = claim_user_scheme_if_diverged(scheme)
if old_id and old_id != scheme.id and old_id in self._schemes:
del self._schemes[old_id]
self._schemes[scheme.id] = scheme
@@ -1197,7 +1445,7 @@ class Md2GostApp:
if backend == "none":
self._append_log("Перетаскивание недоступно — выберите файл кликом по области.")
else:
self._append_log("Можно перетаскивать .md из проводника в окно.")
self._append_log("Можно перетаскивать .md / .docx из проводника в окно.")
def _on_drop(self, paths: list[str]) -> None:
md = first_markdown(paths)
@@ -1205,8 +1453,16 @@ class Md2GostApp:
self._set_markdown(md)
self._flash_zone(ZONE_OK)
return
docx = first_docx(paths)
if docx:
self._set_docx(docx)
self._flash_zone(ZONE_OK)
return
if paths:
self._append_log("Нужен файл .md, а не: " + ", ".join(os.path.basename(p) for p in paths))
self._append_log(
"Нужен файл .md или .docx, а не: "
+ ", ".join(os.path.basename(p) for p in paths)
)
self._flash_zone("#f8d7da")
def _flash_zone(self, color: str) -> None:
@@ -1222,10 +1478,19 @@ class Md2GostApp:
def _browse_md(self) -> None:
path = filedialog.askopenfilename(
title="Исходный Markdown",
filetypes=[("Markdown", "*.md"), ("Все файлы", "*.*")],
title="Исходный Markdown или Word",
filetypes=[
("Markdown / Word", "*.md;*.docx"),
("Markdown", "*.md"),
("Word", "*.docx"),
("Все файлы", "*.*"),
],
)
if path:
if not path:
return
if path.lower().endswith(".docx"):
self._set_docx(path)
else:
self._set_markdown(path)
def _browse_output(self) -> None:
@@ -1290,6 +1555,55 @@ class Md2GostApp:
"Можно ничего не делать — диаграммы нарисует kroki.io.",
)
def _download_graphviz(self) -> None:
from .dfd import (
cached_graphviz_root,
fetch_graphviz,
vendor_graphviz_root,
)
dest = vendor_graphviz_root()
try:
dest.mkdir(parents=True, exist_ok=True)
probe = dest / ".write_test"
probe.write_text("ok", encoding="utf-8")
probe.unlink()
except OSError:
dest = cached_graphviz_root()
self._append_log(f"Скачиваю Graphviz → {dest}")
self.root.update_idletasks()
def work() -> None:
ok = fetch_graphviz(dest)
self.root.after(0, lambda: self._on_graphviz_installed(ok, dest))
import threading
threading.Thread(target=work, daemon=True).start()
def _on_graphviz_installed(self, ok: bool, dest) -> None:
self._refresh_diagram_status()
if ok:
engine = None
try:
from .dfd import resolve_graphviz_engine
engine = resolve_graphviz_engine(False)
except Exception:
pass
messagebox.showinfo(
"md2gost",
f"Graphviz сохранён:\n{dest}\n\n"
f"Движок: {engine or 'dot в каталоге'}",
)
else:
messagebox.showerror(
"md2gost",
"Не удалось скачать Graphviz.\n"
"DFD может пойти через kroki.io "
"(«Как рисовать» → через интернет) или системный PATH.",
)
def _download_chromium(self) -> None:
from .mermaid_renderer import install_playwright_chromium, playwright_browsers_dir, playwright_available
@@ -1351,6 +1665,32 @@ class Md2GostApp:
self._auto_output = True
self._append_log(f"Файл: {path}")
def _set_docx(self, path: str) -> None:
path = os.path.abspath(path)
self._last_md = path
self.zone_label.configure(text=os.path.basename(path))
self.zone_path.configure(text=path)
if self._auto_output:
stem = os.path.splitext(path)[0]
self.output_var.set(stem + ".md")
self._auto_output = True
self._append_log(f"DOCX для импорта: {path}")
def _import_docx(self) -> None:
"""Pick a DOCX (or use current) and run word2md."""
if self._busy:
return
path = self._last_md if self._last_md.lower().endswith(".docx") else ""
if not path:
path = filedialog.askopenfilename(
title="Импорт DOCX → Markdown",
filetypes=[("Word", "*.docx"), ("Все файлы", "*.*")],
)
if not path:
return
self._set_docx(path)
self._run_convert(force_import=True)
def _apply_request(self, req: ConvertRequest) -> None:
self.type_var.set(_display(DOC_TYPE_LABELS, req.doc_type if req.doc_type in DOC_TYPES else "practice"))
self.heading_var.set(_display(HEADING_LABELS, req.heading_numbering or DEFAULT_HEADING_NUMBERING))
@@ -1363,6 +1703,7 @@ class Md2GostApp:
self.scale_var.set(str(scale).rstrip("0").rstrip(".") if isinstance(scale, float) else str(scale))
self.emdash_var.set(bool(req.emdash_to_hyphen))
self.hr_pagebreak_var.set(bool(req.hr_pagebreak))
self.auto_title_var.set(bool(getattr(req, "auto_title", False)))
self.open_var.set(True)
self.check_var.set(bool(req.check))
self.check_only_var.set(bool(req.check_only))
@@ -1398,6 +1739,7 @@ class Md2GostApp:
listing_continuation=_combo_key(self.listing_var.get(), LISTING_CONTINUATION_MODES, DEFAULT_LISTING_CONTINUATION),
emdash_to_hyphen=self.emdash_var.get(),
hr_pagebreak=self.hr_pagebreak_var.get(),
auto_title=self.auto_title_var.get(),
title=self.title_var.get().strip() or None,
assignment=self.assignment_var.get().strip() or None,
check=self.check_var.get() or self.check_only_var.get(),
@@ -1436,13 +1778,39 @@ class Md2GostApp:
return value if value >= 1 else 1.0
def _convert(self) -> None:
self._run_convert(force_import=False)
def _run_convert(self, *, force_import: bool) -> None:
if self._busy:
return
req = self._collect()
if not req.filename:
messagebox.showinfo("md2gost", "Сначала перетащите или выберите markdown-файл.")
messagebox.showinfo(
"md2gost",
"Сначала перетащите или выберите .md (сборка) или .docx (импорт).",
)
return
if not req.check_only:
is_docx = req.filename.lower().endswith(".docx")
if force_import and not is_docx:
messagebox.showinfo("md2gost", "Для импорта нужен файл .docx.")
return
if is_docx:
target = (req.output or "").strip()
if target.lower().endswith(".docx"):
target = os.path.splitext(req.filename)[0] + ".md"
req.output = target
self.output_var.set(target)
if target and os.path.isfile(target):
if not messagebox.askyesno(
"md2gost", f"Файл уже есть:\n{target}\n\nПерезаписать?"
):
base, ext = os.path.splitext(target)
target = f"{base}_import{ext or '.md'}"
self._append_log(f"Не перезаписываем, пишем в {target}")
req.output = target
self.output_var.set(target)
self._append_log("Импорт DOCX → Markdown…")
elif not req.check_only:
target = (req.output or "").strip() or (
default_output_path(req.filename) if req.filename else ""
)
@@ -1455,10 +1823,15 @@ class Md2GostApp:
req.output = target
self.output_var.set(target)
self._auto_output = False
self._append_log("Конвертация…")
else:
self._append_log("Проверка…")
self._busy = True
self.convert_btn.configure(state=tk.DISABLED, bg=NAVY_DIM)
if hasattr(self, "import_btn"):
self.import_btn.configure(state=tk.DISABLED)
self.progress.start(12)
self._append_log("Конвертация…")
def work():
result = convert(req, log=lambda m: self.root.after(0, self._append_log, m))
@@ -1469,6 +1842,8 @@ class Md2GostApp:
def _on_done(self, result) -> None:
self._busy = False
self.convert_btn.configure(state=tk.NORMAL, bg=NAVY)
if hasattr(self, "import_btn"):
self.import_btn.configure(state=tk.NORMAL, bg="#3d5a40")
self.progress.stop()
if self._debug_next or self.debug_var.get():
self.debug_var.set(False)