#!/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 .profiles import ( DOC_TYPES, HEADING_NUMBERING_MODES, DEFAULT_HEADING_NUMBERING, TOC_MODES, DEFAULT_TOC_MODE, TABLE_CONTINUATION_MODES, DEFAULT_TABLE_CONTINUATION, get_profile, ) from .checker import check_markdown, format_report def main(): parser = ArgumentParser( prog="md2gost", description=( "Генерация DOCX-отчётов из Markdown по ТЗ МИРЭА / ГОСТ. " "Типы: coursework/practice/vkr, APID_coursework, PIS_custom. " "FODT: python -m md2fodt …" ), ) parser.add_argument("filename", help="Путь до исходного markdown файла") 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", ) parser.add_argument( "--heading-numbering", choices=HEADING_NUMBERING_MODES, default=DEFAULT_HEADING_NUMBERING, help=( "Нумерация заголовков разделов: " "manual — цифры из markdown (# 1 … / ## 1.1 …), автонумерация Word отключена; " "auto — нумерует Word, цифры в начале заголовка md снимаются. " f"По умолчанию: {DEFAULT_HEADING_NUMBERING}." ), ) parser.add_argument( "--toc", choices=TOC_MODES, default=DEFAULT_TOC_MODE, help=( "Содержание: native — встроенное поле Word TOC (обновить при открытии); " "manual — собрать в md2gost с номерами страниц из layout. " f"По умолчанию: {DEFAULT_TOC_MODE}." ), ) parser.add_argument( "--table-continuation", choices=TABLE_CONTINUATION_MODES, default=DEFAULT_TABLE_CONTINUATION, help=( "Таблицы длиннее страницы: " "off/soft — одна таблица, пагинация Word, без авто«Продолжение» (по умолчанию); " "legacy/caption — режем по оценке высоты и вставляем «Продолжение…» " "(оценка ≠ Word, возможны артефакты). " f"По умолчанию: {DEFAULT_TABLE_CONTINUATION}." ), ) parser.add_argument( "--emdash-to-hyphen", action=BooleanOptionalAction, default=True, help=( "Автозамена типографского тире «—» на дефис «-» в тексте и подписях. " "Включено по умолчанию; отключить: --no-emdash-to-hyphen." ), ) parser.add_argument("--title", help="DOCX титульного листа (вставляется перед телом)") parser.add_argument("--assignment", help="DOCX бланка задания") parser.add_argument("--check", help="Проверить markdown по ТЗ и вывести отчёт", action="store_true") parser.add_argument("--check-only", help="Только проверка, без генерации документа", action="store_true") parser.add_argument("--strict", help="Код выхода 1 при ошибках проверки", action="store_true") parser.add_argument("--syntax-highlighting", help="Подсветка синтаксиса в листингах", action=BooleanOptionalAction) parser.add_argument( "--plantuml-jar", help="Путь к plantuml.jar (иначе env PLANTUML_JAR)", ) parser.add_argument( "--kroki-url", help="URL локального Kroki (иначе env KROKI_URL, default http://localhost:8000)", ) parser.add_argument( "--diagram-fallback", choices=["local", "remote", "off"], default="remote", help="Если локальный рендер UML недоступен: remote (kroki.io), local (ошибка), off", ) parser.add_argument("--debug", help="Добавляет отладочные данные в документ", action="store_true") 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, 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, ) 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: try: from docxcompose.composer import Composer except ImportError: print("Error: docxcompose required for --title/--assignment") sys.exit(3) from .styles import apply_document_styles 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) document.core_properties.author = getuser() document.core_properties.comments = \ "Создано при помощи md2gost (ТЗ МИРЭА)" 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)) if __name__ == "__main__": main()