import os.path import pathlib import zipfile import os import sys import logging import logging.config logging.config.dictConfig({ 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s' } }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'level': 'DEBUG', 'formatter': 'standard', 'stream': 'ext://sys.stderr', }, }, 'loggers': { '': { # root logger 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True }, } }) logger = logging.getLogger() def extract(zip_path: str, dest: str, skip_checks: bool = False): """ Did you know that `.r2z` is just a badly compressed zip? Utilized to automate version controlling configs and plugin manifests NB: Do not use this to install profiles. Most mod manager has an install step after extracting. `dest` needs to be profiled path, that is, should be something like `//profiles/my-profile`, not just `//profiles` with `zip_path` `//exports/my-profile` """ p = pathlib.Path(zip_path) to_dir = pathlib.Path(dest) if to_dir.is_file(): raise KeyError("Expected dest to be directory or be recursively made") if not p.suffix.lower() == ".r2z" and not skip_checks: raise KeyError("Expected r2z suffix, use `skip_checks` to bypass") profile_dir = to_dir os.makedirs(profile_dir, exist_ok=True) with zipfile.ZipFile(p, 'r') as zip_ref: logging.info(f"Extracting {p} to {profile_dir}") zip_ref.extractall(profile_dir) def repo_root_impure(): cwd = pathlib.Path.cwd() return cwd.parent if cwd.name == "script" else cwd def main(): dry_run = os.getenv("DRY_RUN", "false").lower() not in ("false", "0", "no", "nope") if dry_run: logger.debug("Doing dry run") ror2_f = "RiskOfRain2" MODMAN_PATH_ENV = "ROR2_MODMAN_PATH" MODMAN_CANDIDATES = [ pathlib.Path.home().joinpath("AppData","Roaming", *modman_segs, ror2_f) for modman_segs in (("r2modmanPlus-local",), ("Thunderstore Mod Manager", "DataFolder")) ] mod_man_roots = [pathlib.Path(f) for f in os.getenv(MODMAN_PATH_ENV, "").split(',') if f] or MODMAN_CANDIDATES mod_man_roots = [e for e in mod_man_roots if e.is_dir()] logging.debug(f"Using {mod_man_roots=}") dest_base_loc = pathlib.Path(os.getenv("DEST_BASE_LOC") or repo_root_impure().joinpath("r2_profiles")) if dest_base_loc.is_file(): raise KeyError("{dest_base_loc=} is a file, expected directory or able to recursively make dirs") if not dry_run: os.makedirs(dest_base_loc, exist_ok=True) logging.debug(f"Using {dest_base_loc=}") if mod_man_roots == []: raise KeyError(f'No mod manager found locally, set "{MODMAN_PATH_ENV}" env or check {MODMAN_CANDIDATES}') def profile_of(r2z_fname: pathlib.Path): stem = r2z_fname.stem v = stem.split("_") if len(v) == 1: return stem *head, tail = v if tail.isnumeric(): return "_".join(head) return stem # latest profiles (based on OS's reported mtime, disregard r2modman's stem) raw_profiles = [ file for file in sorted( # flatten from both roots [f for root in mod_man_roots for f in root.joinpath("exports").iterdir()], key=lambda e: e.stat().st_mtime, # dict comprehension chooses latter entry if duplicate key reverse=False, ) if file.is_file() and file.suffix.lower() == ".r2z" ] profiles = { profile_of(file): file for file in raw_profiles } logger.debug(f"{profiles=}") if not dry_run: for prof, r2z in profiles.items(): extract(r2z, dest_base_loc.joinpath(prof)) logger.info(f"done extracting {len(profiles)} profiles") if __name__ == "__main__": main()