- Add\Rework UI - Add Split Table and Listing - Add Support Customazeble schems
This commit is contained in:
+147
-105
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user