#!/usr/bin/env python3 """Resolve "which deck am I operating on" the same way in every tool. Every tool used to assume it was run with cwd == that one deck's directory. Now every tool accepts an explicit deck path, so the repo can hold many decks: python3 tools/bracket_check.py --deck decks/reaper-king-no-more MTG_DECK=decks/reaper-king-no-more python3 tools/bracket_check.py Resolution order for the deck directory: 1. --deck/-d PATH on the command line (removed from argv before the caller does its own parsing) 2. $MTG_DECK environment variable 3. cwd, if it looks like a deck dir (has config.yaml) -- backwards compat with the old "cd into the deck dir and run tools/x.py" workflow 4. error from deckctx import DeckContext ctx = DeckContext.from_args() # pops --deck out of sys.argv ctx.path("decklist.mtg") # -> decks//decklist.mtg ctx.commander # -> "The Reaper, King No More" ctx.identity # -> frozenset("BRG"), from the DB ctx.slug # -> "reaper-king-no-more" """ import os import sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def _looks_like_deck_dir(d): return os.path.exists(os.path.join(d, "config.yaml")) or \ os.path.exists(os.path.join(d, "decklist.mtg")) def resolve_deck_dir(argv=None, pop=True): """Find the deck directory, optionally stripping --deck/-d out of argv.""" argv = sys.argv if argv is None else argv for flag in ("--deck", "-d"): if flag in argv: i = argv.index(flag) path = argv[i + 1] if pop: del argv[i:i + 2] return os.path.abspath(path) env = os.environ.get("MTG_DECK") if env: return os.path.abspath(env) if _looks_like_deck_dir("."): return os.path.abspath(".") sys.exit("no deck specified -- pass --deck decks/, set $MTG_DECK, " "or run from inside a deck directory") class DeckContext: """Everything a tool needs to know about the deck it's operating on.""" def __init__(self, deck_dir): self.dir = os.path.abspath(deck_dir) if not os.path.isdir(self.dir): sys.exit(f"no such deck directory: {self.dir}") self.config = self._load_config() self._commander = self.config.get("commander") @classmethod def from_args(cls, argv=None, pop=True): return cls(resolve_deck_dir(argv, pop)) def _load_config(self): cfg_path = os.path.join(self.dir, "config.yaml") if not os.path.exists(cfg_path): return {} try: import yaml except ImportError: print("warning: pyyaml missing, config.yaml ignored", file=sys.stderr) return {} return yaml.safe_load(open(cfg_path)) or {} def path(self, *parts): """A path inside this deck's directory.""" return os.path.join(self.dir, *parts) @property def slug(self): """Directory basename, used as a filesystem-safe deck identifier.""" return os.path.basename(self.dir.rstrip("/")) @property def name(self): """Human-readable deck name. Falls back to the commander, then the slug.""" return self.config.get("name") or self._commander or self.slug @property def commander(self): if not self._commander: sys.exit(f"{self.path('config.yaml')}: no 'commander:' set") return self._commander @property def decklist_path(self): return self.config.get("decklist") and self.path(self.config["decklist"]) \ or self.path("decklist.mtg") @property def blacklist_path(self): return self.path(self.config.get("blacklist_file", "blacklist.txt")) @property def protected(self): return set(self.config.get("protected", [])) def identity(self, db=None): """Color identity, derived from the commander's own card data. Never hardcode a color combination -- look up the commander in the oracle database and use its color_identity, full stop. """ if db is None: from cards import DB db = DB() card = db.get(self.commander) if card is None: sys.exit(f"commander '{self.commander}' not found in card database " f"-- check config.yaml / run tools/fetch_data.py") return card.color_identity def dck_filename(self): """Forge .dck filename derived from this deck's slug.""" return f"{self.slug}.dck" def forge_metadata_name(self): """[metadata] Name= value for the .dck file.""" return self.name