desophos/idleon-saver

View on GitHub
idleon_saver/utility.py

Summary

Maintainability
A
0 mins
Test Coverage
import logging
import os
import sys
import time
from argparse import Action, ArgumentParser, Namespace
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Iterable, List

ROOT_DIR = Path(__file__).resolve().parent.parent
BUGREPORT_LINK = "https://github.com/desophos/idleon-saver/issues/new?assignees=desophos&labels=bug&template=bug_report.md&title="


class Sources(Enum):
    LOCAL = "local"
    FIREBASE = "firebase"


class Formats(Enum):
    IC = "idleon_companion"
    COG = "cogstruction"


class Args(Enum):
    IDLEON = "idleon"
    LDB = "ldb"
    WORKDIR = "workdir"
    INFILE = "infile"
    OUTFILE = "outfile"
    SOURCE = "source"
    TO = "to"


class IdleonAction(Action):
    def __call__(self, parser, namespace, value, option_string=None):
        # In case someone passes the exe path instead of the install dir.
        if value.name == "LegendsOfIdleon.exe":
            value = value.parent
        setattr(namespace, self.dest, value)


class LdbAction(Action):
    def __call__(self, parser, namespace, value, option_string=None):
        # Only check ldb path.
        # Idleon path is only used for the db key, so it doesn't have to exist.
        # (Allows running from VMs.)
        if not (value.exists() and value.is_dir()):
            raise IOError(f"Invalid leveldb path: {value}")
        setattr(namespace, self.dest, value)


class WorkdirAction(Action):
    def __call__(self, parser, namespace, value, option_string=None):
        value.mkdir(exist_ok=True)
        setattr(namespace, self.dest, value)


class SourceAction(Action):
    def __call__(self, parser, namespace, value, option_string=None):
        setattr(namespace, self.dest, Sources(value))


class ToAction(Action):
    def __call__(self, parser, namespace, value, option_string=None):
        setattr(namespace, self.dest, Formats(value))


arg_adders: dict[Args, Callable[[ArgumentParser], Any]] = {
    Args.IDLEON: lambda parser: parser.add_argument(
        "-n",
        "--idleon",
        type=Path,
        default="C:/Program Files (x86)/Steam/steamapps/common/Legends of Idleon",
        help="your Legends of Idleon install path",
        action=IdleonAction,
    ),
    Args.LDB: lambda parser: parser.add_argument(
        "-l",
        "--ldb",
        type=resolved_path,
        default="~/dev/leveldb",
        help="path to the leveldb to work with",
        action=LdbAction,
    ),
    Args.WORKDIR: lambda parser: parser.add_argument(
        "-w",
        "--workdir",
        type=resolved_path,
        default=ROOT_DIR / "work",
        help="path to the working directory where files will be created",
        action=WorkdirAction,
    ),
    Args.INFILE: lambda parser: parser.add_argument(
        "-i",
        "--infile",
        default="",
        help="name of the input file; default varies by script",
    ),
    Args.OUTFILE: lambda parser: parser.add_argument(
        "-o",
        "--outfile",
        default="",
        help="name of the output file; default varies by script",
    ),
    Args.SOURCE: lambda parser: parser.add_argument(
        "-s",
        "--source",
        choices=[member.value for member in Sources],
        default=Sources.FIREBASE.value,
        help="source of save data",
        action=SourceAction,
    ),
    Args.TO: lambda parser: parser.add_argument(
        "-t",
        "--to",
        choices=[member.value for member in Formats],
        default=Formats.IC.value,
        help="format to parse save data into",
        action=ToAction,
    ),
}


def get_args(*to_add: Args) -> Namespace:
    # Redirect logs to stdout for CLI scripts.
    root = logging.getLogger()
    root.setLevel(logging.INFO)
    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(logging.INFO)
    root.addHandler(handler)

    parser = ArgumentParser()
    for arg in to_add:
        arg_adders[arg](parser)
    return parser.parse_args()


def friendly_name(s: str) -> str:
    return s.replace("_", " ").title()


def user_dir() -> Path:
    path = Path(os.environ["APPDATA"], "IdleonSaver")
    path.mkdir(exist_ok=True)
    return path


def logs_dir() -> Path:
    path = user_dir() / "logs"
    path.mkdir(exist_ok=True)
    return path


def zip_from_iterable(iterables):
    return zip(*iterables)


def dict_sorted(d: dict) -> dict:
    return dict(sorted(d.items()))


def from_keys_in(d: dict, keys: Iterable, value=None) -> dict:
    return {d[key]: value for key in keys if key in d}


def chunk(s: str, chunk_size: int) -> List[str]:
    return [s[i : i + chunk_size] for i in range(0, len(s), chunk_size)]


def resolved_path(s: str) -> Path:
    return Path(s).expanduser().resolve()


def wait_for(check: Callable[[], Any], timeout: float = 1.0) -> bool:
    start = time.time()
    while time.time() - start < timeout:
        if check():
            return True
        time.sleep(0.1)
    return False