From 057ac98843cc1eff7069f6f2cebd1bb734bfc2c3 Mon Sep 17 00:00:00 2001 From: yrriban Date: Sat, 17 May 2025 11:50:59 -0400 Subject: [PATCH] Read values out of a config file instead of hardcoding them. Further streamline common tasks in a new base config class. --- dcc/config.py | 68 ++++++++++++++++++++++++++++++------------------ dcc/doom_base.py | 43 +++++++++++++++--------------- dcc/dsda.py | 4 +-- dcc/eureka.py | 6 ++--- dcc/extract.py | 7 +++-- dcc/fabricate.py | 2 +- dcc/fetch.py | 5 ++-- dcc/ls.py | 11 ++++---- dcc/play.py | 2 +- dcc/record.py | 2 +- dcc/thumb.py | 2 +- 11 files changed, 83 insertions(+), 69 deletions(-) diff --git a/dcc/config.py b/dcc/config.py index 1205d67..5b805c1 100644 --- a/dcc/config.py +++ b/dcc/config.py @@ -2,17 +2,9 @@ import io import pathlib import os import re +import tomlkit -DOOM=pathlib.Path.home().joinpath("doom") -DSDA_DIR=DOOM.joinpath("dsda-doom") -DSDA_VER='0.28.3' -DSDA=DSDA_DIR.joinpath('dsda-doom-{}-Linux.appimage'.format(DSDA_VER)) -IWADS=DOOM.joinpath("iwads") -PWADS=DOOM.joinpath("pwads") -DEMOS=DOOM.joinpath("demos") -OUTPUT=DOOM.joinpath("fabricate") - -DEFAULT_IWAD=IWADS.joinpath("DOOM2.WAD") +from cliff.command import Command THUMB_WIDTH=1280 THUMB_HEIGHT=720 @@ -22,21 +14,47 @@ TEXT_STROKE_COLOR="srgb(176,0,0)" MIRROR="https://youfailit.net/pub/idgames" # NYC -def Complevel(wad): - complevel = PwadPath(wad).joinpath("complevel") - if not complevel.exists(): - raise Exception("No complevel set in PWAD dir {}.".format(pwadpath)) - with io.open(complevel) as f: - return f.read().strip() +class Base(Command): + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument('--doom', default=pathlib.Path.home().joinpath("doom")) + parser.add_argument('--config-name', default='config.toml') + return parser -def IwadPath(wad): - iwad = DEFAULT_IWAD - iwadpath = PwadPath(wad).joinpath("iwad") - if iwadpath.exists(): - with io.open(iwadpath) as f: - iwad = IWADS.joinpath(f.read().strip() + ".WAD") - return iwad + def run(self, parsed_args): + self._doom = pathlib.Path(parsed_args.doom) + self._config_name = parsed_args.config_name + self._config = tomlkit.toml_file.TOMLFile(self.doom.joinpath(self.config_name)).read() + self._dsda = self._config.get("dsda") + if self.dsda is None: + raise Exception(f"required key 'dsda' not set in config {self.doom.joinpath(self.config_name)}.") + for d in ("iwads", "pwads", "demos", "fabricate"): + self._init_path(d) + + self.take_action(parsed_args) + + def _init_path(self, what): + setattr(self, f"_{what}", self.doom.joinpath(self._config.get(what, what))) + setattr(type(self), what, property(lambda self: getattr(self, f"_{what}"))) + + @property + def doom(self): + return self._doom + + @property + def config_name(self): + return self._config_name + + @property + def dsda(self): + return self._doom.joinpath(self._dsda) + + def iwad_path(self, wad): + iwad = self.iwads.joinpath(self._config.get("default_iwad")) + iwadpath = self.pwads.joinpath("iwad") + if iwadpath.exists(): + with io.open(iwadpath) as f: + iwad = self.iwads.joinpath(f.read().strip() + ".WAD") + return iwad -def PwadPath(wad): - return PWADS.joinpath(wad) diff --git a/dcc/doom_base.py b/dcc/doom_base.py index a1ed843..753392c 100644 --- a/dcc/doom_base.py +++ b/dcc/doom_base.py @@ -2,6 +2,7 @@ from abc import abstractmethod from cliff.command import Command import dcc.config import io +import os class WadMap(dcc.config.Base): def get_parser(self, prog_name): @@ -15,11 +16,7 @@ class WadMap(dcc.config.Base): self._wad = parsed_args.wad self._map = parsed_args.map self._name = parsed_args.name - self.take_action(parsed_args) - - @abstractmethod - def take_action(self, parsed_args): - pass + super().run(parsed_args) @property def wad(self): @@ -34,9 +31,9 @@ class WadMap(dcc.config.Base): return "" if self._name is None else "_" + self._name def dsda_preamble(self): - args = ["-iwad", dcc.config.IwadPath(self.wad)] + args = ["-iwad", self.iwad_path(self.wad)] - pwadpath = dcc.config.PWADS.joinpath(self.wad) + pwadpath = self.pwads.joinpath(self.wad) wads = sorted(pwadpath.glob('*.wad', case_sensitive=False)) if len(wads) > 0: args = args + ["-file"] + wads @@ -45,19 +42,21 @@ class WadMap(dcc.config.Base): if len(dehs) > 0: args = args + ["-deh"] + dehs - complevel = pwadpath.joinpath("complevel") - if not complevel.exists(): - raise Exception("No complevel set in PWAD dir {}.".format(pwadpath)) - - with io.open(complevel) as f: - args = args + ["-complevel", dcc.config.Complevel(self.wad)] - + args = args + ["-complevel", self.complevel()] args = args + ["-skill", "4"] args = args + ["-warp", self.map] return args + def complevel(self): + complevel = self.pwads.joinpath(self.wad).joinpath("complevel") + if not complevel.exists(): + raise Exception("No complevel set in PWAD dir {}.".format(pwadpath)) + + with io.open(complevel) as f: + return f.read().strip() + def demo_in_path(self): - candidates = [x for x in dcc.config.DEMOS.joinpath(self.wad).glob(self._file_base("*.lmp"))] + candidates = [x for x in self.demos.joinpath(self.wad).glob(self._file_base("*.lmp"))] if len(candidates) == 0: raise Exception("no suitable demo candidates for WAD {} MAP {} name {}.".format(self.wad, self.map, self.name_string)) if len(candidates) == 1: @@ -65,28 +64,28 @@ class WadMap(dcc.config.Base): return sorted(filter(lambda s : re.search("-", str(s)), candidates))[-1] def dsda_text_path(self): - return self._ensure(dcc.config.DEMOS.joinpath(self.wad)).joinpath(self._file_base(".txt")) + return self._ensure(self.demos.joinpath(self.wad)).joinpath(self._file_base(".txt")) def video_path(self): - return self._ensure(dcc.config.OUTPUT.joinpath(self.wad)).joinpath(self._file_base(".mp4")) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath(self._file_base(".mp4")) def demo_out_path(self): - return self._ensure(dcc.config.DEMOS.joinpath(self.wad)).joinpath(self._file_base(".lmp")) + return self._ensure(self.demos.joinpath(self.wad)).joinpath(self._file_base(".lmp")) def target_bucket(self): return "doom/" + self._file_base(".lmp") def base_thumb_path(self): - return self._ensure(dcc.config.OUTPUT.joinpath(self.wad)).joinpath(_file_base("_base.png")) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath(self._file_base("_base.png")) def m_doom_path(self): - return self._ensure(OUTPUT.joinpath(self.wad)).joinpath("M_DOOM_scaled.png") + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath("M_DOOM_scaled.png") def text_thumb_path(self): - return self._ensure(dcc.config.OUTPUT.joinpath(self.wad)).joinpath(_file_base("_text.png")) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath(self._file_base("_text.png")) def thumb_path(self): - return self._ensure(dcc.config.OUTPUT.joinpath(self.wad)).joinpath(_file_base("_thumb.png")) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath(self._file_base("_thumb.png")) def _file_base(self, ext): return "{}_map{}{}{}".format(self.wad, self.map, self.name_string, ext) diff --git a/dcc/dsda.py b/dcc/dsda.py index 8f71f70..2672fb0 100644 --- a/dcc/dsda.py +++ b/dcc/dsda.py @@ -17,7 +17,7 @@ class DSDA(dcc.doom_base.WadMap): dip = self.demo_in_path() dtp = self.dsda_text_path() if not dtp.exists(): - command = [dcc.config.DSDA] + command = [self.dsda] if shutil.which("xvfb-run") is not None: command = ["xvfb-run"] + command # TODO: negative tics should seek from the end, but this doesn't seem to work. @@ -51,6 +51,6 @@ class DSDA(dcc.doom_base.WadMap): # TODO: demo names other than uv-max. fnf = fh1 + "-" + fh2 + ".zip" - with zipfile.ZipFile(dcc.config.DEMOS.joinpath(self.wad).joinpath(fnf), mode="w") as zf: + with zipfile.ZipFile(self.demos.joinpath(self.wad).joinpath(fnf), mode="w") as zf: zf.write(dip, arcname=dip.name) zf.write(dtp, arcname=dtp.name) diff --git a/dcc/eureka.py b/dcc/eureka.py index 1b5a6d8..4c86af0 100644 --- a/dcc/eureka.py +++ b/dcc/eureka.py @@ -4,12 +4,12 @@ import subprocess class Eureka(dcc.doom_base.WadMap): def take_action(self, parsed_args): - iwad = dcc.config.IwadPath(parsed_args.wad) - pwadpath = dcc.config.PwadPath(parsed_args.wad) + iwad = self.iwad_path(parsed_args.wad) + pwadpath = self.pwads.joinpath(parsed_args.wad) mw = list(pwadpath.glob('*{}*.wad'.format(parsed_args.wad), case_sensitive=False)) if len(mw) != 1: raise Exception("Unable to guess at main pwad for wad {}.".format(parsed_args.wad)) - complevel = dcc.config.Complevel(parsed_args.wad) + complevel = self.complevel() port = "vanilla" if complevel == "9": port = "boom" diff --git a/dcc/extract.py b/dcc/extract.py index a3ad366..43f49ae 100644 --- a/dcc/extract.py +++ b/dcc/extract.py @@ -1,11 +1,10 @@ -from cliff.command import Command import dcc.config import omg import numpy as np import wand.color import wand.image -class Extract(Command): +class Extract(dcc.config.base): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument('wad') @@ -13,7 +12,7 @@ class Extract(Command): return parser def take_action(self, parsed_args): - wads = sorted(dcc.config.PWADS.joinpath(parsed_args.wad).glob('*.wad', case_sensitive=False), reverse=True) + wads = sorted(self.pwads.joinpath(self.wad).glob('*.wad', case_sensitive=False), reverse=True) for w in wads: try: @@ -23,7 +22,7 @@ class Extract(Command): # With no arguments, convert() changes a paletted image to an RGB one. with wand.image.Image.from_array(np.array(gl.to_Image().convert())) as img: img.transparent_color(wand.color.Color("#ff00ff"), 0.0) - img.save(filename=dcc.config.OUTPUT.joinpath(parsed_args.wad).joinpath(parsed_args.lump + ".png")) + img.save(filename=self.output.joinpath(parsed_args.wad).joinpath(parsed_args.lump + ".png")) return except Exception as e: print("Wad {} likely has no lump {} (exception {}).".format(w, parsed_args.lump, e)) diff --git a/dcc/fabricate.py b/dcc/fabricate.py index 229f50e..73d078c 100644 --- a/dcc/fabricate.py +++ b/dcc/fabricate.py @@ -14,7 +14,7 @@ class Fabricate(dcc.doom_base.WadMap): def take_action(self, parsed_args): with tempfile.TemporaryDirectory() as td: with contextlib.chdir(td): - command = [dcc.config.DSDA] + command = [self.dsda] if not parsed_args.fg and shutil.which("xvfb-run") is not None: command = ["xvfb-run"] + command subprocess.run(command + self.dsda_preamble() + diff --git a/dcc/fetch.py b/dcc/fetch.py index a0238b0..d58dc1a 100644 --- a/dcc/fetch.py +++ b/dcc/fetch.py @@ -1,4 +1,3 @@ -from cliff.command import Command import dcc.config import io import json @@ -6,7 +5,7 @@ import pathlib import urllib.request import zipfile -class Fetch(Command): +class Fetch(dcc.config.Base): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("id_or_name") @@ -24,7 +23,7 @@ class Fetch(Command): with urllib.request.urlopen(rpath) as response: z = zipfile.ZipFile(io.BytesIO(response.read())) - z.extractall(path=dcc.config.PwadPath(wad)) + z.extractall(path=self.pwads.joinpath(wad)) # TODO: explicit error handling. Let users choose when >1 result. def search_idgames(self, wad): diff --git a/dcc/ls.py b/dcc/ls.py index dad86ce..7d78610 100644 --- a/dcc/ls.py +++ b/dcc/ls.py @@ -1,8 +1,7 @@ -import cliff.command import dcc.config import os -class List(cliff.command.Command): +class List(dcc.config.Base): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("target") @@ -12,13 +11,13 @@ class List(cliff.command.Command): def take_action(self, parsed_args): match parsed_args.target: case "pwads": - self.list(x.name for x in os.scandir(dcc.config.PWADS) if x.is_dir()) + self.list(x.name for x in os.scandir(self.pwads) if x.is_dir()) case "iwads": - self.list(x.name for x in os.scandir(dcc.config.IWADS) if x.is_file()) + self.list(x.name for x in os.scandir(self.iwads) if x.is_file()) case "demos": - self.list(x.name for x in os.scandir(dcc.config.DEMOS.joinpath(parsed_args.wad)) if x.name.endswith(".lmp")) + self.list(x.name for x in os.scandir(self.demos.joinpath(parsed_args.wad)) if x.name.endswith(".lmp")) case "videos": - self.list(x.name for x in os.scandir(dcc.config.OUTPUT.joinpath(parsed_args.wad)) if x.name.endswith(".mp4")) + self.list(x.name for x in os.scandir(self.output.joinpath(parsed_args.wad)) if x.name.endswith(".mp4")) def list(self, gen): # TODO: fancy text? diff --git a/dcc/play.py b/dcc/play.py index 1a49caa..809862a 100644 --- a/dcc/play.py +++ b/dcc/play.py @@ -4,4 +4,4 @@ import subprocess class Play(dcc.doom_base.WadMap): def take_action(self, parsed_args): - subprocess.run([dcc.config.DSDA] + self.dsda_preamble()) + subprocess.run([self.dsda] + self.dsda_preamble()) diff --git a/dcc/record.py b/dcc/record.py index 2c6aee2..ce8329e 100644 --- a/dcc/record.py +++ b/dcc/record.py @@ -4,5 +4,5 @@ import subprocess class Record(dcc.doom_base.WadMap): def take_action(self, parsed_args): - subprocess.run([dcc.config.DSDA] + self.dsda_preamble() + + subprocess.run([self.dsda] + self.dsda_preamble() + ["-record", self.demo_out_path()]) diff --git a/dcc/thumb.py b/dcc/thumb.py index b0e1bcb..9ff3b07 100644 --- a/dcc/thumb.py +++ b/dcc/thumb.py @@ -23,7 +23,7 @@ class Thumb(dcc.doom_base.WadMap): bi.composite(mdi, gravity="north_west") if parsed_args.index: - with wand.image.Image(filename=dcc.config.OUTPUT.joinpath("doomed_index.png")) as di: + with wand.image.Image(filename=self.output.joinpath("doomed_index.png")) as di: di.border(tc, 1, 1) bi.composite(di, gravity="north_east")