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
+105 -14
View File
@@ -258,12 +258,36 @@ def check_bibliography(text: str, profile: DocProfile) -> list[Issue]:
n = len(entries)
if profile.sectional_biblio:
# Count per ## section roughly
if n < profile.min_sources:
# Count per ## section inside bibliography
sections: list[tuple[str, int]] = []
current_title: str | None = None
current_count = 0
for line in block.splitlines():
hm = re.match(r"^#{2,6}\s+(\*?)(.+)$", line)
if hm:
if current_title is not None:
sections.append((current_title, current_count))
current_title = hm.group(2).strip()
current_count = 0
continue
if BIBLIO_LINE_RE.match(line.strip()):
current_count += 1
if current_title is not None:
sections.append((current_title, current_count))
if sections:
for title, cnt in sections:
if cnt < profile.min_sources:
issues.append(Issue(
"biblio.count", "warning",
f"ВКР: в разделе списка «{title}» желательно ≥{profile.min_sources} "
f"источников (сейчас {cnt})",
))
elif n < profile.min_sources:
issues.append(Issue(
"biblio.count", "warning",
f"ВКР: в каждом разделе списка желательно{profile.min_sources} источников "
f"(сейчас всего {n})",
f"ВКР: список должен делиться на разделы; в каждом{profile.min_sources} "
f"источников (сейчас всего {n}, разделов нет)",
))
else:
if n < profile.min_sources:
@@ -288,13 +312,10 @@ def check_bibliography(text: str, profile: DocProfile) -> list[Issue]:
))
# Citation order vs first appearance
# Exclude intro/conclusion from citation scan for order
body_for_cites = text
cites = find_citations(body_for_cites)
# Filter cites that appear only in intro/conclusion — still listed
keys = [e[0] for e in entries]
if cites and keys:
# First N unique cites should match order of keys for simple lists
if not profile.sectional_biblio:
expected = cites[:len(keys)]
if keys != expected and set(keys) == set(expected):
@@ -313,16 +334,15 @@ def check_bibliography(text: str, profile: DocProfile) -> list[Issue]:
def check_object_refs(text: str) -> list[Issue]:
issues = []
# Captions / images
labels = set(re.findall(r"^%(\w+)", text, re.M))
# image titles with %id
labels.update(re.findall(r'!\[[^\]]*\]\([^)]*%(\w+)', text))
refs = set(re.findall(r"@[\wА-Яа-я]+:(\w+)", text))
for lab in labels:
# At least one @?:lab or word reference — soft check
if lab not in refs and f"@{lab}" not in text:
# only warn if label looks intentional
pass
if lab not in refs:
issues.append(Issue(
"ref.unused", "warning",
f"Метка «{lab}» объявлена, но в тексте нет ссылки @…:{lab}",
))
for ref in refs:
if ref not in labels:
issues.append(Issue(
@@ -342,6 +362,66 @@ def check_appendices(text: str) -> list[Issue]:
f"Буква «{letter}» не используется для обозначения приложений",
_line_of(text, m.start()),
))
# Multiple lettered appendices should have a list after # *ПРИЛОЖЕНИЯ
apps = list(re.finditer(
r"^#{1,6}\s+\*?Приложение\s+[А-ЯA-ZЁ]\b",
text, re.M | re.I,
))
# Filter out the section header «ПРИЛОЖЕНИЯ» / bare «ПРИЛОЖЕНИЕ»
lettered = []
for m in apps:
line = m.group(0)
if re.search(r"(?i)приложение\s+[А-ЯA-ZЁ]", line):
lettered.append(m)
if len(lettered) >= 2:
sec = re.search(
r"^#\s*\*?\s*ПРИЛОЖЕНИЯ?\s*$",
text, re.M | re.I,
)
if sec:
between = text[sec.end(): lettered[0].start()]
# Expect a plain-text list (not only blank / headings)
has_list = bool(re.search(r"(?im)^\s*[-–—*]|\bприложение\s+[А-ЯA-ZЁ]\b", between))
has_prose = bool(re.search(r"[А-Яа-яA-Za-z]{3,}", between))
if not (has_list or has_prose):
issues.append(Issue(
"appendix.toc", "warning",
"При нескольких приложениях после «ПРИЛОЖЕНИЯ» нужен перечень "
"(основной текст) с номерами и названиями",
_line_of(text, sec.start()),
))
return issues
def check_continuation_hints(
text: str,
*,
table_continuation: str = "off",
listing_continuation: str = "off",
) -> list[Issue]:
"""Warn that Word cannot auto-insert «Продолжение…» when mode is off/soft."""
issues: list[Issue] = []
soft = {"off", "soft"}
if table_continuation in soft and _TABLE_BLOCK_RE.search(text):
issues.append(Issue(
"table.continuation", "warning",
"Word сам не вставит «Продолжение Таблицы N» при переносе. "
"Варианты: --table-continuation word (точный разрыв через Word COM) "
"или caption (оценка высоты ≠ вёрстка Word, возможны артефакты).",
))
# Long fenced code blocks (likely listings)
long_listing = False
for m in re.finditer(r"^```[^\n]*\n([\s\S]*?)^```", text, re.M):
if m.group(1).count("\n") >= 40:
long_listing = True
break
if listing_continuation in soft and long_listing:
issues.append(Issue(
"listing.continuation", "warning",
"Длинный листинг: Word сам не вставит «Продолжение Листинга N». "
"Варианты: --listing-continuation word или caption.",
))
return issues
@@ -404,7 +484,13 @@ def check_table_merge(text: str) -> list[Issue]:
return issues
def check_markdown(text: str, doc_type: str = "coursework") -> list[Issue]:
def check_markdown(
text: str,
doc_type: str = "coursework",
*,
table_continuation: str = "off",
listing_continuation: str = "off",
) -> list[Issue]:
profile = get_profile(doc_type)
issues: list[Issue] = []
issues.extend(check_structure(text, profile))
@@ -417,6 +503,11 @@ def check_markdown(text: str, doc_type: str = "coursework") -> list[Issue]:
issues.extend(check_object_refs(text))
issues.extend(check_appendices(text))
issues.extend(check_table_merge(text))
issues.extend(check_continuation_hints(
text,
table_continuation=table_continuation,
listing_continuation=listing_continuation,
))
return issues