refactor(clew): recreate extracted game tree
This commit is contained in:
103
src/clew/clew.py
103
src/clew/clew.py
@@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
from rpycdec.rpa import extract_rpa
|
from rpycdec.rpa import extract_rpa
|
||||||
|
|
||||||
LOGGER = logging.getLogger("clew")
|
LOGGER = logging.getLogger("clew")
|
||||||
MANAGED_DIRS = ("source", "work", "logs")
|
MANAGED_DIRS = ("extracted", "logs")
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv: list[str]) -> argparse.Namespace:
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
@@ -28,7 +28,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|||||||
"--output-root",
|
"--output-root",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=Path("output"),
|
default=Path("output"),
|
||||||
help="Preparation output root. Defaults to repository output/.",
|
help="Output root. Defaults to repository output/.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--force",
|
"--force",
|
||||||
@@ -58,76 +58,28 @@ def resolve_output_root(output_root: Path) -> Path:
|
|||||||
return output_root.resolve()
|
return output_root.resolve()
|
||||||
|
|
||||||
|
|
||||||
def prepare_output(output_root: Path, force: bool) -> tuple[Path, Path, Path]:
|
def prepare_output(output_root: Path, force: bool) -> tuple[Path, Path]:
|
||||||
for name in MANAGED_DIRS:
|
for name in MANAGED_DIRS:
|
||||||
path = output_root / name
|
path = output_root / name
|
||||||
if path.exists() and not force:
|
if path.exists() and not force:
|
||||||
fail(f"refusing to overwrite existing preparation path: {path}")
|
fail(f"refusing to overwrite existing output path: {path}")
|
||||||
|
|
||||||
if force:
|
if force:
|
||||||
for name in MANAGED_DIRS:
|
for name in MANAGED_DIRS:
|
||||||
shutil.rmtree(output_root / name, ignore_errors=True)
|
shutil.rmtree(output_root / name, ignore_errors=True)
|
||||||
|
|
||||||
source_dir = output_root / "source"
|
extracted_dir = output_root / "extracted"
|
||||||
work_dir = output_root / "work"
|
|
||||||
logs_dir = output_root / "logs"
|
logs_dir = output_root / "logs"
|
||||||
for path in (source_dir, work_dir, logs_dir):
|
for path in (extracted_dir, logs_dir):
|
||||||
path.mkdir(parents=True, exist_ok=True)
|
path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
return source_dir, work_dir, logs_dir
|
return extracted_dir, logs_dir
|
||||||
|
|
||||||
|
|
||||||
def relative_to(root: Path, path: Path) -> Path:
|
def relative_to(root: Path, path: Path) -> Path:
|
||||||
return path.resolve().relative_to(root.resolve())
|
return path.resolve().relative_to(root.resolve())
|
||||||
|
|
||||||
|
|
||||||
def is_renpy_runtime_path(relative: Path) -> bool:
|
|
||||||
parts = relative.parts
|
|
||||||
return bool(parts) and (parts[0] == "renpy" or "renpy" in parts[:-1])
|
|
||||||
|
|
||||||
|
|
||||||
def copy_source_file(
|
|
||||||
source_file: Path,
|
|
||||||
input_root: Path,
|
|
||||||
source_dir: Path,
|
|
||||||
label: str,
|
|
||||||
destination_prefix: Path = Path(),
|
|
||||||
) -> None:
|
|
||||||
relative = relative_to(input_root, source_file)
|
|
||||||
output_relative = destination_prefix / relative
|
|
||||||
if is_renpy_runtime_path(relative):
|
|
||||||
LOGGER.info("skipped %s Ren'Py runtime source: %s", label, output_relative)
|
|
||||||
return
|
|
||||||
|
|
||||||
destination = source_dir / output_relative
|
|
||||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
shutil.copy2(source_file, destination)
|
|
||||||
LOGGER.info("copied %s source: %s", label, output_relative)
|
|
||||||
|
|
||||||
|
|
||||||
def copy_compiled_file(compiled_file: Path, input_root: Path, work_dir: Path) -> None:
|
|
||||||
relative = relative_to(input_root, compiled_file)
|
|
||||||
if is_renpy_runtime_path(relative):
|
|
||||||
LOGGER.info("skipped Ren'Py runtime compiled script: %s", relative)
|
|
||||||
return
|
|
||||||
|
|
||||||
destination = work_dir / "compiled" / relative
|
|
||||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
shutil.copy2(compiled_file, destination)
|
|
||||||
LOGGER.info("copied compiled script: %s", relative)
|
|
||||||
|
|
||||||
|
|
||||||
def archive_work_path(work_dir: Path, archive_relative: Path) -> Path:
|
|
||||||
return work_dir / "archives" / archive_relative.with_suffix("")
|
|
||||||
|
|
||||||
|
|
||||||
def archive_destination_prefix(archive_relative: Path) -> Path:
|
|
||||||
parent = archive_relative.parent
|
|
||||||
if str(parent) == ".":
|
|
||||||
return Path()
|
|
||||||
return parent
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_suffixes(suffixes: list[str] | None) -> list[str] | None:
|
def normalize_suffixes(suffixes: list[str] | None) -> list[str] | None:
|
||||||
if suffixes is None:
|
if suffixes is None:
|
||||||
return None
|
return None
|
||||||
@@ -142,15 +94,22 @@ def normalize_suffixes(suffixes: list[str] | None) -> list[str] | None:
|
|||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def copy_loose_file(file: Path, game_root: Path, extracted_dir: Path) -> None:
|
||||||
|
relative = relative_to(game_root, file)
|
||||||
|
destination = extracted_dir / relative
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(file, destination)
|
||||||
|
LOGGER.info("copied loose file: %s", relative)
|
||||||
|
|
||||||
|
|
||||||
def extract_archive(
|
def extract_archive(
|
||||||
archive: Path,
|
archive: Path,
|
||||||
game_root: Path,
|
game_root: Path,
|
||||||
source_dir: Path,
|
extracted_dir: Path,
|
||||||
work_dir: Path,
|
|
||||||
suffixes: list[str] | None,
|
suffixes: list[str] | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
archive_relative = relative_to(game_root, archive)
|
archive_relative = relative_to(game_root, archive)
|
||||||
output_dir = archive_work_path(work_dir, archive_relative)
|
output_dir = extracted_dir / archive_relative.with_suffix("")
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
LOGGER.info("extracting archive: %s", archive_relative)
|
LOGGER.info("extracting archive: %s", archive_relative)
|
||||||
@@ -158,16 +117,6 @@ def extract_archive(
|
|||||||
extract_rpa(handle, output_dir=str(output_dir), suffixes=suffixes)
|
extract_rpa(handle, output_dir=str(output_dir), suffixes=suffixes)
|
||||||
LOGGER.info("extracted archive: %s", archive_relative)
|
LOGGER.info("extracted archive: %s", archive_relative)
|
||||||
|
|
||||||
destination_prefix = archive_destination_prefix(archive_relative)
|
|
||||||
for source_file in sorted(output_dir.rglob("*.rpy")):
|
|
||||||
copy_source_file(
|
|
||||||
source_file,
|
|
||||||
output_dir,
|
|
||||||
source_dir,
|
|
||||||
"archived",
|
|
||||||
destination_prefix,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_logging(logs_dir: Path) -> None:
|
def configure_logging(logs_dir: Path) -> None:
|
||||||
log_file = logs_dir / "clew.log"
|
log_file = logs_dir / "clew.log"
|
||||||
@@ -190,21 +139,17 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
fail(f"game root is not a directory: {game_root}")
|
fail(f"game root is not a directory: {game_root}")
|
||||||
|
|
||||||
suffixes = normalize_suffixes(args.suffix)
|
suffixes = normalize_suffixes(args.suffix)
|
||||||
source_dir, work_dir, logs_dir = prepare_output(output_root, args.force)
|
extracted_dir, logs_dir = prepare_output(output_root, args.force)
|
||||||
configure_logging(logs_dir)
|
configure_logging(logs_dir)
|
||||||
|
|
||||||
LOGGER.info("scanning loose source files")
|
LOGGER.info("copying loose files")
|
||||||
for source_file in sorted(game_root.rglob("*.rpy")):
|
for file in sorted(game_root.rglob("*")):
|
||||||
copy_source_file(source_file, game_root, source_dir, "loose")
|
if file.is_file() and file.suffix.lower() != ".rpa":
|
||||||
|
copy_loose_file(file, game_root, extracted_dir)
|
||||||
|
|
||||||
LOGGER.info("scanning loose compiled scripts")
|
LOGGER.info("extracting archives")
|
||||||
for suffix in ("*.rpyc", "*.rpymc"):
|
|
||||||
for compiled_file in sorted(game_root.rglob(suffix)):
|
|
||||||
copy_compiled_file(compiled_file, game_root, work_dir)
|
|
||||||
|
|
||||||
LOGGER.info("scanning archives")
|
|
||||||
for archive in sorted(game_root.rglob("*.rpa")):
|
for archive in sorted(game_root.rglob("*.rpa")):
|
||||||
extract_archive(archive, game_root, source_dir, work_dir, suffixes)
|
extract_archive(archive, game_root, extracted_dir, suffixes)
|
||||||
|
|
||||||
LOGGER.info("done")
|
LOGGER.info("done")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user