Update 0.4.0
Python application / build (push) Has been cancelled

- Add\Rework UI
- Add Split Table and Listing
- Add Support Customazeble schems
This commit is contained in:
Igor20264
2026-09-04 22:28:39 +03:00
parent 516abe7b83
commit b38661f588
70 changed files with 69532 additions and 413 deletions
+147 -105
View File
@@ -1,41 +1,44 @@
#!/usr/bin/env python
from argparse import ArgumentParser, BooleanOptionalAction
import os
import os.path
import sys
from getpass import getuser
from docx import Document
from .converter import Converter
from .pipeline import ConvertRequest, convert, should_launch_gui
from .profiles import (
DEFAULT_HEADING_NUMBERING,
DEFAULT_TABLE_CONTINUATION,
DEFAULT_LISTING_CONTINUATION,
DEFAULT_TOC_MODE,
DOC_TYPES,
HEADING_NUMBERING_MODES,
DEFAULT_HEADING_NUMBERING,
TOC_MODES,
DEFAULT_TOC_MODE,
TABLE_CONTINUATION_MODES,
DEFAULT_TABLE_CONTINUATION,
get_profile,
LISTING_CONTINUATION_MODES,
TOC_MODES,
)
from .checker import check_markdown, format_report
def main():
def build_parser() -> ArgumentParser:
parser = ArgumentParser(
prog="md2gost",
description=(
"Генерация DOCX-отчётов из Markdown по ТЗ МИРЭА / ГОСТ. "
"Типы: coursework/practice/vkr, APID_coursework, PIS_custom. "
"Без файла или с --gui открывается окно. "
"FODT: python -m md2fodt …"
),
)
parser.add_argument("filename", help="Путь до исходного markdown файла")
parser.add_argument(
"filename", nargs="?",
help="Путь до .md (или .docx с --check-pages). Без файла открывается GUI",
)
parser.add_argument(
"--gui", action="store_true",
help="Открыть графический интерфейс (можно сразу передать .md)",
)
parser.add_argument("-o", "--output", help="Путь до сгенерированного .docx")
parser.add_argument("-t", "--template", help="Путь до шаблона .docx")
parser.add_argument(
"--type", dest="doc_type", choices=DOC_TYPES, default="coursework",
help="Тип: coursework | practice | vkr | PIS_custom | APID_coursework",
"--type", dest="doc_type", choices=DOC_TYPES, default="practice",
help="Тип: practice | coursework | vkr | PIS_custom | APID_coursework (по умолчанию practice)",
)
parser.add_argument(
"--heading-numbering",
@@ -64,19 +67,53 @@ def main():
default=DEFAULT_TABLE_CONTINUATION,
help=(
"Таблицы длиннее страницы: "
"off/soft — одна таблица, пагинация Word, без авто«Продолжение» (по умолчанию); "
"word — после сохранения Word COM режет по реальной пагинации + «Продолжение…» "
"(по умолчанию; нужны Windows, Word, pywin32); "
"off/soft — одна таблица, пагинация Word, без авто«Продолжение»; "
"legacy/caption — режем по оценке высоты и вставляем «Продолжение…» "
"(оценка ≠ Word, возможны артефакты). "
f"По умолчанию: {DEFAULT_TABLE_CONTINUATION}."
),
)
parser.add_argument(
"--listing-continuation",
choices=LISTING_CONTINUATION_MODES,
default=DEFAULT_LISTING_CONTINUATION,
help=(
"Листинги длиннее страницы: "
"word — после сохранения Word COM + «Продолжение Листинга…» "
"(по умолчанию; нужны Windows, Word, pywin32); "
"off/soft — один блок, пагинация Word, без авто«Продолжение»; "
"legacy/caption — режем по оценке высоты и вставляем «Продолжение Листинга…» "
"(оценка ≠ Word, возможны артефакты). "
f"По умолчанию: {DEFAULT_LISTING_CONTINUATION}."
),
)
parser.add_argument(
"--table-repeat-header",
action=BooleanOptionalAction,
default=False,
help=(
"При --table-continuation word: повторять шапку таблицы на каждом фрагменте "
"после разрыва. По умолчанию выкл."
),
)
parser.add_argument(
"--emdash-to-hyphen",
action=BooleanOptionalAction,
default=True,
default=False,
help=(
"Автозамена типографского тире «—» на дефис «-» в тексте и подписях. "
"Включено по умолчанию; отключить: --no-emdash-to-hyphen."
"По умолчанию выключено (тире «—» по методичке); включить: --emdash-to-hyphen."
),
)
parser.add_argument(
"--hr-pagebreak",
action=BooleanOptionalAction,
default=False,
help=(
"Строка «---» / «***» / «___» — разрыв страницы Word. "
"По умолчанию такие строки игнорируются; включить: --hr-pagebreak."
),
)
parser.add_argument("--title", help="DOCX титульного листа (вставляется перед телом)")
@@ -85,6 +122,15 @@ def main():
action="store_true")
parser.add_argument("--check-only", help="Только проверка, без генерации документа",
action="store_true")
parser.add_argument(
"--check-pages",
help=(
"После конвертации (или для готового .docx) проверить полупустые страницы "
"через Microsoft Word. Эвристика — возможны ложные срабатывания. "
"Нужны Windows, Word и pywin32."
),
action="store_true",
)
parser.add_argument("--strict", help="Код выхода 1 при ошибках проверки",
action="store_true")
parser.add_argument("--syntax-highlighting", help="Подсветка синтаксиса в листингах",
@@ -103,108 +149,104 @@ def main():
default="remote",
help="Если локальный рендер UML недоступен: remote (kroki.io), local (ошибка), off",
)
parser.add_argument(
"--diagram-format",
choices=["png", "svg"],
default="png",
help="Формат схем в Word: png (по умолчанию) или svg (вектор + PNG-запасной, Word 2016+)",
)
parser.add_argument(
"--diagram-scale",
type=float,
default=2.0,
help="Масштаб рендера PlantUML PNG (качество); размер на странице как при 1. По умолчанию 2",
)
parser.add_argument(
"--schemes",
dest="schemes_path",
help="Путь к md2gost.schemes.json (иначе рядом с приложением / с .md)",
)
parser.add_argument("--debug", help="Добавляет отладочные данные в документ",
action="store_true")
return parser
args = parser.parse_args()
filename, output, template, debug = \
args.filename, args.output, args.template, args.debug
if args.syntax_highlighting:
os.environ["SYNTAX_HIGHLIGHTING"] = "1"
from .diagram_renderer import configure_diagrams
configure_diagrams(
plantuml_jar=args.plantuml_jar,
kroki_url=args.kroki_url,
fallback=args.diagram_fallback,
)
if not filename.endswith(".md"):
print("Error: filename must have md format")
exit(1)
os.environ["WORKING_DIR"] = os.path.dirname(os.path.abspath(filename)) or "."
with open(filename, encoding="utf-8") as f:
md_text = f.read()
if args.check or args.check_only:
issues = check_markdown(md_text, args.doc_type)
print(format_report(issues))
errors = [i for i in issues if i.severity == "error"]
if args.strict and errors:
sys.exit(1)
if args.check_only:
sys.exit(0 if not errors else (1 if args.strict else 0))
if not output:
output = os.path.basename(filename).replace(".md", ".docx")
elif not output.endswith(".docx"):
print("Error: output file must have docx format")
exit(1)
if not template:
from . import package_dir
template = os.path.join(package_dir(), "Template.docx")
converter = Converter(
filename, output, template, debug,
def request_from_args(args) -> ConvertRequest:
return ConvertRequest(
filename=args.filename or "",
output=args.output,
template=args.template,
doc_type=args.doc_type,
heading_numbering=args.heading_numbering,
emdash_to_hyphen=args.emdash_to_hyphen,
toc_mode=args.toc,
table_continuation=args.table_continuation,
listing_continuation=args.listing_continuation,
table_repeat_header=bool(getattr(args, "table_repeat_header", False)),
emdash_to_hyphen=args.emdash_to_hyphen,
hr_pagebreak=args.hr_pagebreak,
title=args.title,
assignment=args.assignment,
check=args.check,
check_only=args.check_only,
check_pages=bool(getattr(args, "check_pages", False)),
strict=args.strict,
syntax_highlighting=bool(args.syntax_highlighting),
plantuml_jar=args.plantuml_jar,
kroki_url=args.kroki_url,
diagram_fallback=args.diagram_fallback,
diagram_format=args.diagram_format,
diagram_scale=float(args.diagram_scale),
schemes_path=args.schemes_path,
debug=args.debug,
open_when_done=bool(args.debug),
)
converter.convert()
document = converter.document
# Front matter is appended *into* a shell that already has coursework styles.
# Never use title.docx as compose base: python-docx default template has
# Calibri + accent-blue headings and would override ГОСТ стили.
if args.title or args.assignment:
def _argv_needs_console(argv: list[str]) -> bool:
"""Frozen windowed exe: attach a console for CLI / --help, not for GUI."""
args = argv[1:]
if not args:
return False
if "-h" in args or "--help" in args:
return True
if "--gui" in args:
return False
return any(not a.startswith("-") for a in args)
def _enable_windows_console() -> None:
if sys.platform != "win32" or not getattr(sys, "frozen", False):
return
try:
import ctypes
kernel32 = ctypes.windll.kernel32
if not kernel32.AttachConsole(0xFFFFFFFF):
if not kernel32.GetConsoleWindow():
kernel32.AllocConsole()
sys.stdout = open("CONOUT$", "w", encoding="utf-8", errors="replace")
sys.stderr = open("CONOUT$", "w", encoding="utf-8", errors="replace")
try:
from docxcompose.composer import Composer
except ImportError:
print("Error: docxcompose required for --title/--assignment")
sys.exit(3)
from .styles import apply_document_styles
sys.stdin = open("CONIN$", "r", encoding="utf-8", errors="replace")
except OSError:
pass
except Exception:
pass
shell = Document(template)
apply_document_styles(shell, get_profile(args.doc_type).style_preset)
body = shell.element.body
for child in list(body):
if not child.tag.endswith("}sectPr"):
body.remove(child)
composer = Composer(shell)
if args.title:
composer.append(Document(args.title))
shell.add_page_break()
if args.assignment:
composer.append(Document(args.assignment))
shell.add_page_break()
composer.append(document)
document = composer.doc
apply_document_styles(document, get_profile(args.doc_type).style_preset)
def main():
if _argv_needs_console(sys.argv):
_enable_windows_console()
parser = build_parser()
args = parser.parse_args()
req = request_from_args(args)
document.core_properties.author = getuser()
document.core_properties.comments = \
"Создано при помощи md2gost (ТЗ МИРЭА)"
if should_launch_gui(args.filename, args.gui):
from .gui import run_gui
run_gui(req)
return
document.save(output)
print(f"Generated document: {os.path.abspath(output)}")
if debug:
import platform
if platform.system() == 'Darwin':
import subprocess
subprocess.call(('open', output))
elif platform.system() == 'Windows':
os.startfile(output)
else:
import subprocess
subprocess.call(('xdg-open', output))
result = convert(req)
sys.exit(result.exit_code)
if __name__ == "__main__":