#!/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())