Files
Minint/Report/append_sources_to_report.py
2026-04-07 21:00:49 +03:00

144 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""
Append source code listings to a Markdown report.
Usage example:
python3 Report/append_sources_to_report.py \
--input Report/lab2/zivro-lab2-report.md \
--output Report/lab2/zivro-lab2-report-with-code.md \
--base . \
--include "Minint/**/*.cs" \
--include "Minint.Core/**/*.cs" \
--include "Minint.Infrastructure/**/*.cs" \
--include "Minint.Tests/**/*.cs"
"""
from __future__ import annotations
import argparse
from pathlib import Path
EXT_TO_LANG: dict[str, str] = {
".cs": "csharp",
".axaml": "xml",
".xml": "xml",
".json": "json",
".md": "markdown",
".sh": "bash",
".py": "python",
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Append code listings to report markdown.")
parser.add_argument("--input", required=True, help="Path to source markdown report.")
parser.add_argument("--output", required=True, help="Path to resulting markdown report.")
parser.add_argument(
"--base",
default=".",
help="Project root path used for resolving include patterns.",
)
parser.add_argument(
"--include",
action="append",
default=[],
help="Glob pattern relative to --base. Can be passed multiple times.",
)
parser.add_argument(
"--exclude-substring",
action="append",
default=["/bin/", "/obj/", ".git/"],
help="Skip files that contain this substring in path.",
)
parser.add_argument(
"--title",
default="Приложение A. Исходные тексты",
help="Heading title for generated appendix.",
)
return parser.parse_args()
def detect_lang(path: Path) -> str:
return EXT_TO_LANG.get(path.suffix.lower(), "text")
def should_skip(path: Path, excludes: list[str]) -> bool:
normalized = str(path).replace("\\", "/")
return any(sub in normalized for sub in excludes)
def collect_files(base: Path, patterns: list[str], excludes: list[str]) -> list[Path]:
files: list[Path] = []
seen: set[Path] = set()
for pattern in patterns:
for p in base.glob(pattern):
if not p.is_file():
continue
if should_skip(p, excludes):
continue
rp = p.resolve()
if rp in seen:
continue
seen.add(rp)
files.append(rp)
files.sort(key=lambda p: str(p).replace("\\", "/"))
return files
def build_appendix(base: Path, files: list[Path], title: str) -> str:
lines: list[str] = []
lines.append("")
lines.append("---")
lines.append("")
lines.append(f"## {title}")
lines.append("")
lines.append(
f"Сформировано автоматически скриптом `Report/append_sources_to_report.py` (файлов: {len(files)})."
)
lines.append("")
for i, file_path in enumerate(files, start=1):
rel = file_path.relative_to(base).as_posix()
lang = detect_lang(file_path)
content = file_path.read_text(encoding="utf-8", errors="replace").rstrip()
lines.append(f"### A.{i}. `{rel}`")
lines.append("")
lines.append(f"```{lang}")
if content:
lines.append(content)
lines.append("```")
lines.append("")
return "\n".join(lines)
def main() -> int:
args = parse_args()
input_md = Path(args.input).resolve()
output_md = Path(args.output).resolve()
base = Path(args.base).resolve()
patterns = args.include or ["**/*.cs"]
if not input_md.is_file():
raise FileNotFoundError(f"Input markdown not found: {input_md}")
if not base.exists():
raise FileNotFoundError(f"Base path not found: {base}")
report_body = input_md.read_text(encoding="utf-8")
files = collect_files(base, patterns, args.exclude_substring)
appendix = build_appendix(base, files, args.title)
output_md.parent.mkdir(parents=True, exist_ok=True)
output_md.write_text(report_body.rstrip() + "\n" + appendix + "\n", encoding="utf-8")
print(f"Input: {input_md}")
print(f"Output: {output_md}")
print(f"Files appended: {len(files)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())