diff options
| author | Valentin Popov <valentin@popov.link> | 2026-02-10 01:39:12 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-02-10 01:39:12 +0300 |
| commit | 828106ba810063801b14ea3d5b0d33802fcf0d2b (patch) | |
| tree | fb03981ae853d20655db4e383406c2c19525388d /tools/init_testdata.py | |
| parent | a7dd18fa1d82ef6a561797afb2dd6dcb3f298e4a (diff) | |
| download | fparkan-828106ba810063801b14ea3d5b0d33802fcf0d2b.tar.xz fparkan-828106ba810063801b14ea3d5b0d33802fcf0d2b.zip | |
feat: добавить скрипты для инициализации тестовых данных и настройки окружения
Diffstat (limited to 'tools/init_testdata.py')
| -rw-r--r-- | tools/init_testdata.py | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/tools/init_testdata.py b/tools/init_testdata.py new file mode 100644 index 0000000..4079cdb --- /dev/null +++ b/tools/init_testdata.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +""" +Initialize test data folders by archive signatures. + +The script scans all files in --input and copies matching archives into: + --output/nres/<relative path> + --output/rsli/<relative path> +""" + +from __future__ import annotations + +import argparse +import shutil +import sys +from pathlib import Path + +MAGIC_NRES = b"NRes" +MAGIC_RSLI = b"NL\x00\x01" + + +def is_relative_to(path: Path, base: Path) -> bool: + try: + path.relative_to(base) + except ValueError: + return False + return True + + +def detect_archive_type(path: Path) -> str | None: + try: + with path.open("rb") as handle: + magic = handle.read(4) + except OSError as exc: + print(f"[warn] cannot read {path}: {exc}", file=sys.stderr) + return None + + if magic == MAGIC_NRES: + return "nres" + if magic == MAGIC_RSLI: + return "rsli" + return None + + +def scan_archives(input_root: Path, excluded_root: Path | None) -> list[tuple[Path, str]]: + found: list[tuple[Path, str]] = [] + for path in sorted(input_root.rglob("*")): + if not path.is_file(): + continue + if excluded_root and is_relative_to(path.resolve(), excluded_root): + continue + + archive_type = detect_archive_type(path) + if archive_type: + found.append((path, archive_type)) + return found + + +def confirm_overwrite(path: Path) -> str: + prompt = ( + f"File exists: {path}\n" + "Overwrite? [y]es / [n]o / [a]ll / [q]uit (default: n): " + ) + while True: + try: + answer = input(prompt).strip().lower() + except EOFError: + return "quit" + + if answer in {"", "n", "no"}: + return "no" + if answer in {"y", "yes"}: + return "yes" + if answer in {"a", "all"}: + return "all" + if answer in {"q", "quit"}: + return "quit" + print("Please answer with y, n, a, or q.") + + +def copy_archives( + archives: list[tuple[Path, str]], + input_root: Path, + output_root: Path, + force: bool, +) -> int: + copied = 0 + skipped = 0 + overwritten = 0 + overwrite_all = force + + type_counts = {"nres": 0, "rsli": 0} + for _, archive_type in archives: + type_counts[archive_type] += 1 + + print( + f"Found archives: total={len(archives)}, " + f"nres={type_counts['nres']}, rsli={type_counts['rsli']}" + ) + + for source, archive_type in archives: + rel_path = source.relative_to(input_root) + destination = output_root / archive_type / rel_path + destination.parent.mkdir(parents=True, exist_ok=True) + + if destination.exists(): + if destination.is_dir(): + print( + f"[error] destination is a directory, expected file: {destination}", + file=sys.stderr, + ) + return 2 + + if not overwrite_all: + if not sys.stdin.isatty(): + print( + "[error] destination file exists but stdin is not interactive. " + "Use --force to overwrite without prompts.", + file=sys.stderr, + ) + return 2 + + decision = confirm_overwrite(destination) + if decision == "quit": + print("Aborted by user.") + return 130 + if decision == "no": + skipped += 1 + continue + if decision == "all": + overwrite_all = True + + overwritten += 1 + + try: + shutil.copy2(source, destination) + except OSError as exc: + print(f"[error] failed to copy {source} -> {destination}: {exc}", file=sys.stderr) + return 2 + copied += 1 + + print( + f"Done: copied={copied}, overwritten={overwritten}, skipped={skipped}, " + f"output={output_root}" + ) + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Initialize test data by scanning NRes/RsLi signatures." + ) + parser.add_argument( + "--input", + required=True, + help="Input directory to scan recursively.", + ) + parser.add_argument( + "--output", + required=True, + help="Output root directory (archives go to nres/ and rsli/ subdirs).", + ) + parser.add_argument( + "--force", + action="store_true", + help="Overwrite destination files without confirmation prompts.", + ) + return parser + + +def main() -> int: + args = build_parser().parse_args() + + input_root = Path(args.input) + if not input_root.exists(): + print(f"[error] input directory does not exist: {input_root}", file=sys.stderr) + return 2 + if not input_root.is_dir(): + print(f"[error] input path is not a directory: {input_root}", file=sys.stderr) + return 2 + + output_root = Path(args.output) + if output_root.exists() and not output_root.is_dir(): + print(f"[error] output path exists and is not a directory: {output_root}", file=sys.stderr) + return 2 + + input_resolved = input_root.resolve() + output_resolved = output_root.resolve() + if input_resolved == output_resolved: + print("[error] input and output directories must be different.", file=sys.stderr) + return 2 + + excluded_root: Path | None = None + if is_relative_to(output_resolved, input_resolved): + excluded_root = output_resolved + print(f"Notice: output is inside input, skipping scan under: {excluded_root}") + + archives = scan_archives(input_root, excluded_root) + + output_root.mkdir(parents=True, exist_ok=True) + return copy_archives(archives, input_root, output_root, force=args.force) + + +if __name__ == "__main__": + raise SystemExit(main()) |
