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
+144 -17
View File
@@ -9,7 +9,7 @@ import subprocess
import traceback
from dataclasses import dataclass
from datetime import datetime
from getpass import getuser
from pathlib import Path
from typing import Callable
from docx import Document
@@ -41,6 +41,10 @@ class ConvertRequest:
hr_pagebreak: bool = False
title: str | None = None
assignment: str | None = None
student: str | None = None
group: str | None = None
# Auto-build title from info_conv.yaml / ```title (off by default).
auto_title: bool = False
check: bool = False
check_only: bool = False
strict: bool = False
@@ -58,6 +62,15 @@ class ConvertRequest:
table_repeat_header: bool = False
# PAGE field start on body section; None = continuous from document start.
page_number_start: int | None = None
# DOCX File → Info; None = take from md2gost.user.json (see doc_metadata).
doc_author_source: str | None = None
doc_author: str | None = None
doc_last_modified_by: str | None = None
doc_title: str | None = None
doc_subject: str | None = None
doc_keywords: str | None = None
doc_comments: str | None = None
doc_category: str | None = None
@dataclass
@@ -127,16 +140,33 @@ def _apply_page_offset(document, req: ConvertRequest, *, had_title: bool, had_as
from .page_geometry import apply_page_number_start
start = getattr(req, "page_number_start", None)
start_i: int | None
if start is None:
return
try:
start_i = int(start)
except (TypeError, ValueError):
return
start_i = None
else:
try:
start_i = int(start)
except (TypeError, ValueError):
start_i = None
front = int(bool(had_title)) + int(bool(had_assignment))
# Always strip copied w:pgNumType/@w:start (landscape sections); optional body restart.
apply_page_number_start(document, start_i, front_sections=front)
def _page_offset_args(req: ConvertRequest, *, had_title: bool, had_assignment: bool) -> tuple[int | None, int]:
"""Return (page_number_start, front_sections) for post-Word patch."""
start = getattr(req, "page_number_start", None)
if start is None:
start_i = None
else:
try:
start_i = int(start)
except (TypeError, ValueError):
start_i = None
front = int(bool(had_title)) + int(bool(had_assignment))
return start_i, front
def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
"""Run check and/or conversion. Does not re-raise; errors go into ConvertResult."""
emit: LogFn = log if log is not None else print
@@ -154,6 +184,24 @@ def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
emit(report)
return ConvertResult(True, 0, check_report=report, message=report)
# DOCX → Markdown (word2md) when input is .docx without --check-pages
if filename.lower().endswith(".docx"):
from word2md import ImportRequest, convert_docx
out = (req.output or "").strip() or None
if out and out.lower().endswith(".docx"):
out = str(Path(filename).with_suffix(".md"))
result = convert_docx(
ImportRequest(filename=filename, output=out),
log=emit,
)
return ConvertResult(
result.ok,
result.exit_code,
output_path=result.output_path,
message=result.message,
)
if not filename:
return fail(2, "Укажите исходный markdown-файл")
if not filename.lower().endswith(".md"):
@@ -192,10 +240,57 @@ def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
with open(filename, encoding="utf-8") as f:
md_text = f.read()
from .title_page import (
build_title_docx,
load_info_conv,
merge_title_meta,
parse_and_strip_title_fence,
should_generate_title,
)
from .user_profile import load_user_profile
fence_fields, body_md = parse_and_strip_title_fence(md_text)
yaml_fields = load_info_conv(md_dir)
generated_title_path: str | None = None
title_path = (req.title or "").strip() or None
if not output:
output = default_output_path(filename)
if should_generate_title(
explicit_title=title_path,
fence_fields=fence_fields,
md_dir=md_dir,
enabled=bool(req.auto_title),
):
profile = load_user_profile()
student = (req.student or "").strip() or profile.student
group = (req.group or "").strip() or profile.group
if not student or not group:
return fail(
2,
"Для генерации титула нужны ФИО и группа студента "
"(GUI: Настройки → Студент…, или CLI: --student / --group).",
)
meta = merge_title_meta(
yaml_fields=yaml_fields,
fence_fields=fence_fields,
student=student,
group=group,
)
out_dir = os.path.dirname(os.path.abspath(output)) or md_dir
generated_title_path = os.path.join(out_dir, ".md2gost_title.docx")
try:
build_title_docx(meta, generated_title_path)
title_path = generated_title_path
emit(f"Титульный лист: {generated_title_path}")
except Exception as exc:
return fail(1, f"Не удалось сгенерировать титул: {exc}")
check_report = ""
if req.check or req.check_only:
issues = check_markdown(
md_text,
body_md,
req.doc_type,
table_continuation=req.table_continuation,
listing_continuation=req.listing_continuation,
@@ -211,9 +306,6 @@ def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
message=check_report,
)
if not output:
output = default_output_path(filename)
template = (req.template or "").strip() or default_template_path()
handler = _CallbackLogHandler(emit)
@@ -236,11 +328,12 @@ def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
listing_continuation=req.listing_continuation,
hr_pagebreak=req.hr_pagebreak,
styles_path=req.styles_path or None,
markdown_text=body_md,
)
converter.convert()
document = converter.document
if req.title or req.assignment:
if title_path or req.assignment:
try:
from docxcompose.composer import Composer
except ImportError:
@@ -256,8 +349,8 @@ def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
body.remove(child)
composer = Composer(shell)
if req.title:
composer.append(Document(req.title))
if title_path:
composer.append(Document(title_path))
shell.add_page_break()
if req.assignment:
composer.append(Document(req.assignment))
@@ -267,19 +360,36 @@ def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
apply_style_config(document, style_cfg)
_fix_front_matter_after_compose(
document,
had_title=bool(req.title),
had_title=bool(title_path),
had_assignment=bool(req.assignment),
)
_apply_page_offset(
document,
req,
had_title=bool(req.title),
had_title=bool(title_path),
had_assignment=bool(req.assignment),
)
document.core_properties.author = getuser()
document.core_properties.comments = "Создано при помощи md2gost (ТЗ МИРЭА)"
from .doc_metadata import apply_document_metadata, resolve_document_metadata
from .user_profile import load_user_profile as _load_user_profile
profile = _load_user_profile()
apply_document_metadata(
document,
resolve_document_metadata(
stored=profile.metadata,
student=(req.student or "").strip() or profile.student,
author_source=req.doc_author_source,
author=req.doc_author,
last_modified_by=req.doc_last_modified_by,
title=req.doc_title,
subject=req.doc_subject,
keywords=req.doc_keywords,
comments=req.doc_comments,
category=req.doc_category,
),
)
try:
document.save(output)
except PermissionError:
@@ -297,12 +407,19 @@ def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
root_log.removeHandler(handler)
attached_diag.removeHandler(handler)
attached_diag.setLevel(prev_diag_level)
if generated_title_path and os.path.isfile(generated_title_path):
try:
os.remove(generated_title_path)
except OSError:
pass
abs_out = os.path.abspath(output)
emit(f"Generated document: {abs_out}")
if req.table_continuation == "word" or req.listing_continuation == "word":
from .word_fix import fix_continuations
from .page_geometry import patch_docx_page_starts
fix = fix_continuations(
abs_out,
tables=(req.table_continuation == "word"),
@@ -317,6 +434,16 @@ def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult:
check_report = (
(check_report + "\n" + fix.message).strip() if check_report else fix.message
)
# Word Save often rewrites w:pgNumType/@w:start=\"1\" on every section — strip again.
start_i, front = _page_offset_args(
req,
had_title=bool(title_path),
had_assignment=bool(req.assignment),
)
try:
patch_docx_page_starts(abs_out, start_i, front_sections=front)
except Exception as exc:
emit(f"Не удалось восстановить сквозную нумерацию после Word: {exc}")
if req.check_pages:
from .page_fill_check import check_docx_page_fill, format_page_fill_report