v0.5.6
Example document / build (push) Has been cancelled
Python application / build (push) Has been cancelled

This commit is contained in:
Igor20264
2026-09-22 18:49:09 +03:00
parent 7fc112e2ce
commit daa84f8bce
21 changed files with 2761 additions and 23 deletions
+114 -9
View File
@@ -29,6 +29,7 @@ from .help_content import (
load_docs_catalog,
load_prompt_catalog,
)
from .office import DEFAULT_OFFICE_ENGINE, OFFICE_ENGINE_MODES
from .pipeline import ConvertRequest, convert, default_output_path, timestamped_output_path
from .profiles import (
DEFAULT_HEADING_NUMBERING,
@@ -64,6 +65,14 @@ TABLE_LABELS = {
"legacy": "Резать + «Продолжение»",
"caption": "Резать + разрыв страницы + «Продолжение»",
}
OFFICE_ENGINE_LABELS = {
"auto": "Авто: Word → LibreOffice",
"word": "Только Microsoft Word",
"libre": "LibreOffice Writer (UNO)",
}
GUI_OFFICE_ENGINE_MODES = tuple(
key for key in OFFICE_ENGINE_MODES if key != "openoffice"
)
FALLBACK_LABELS = {
"remote": "Авто: локально, иначе интернет (kroki.io)",
"local": "Только этот компьютер (без kroki.io)",
@@ -245,6 +254,7 @@ class Md2GostApp:
self.toc_var = tk.StringVar()
self.table_var = tk.StringVar()
self.listing_var = tk.StringVar()
self.office_engine_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)
@@ -1067,10 +1077,18 @@ class Md2GostApp:
tip="off — Word сам переносит; word — Split + «Продолжение» после сборки (нужен Word)")
self._combo(main, 4, "Продолжение листингов", self.listing_var, _combo_values(TABLE_LABELS, LISTING_CONTINUATION_MODES),
tip="То же для листингов; word требует Windows + Word + pywin32")
self._combo(
main,
5,
"Движок пост-разрыва",
self.office_engine_var,
_combo_values(OFFICE_ENGINE_LABELS, GUI_OFFICE_ENGINE_MODES),
tip="При режиме word: Word COM или LibreOffice UNO (soffice)",
)
ttk.Label(main, text="Смещение страниц").grid(row=5, column=0, sticky="w", padx=(0, 8), pady=3)
ttk.Label(main, text="Смещение страниц").grid(row=6, column=0, sticky="w", padx=(0, 8), pady=3)
page_row = ttk.Frame(main)
page_row.grid(row=5, column=1, sticky="w", pady=3)
page_row.grid(row=6, column=1, sticky="w", pady=3)
page_entry = ttk.Entry(page_row, textvariable=self.page_start_var, width=8)
page_entry.pack(side=tk.LEFT)
_tip(
@@ -1085,7 +1103,7 @@ class Md2GostApp:
).pack(side=tk.LEFT)
flags = ttk.Frame(main)
flags.grid(row=6, column=0, columnspan=2, sticky="w", pady=(8, 0))
flags.grid(row=7, column=0, columnspan=2, sticky="w", pady=(8, 0))
checks = [
("Заменять «—» на «-»", self.emdash_var, "Типографское тире → дефис (методичка обычно требует «—»)"),
("--- → разрыв страницы", self.hr_pagebreak_var, "Строка --- станет page break в Word"),
@@ -1139,17 +1157,41 @@ class Md2GostApp:
log_frame = ttk.LabelFrame(outer, text="Журнал", padding=4)
log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 0))
log_toolbar = ttk.Frame(log_frame)
log_toolbar.pack(fill=tk.X, pady=(0, 4))
ttk.Button(
log_toolbar, text="Копировать выделение", command=self._log_copy_selection
).pack(side=tk.LEFT)
ttk.Button(
log_toolbar, text="Копировать весь журнал", command=self._log_copy_all
).pack(side=tk.LEFT, padx=(6, 0))
log_body = ttk.Frame(log_frame)
log_body.pack(fill=tk.BOTH, expand=True)
self.log = tk.Text(
log_frame, height=7, wrap=tk.WORD, relief=tk.FLAT,
log_body, height=7, wrap=tk.WORD, relief=tk.FLAT,
bg="#fffdf8", fg=INK, padx=8, pady=6,
font=("Consolas", 9) if os.name == "nt" else ("monospace", 9),
exportselection=True, undo=False,
)
scroll = ttk.Scrollbar(log_frame, command=self.log.yview)
scroll = ttk.Scrollbar(log_body, command=self.log.yview)
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", "Готово к работе. Перетащите .md (сборка DOCX) или .docx (импорт в Markdown).\n")
self.log.configure(state=tk.DISABLED)
self.log.insert(
"1.0",
"Готово к работе. Перетащите .md (сборка DOCX) или .docx (импорт в Markdown).\n",
)
self._log_menu = tk.Menu(self.root, tearoff=0)
self._log_menu.add_command(
label="Копировать", command=self._log_copy_selection
)
self._log_menu.add_command(
label="Копировать весь журнал", command=self._log_copy_all
)
self.log.bind("<Button-3>", self._log_context_menu)
self.log.bind("<Control-Button-1>", self._log_context_menu)
self.log.bind("<Key>", self._log_keypress)
self.log.bind("<<Copy>>", self._log_copy_event)
def _build_schemes_tab(self, parent) -> None:
ttk.Label(
@@ -1697,6 +1739,10 @@ class Md2GostApp:
self.toc_var.set(_display(TOC_LABELS, req.toc_mode or DEFAULT_TOC_MODE))
self.table_var.set(_display(TABLE_LABELS, req.table_continuation or DEFAULT_TABLE_CONTINUATION))
self.listing_var.set(_display(TABLE_LABELS, req.listing_continuation or DEFAULT_LISTING_CONTINUATION))
office_engine = req.office_engine or DEFAULT_OFFICE_ENGINE
if office_engine not in GUI_OFFICE_ENGINE_MODES:
office_engine = DEFAULT_OFFICE_ENGINE
self.office_engine_var.set(_display(OFFICE_ENGINE_LABELS, office_engine))
self.fallback_var.set(_display(FALLBACK_LABELS, req.diagram_fallback or "remote"))
self.format_var.set(_display(FORMAT_LABELS, req.diagram_format or "png"))
scale = getattr(req, "diagram_scale", 2.0) or 2.0
@@ -1737,6 +1783,11 @@ class Md2GostApp:
toc_mode=_combo_key(self.toc_var.get(), TOC_MODES, DEFAULT_TOC_MODE),
table_continuation=_combo_key(self.table_var.get(), TABLE_CONTINUATION_MODES, DEFAULT_TABLE_CONTINUATION),
listing_continuation=_combo_key(self.listing_var.get(), LISTING_CONTINUATION_MODES, DEFAULT_LISTING_CONTINUATION),
office_engine=_combo_key(
self.office_engine_var.get(),
GUI_OFFICE_ENGINE_MODES,
DEFAULT_OFFICE_ENGINE,
),
emdash_to_hyphen=self.emdash_var.get(),
hr_pagebreak=self.hr_pagebreak_var.get(),
auto_title=self.auto_title_var.get(),
@@ -1860,17 +1911,71 @@ class Md2GostApp:
self._append_log(result.message or "Ошибка")
messagebox.showerror("md2gost", result.message or "Ошибка конвертации")
def _log_to_clipboard(self, text: str) -> None:
self.root.clipboard_clear()
self.root.clipboard_append(text)
self.root.update_idletasks()
def _log_copy_selection(self) -> None:
try:
if not self.log.tag_ranges(tk.SEL):
return
text = self.log.get(tk.SEL_FIRST, tk.SEL_LAST)
except tk.TclError:
return
if text:
self._log_to_clipboard(text)
def _log_copy_all(self) -> None:
text = self.log.get("1.0", tk.END).strip()
if text:
self._log_to_clipboard(text)
def _log_context_menu(self, event) -> str:
try:
self._log_menu.tk_popup(event.x_root, event.y_root)
finally:
self._log_menu.grab_release()
return "break"
def _log_copy_event(self, _event=None) -> str | None:
self._log_copy_selection()
return "break"
def _log_keypress(self, event) -> str | None:
"""Read-only log: allow selection/copy, block edits."""
if event.state & 0x4:
key = event.keysym.lower()
if key == "c":
self._log_copy_selection()
return "break"
if key == "a":
self.log.tag_add(tk.SEL, "1.0", tk.END)
self.log.mark_set(tk.INSERT, tk.END)
self.log.see(tk.INSERT)
return "break"
if key == "insert":
self._log_copy_selection()
return "break"
if event.keysym in (
"Left", "Right", "Up", "Down", "Home", "End",
"Prior", "Next", "Shift_L", "Shift_R", "Control_L", "Control_R",
"Tab", "ISO_Left_Tab",
):
return None
if not event.char:
return None
return "break"
def _append_log(self, text: str) -> None:
if not text:
return
if not hasattr(self, "log"):
return
self.log.configure(state=tk.NORMAL)
if not text.endswith("\n"):
text += "\n"
self.log.insert(tk.END, text)
self.log.see(tk.END)
self.log.configure(state=tk.DISABLED)
def run(self) -> None:
self.root.mainloop()