Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
156 lines
5.9 KiB
Python
156 lines
5.9 KiB
Python
"""Понижение XML-док комментариев с private/internal членов до обычных `//` (код-стайл «Дейл» §5).
|
||
|
||
Правило: XML-doc — только на public/protected. Приватные детали реализации — при необходимости короткий
|
||
обычный комментарий. Скрипт сохраняет текст (теги <param>/<returns> и т.п. разворачиваются в читаемый вид).
|
||
|
||
Режимы:
|
||
python scripts/fix_private_docs.py --preview [N] # показать N примеров (по умолчанию 10)
|
||
python scripts/fix_private_docs.py --apply # применить
|
||
python scripts/fix_private_docs.py --check # сколько блоков будет изменено
|
||
"""
|
||
import os
|
||
import re
|
||
import sys
|
||
|
||
ROOT = r"C:\telbase\src"
|
||
EXC = ("bin", "obj", "node_modules", ".git")
|
||
DOC_RE = re.compile(r"^(\s*)///[ \t]?(.*)$")
|
||
ACC_RE = re.compile(r"\b(public|protected|private|internal)\b")
|
||
|
||
|
||
def demote(block_content):
|
||
"""block_content: список строк после '///'. Возвращает список строк текста обычного комментария."""
|
||
t = "\n".join(block_content)
|
||
t = re.sub(r"<summary>\s*(.*?)\s*</summary>", r"\1", t, flags=re.S)
|
||
t = re.sub(r"<remarks>\s*(.*?)\s*</remarks>", r"\1", t, flags=re.S)
|
||
t = re.sub(r"<typeparamref\s+name=\"([^\"]+)\"\s*/?>", r"\1", t)
|
||
t = re.sub(r"<paramref\s+name=\"([^\"]+)\"\s*/?>", r"\1", t)
|
||
t = re.sub(r"<typeparam\s+name=\"([^\"]+)\"\s*>\s*(.*?)\s*</typeparam>", r"\1: \2", t, flags=re.S)
|
||
t = re.sub(r"<param\s+name=\"([^\"]+)\"\s*>\s*(.*?)\s*</param>", r"\1: \2", t, flags=re.S)
|
||
t = re.sub(r"<returns>\s*(.*?)\s*</returns>", r"Возвращает: \1", t, flags=re.S)
|
||
t = re.sub(
|
||
r"<exception\s+cref=\"([^\"]+)\"\s*>\s*(.*?)\s*</exception>",
|
||
r"Исключение \1: \2",
|
||
t,
|
||
flags=re.S,
|
||
)
|
||
t = re.sub(r"<see\s+cref=\"([^\"]+)\"\s*/?>", r"\1", t)
|
||
t = re.sub(r"<seealso\s+cref=\"[^\"]+\"\s*/?>", "", t)
|
||
t = re.sub(r"<inheritdoc\s*/?>", "", t)
|
||
t = re.sub(r"</?c>", "", t)
|
||
t = re.sub(r"<[^>]+>", "", t)
|
||
out = []
|
||
for line in t.split("\n"):
|
||
s = line.strip()
|
||
if s:
|
||
out.append(s)
|
||
return out
|
||
|
||
|
||
def iter_cs():
|
||
for dp, dn, fn in os.walk(ROOT):
|
||
dn[:] = [d for d in dn if d not in EXC]
|
||
for f in fn:
|
||
if f.endswith(".cs"):
|
||
yield os.path.join(dp, f)
|
||
|
||
|
||
def find_blocks(lines):
|
||
"""Возвращает список (start, end, block_content, decl_index, decl)."""
|
||
res = []
|
||
i = 0
|
||
while i < len(lines):
|
||
m = DOC_RE.match(lines[i].rstrip("\r"))
|
||
if not m:
|
||
i += 1
|
||
continue
|
||
start = i
|
||
block = []
|
||
while i < len(lines):
|
||
m2 = DOC_RE.match(lines[i].rstrip("\r"))
|
||
if not m2:
|
||
break
|
||
block.append(m2.group(2))
|
||
i += 1
|
||
end = i # exclusive
|
||
j = i
|
||
decl = ""
|
||
while j < len(lines):
|
||
s = lines[j].strip()
|
||
if s == "" or s.startswith("["):
|
||
j += 1
|
||
continue
|
||
decl = s
|
||
break
|
||
if not decl:
|
||
continue
|
||
if not any("<summary>" in b or "</summary>" in b for b in block):
|
||
continue
|
||
am = ACC_RE.search(decl)
|
||
if am and am.group(1) in ("private", "internal"):
|
||
res.append((start, end, block, decl))
|
||
return res
|
||
|
||
|
||
def process_file(path, apply):
|
||
raw = open(path, "rb").read().decode("utf-8")
|
||
lines = raw.splitlines(keepends=True)
|
||
plain = [l.rstrip("\r\n") for l in lines]
|
||
endings = [l[len(l.rstrip("\r\n")):] for l in lines]
|
||
blocks = find_blocks(plain)
|
||
if not blocks:
|
||
return 0, []
|
||
changes = []
|
||
for start, end, block, decl in blocks:
|
||
indent = DOC_RE.match(plain[start]).group(1)
|
||
comments = demote(block)
|
||
new_lines = [indent + "//" + (" " + c if c else "") for c in comments]
|
||
changes.append((start, end, new_lines, decl))
|
||
# применяем с конца
|
||
for start, end, new_lines, decl in reversed(changes):
|
||
ending = endings[start] if endings[start] else (endings[end - 1] if end > start else "\n")
|
||
replacement = [nl + ending for nl in new_lines]
|
||
lines[start:end] = replacement
|
||
if apply:
|
||
open(path, "w", encoding="utf-8", newline="").write("".join(lines))
|
||
return len(changes), [(b[0], b[3], demote(b[2])) for b in [(s, e, bl, d) for s, e, bl, d in blocks]]
|
||
|
||
|
||
def main():
|
||
if "--preview" in sys.argv:
|
||
n = 10
|
||
idx = sys.argv.index("--preview")
|
||
if idx + 1 < len(sys.argv) and sys.argv[idx + 1].isdigit():
|
||
n = int(sys.argv[idx + 1])
|
||
shown = 0
|
||
for path in iter_cs():
|
||
raw = open(path, "rb").read().decode("utf-8")
|
||
plain = [l.rstrip("\r\n") for l in raw.split("\n")]
|
||
for start, end, block, decl in find_blocks(plain):
|
||
print("FILE:", os.path.relpath(path, r"C:\telbase"), f"line {start + 1}")
|
||
print(" DECL:", decl[:100])
|
||
print(" BEFORE:")
|
||
for b in plain[start:end]:
|
||
print(" ", b)
|
||
print(" AFTER:")
|
||
for c in demote(block):
|
||
print(" // " + c)
|
||
print()
|
||
shown += 1
|
||
if shown >= n:
|
||
return
|
||
return
|
||
apply = "--apply" in sys.argv
|
||
total = 0
|
||
files = 0
|
||
for path in iter_cs():
|
||
cnt, _ = process_file(path, apply)
|
||
if cnt:
|
||
files += 1
|
||
total += cnt
|
||
print(("APPLIED" if apply else "DRY-RUN"), "blocks:", total, "files:", files)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|