diff --git a/wau/addons.py b/wau/addons.py index 46947da..bd0b9dd 100644 --- a/wau/addons.py +++ b/wau/addons.py @@ -6,7 +6,7 @@ import re import shutil from dataclasses import dataclass, field from pathlib import Path -from typing import Iterable, List +from typing import Iterable, List, Dict from . import config, providers @@ -17,7 +17,7 @@ logger = logging.getLogger(__name__) class Addon: name: str = None url: str = None - dirs: List[Path] = field(default_factory=list) + dirs: Dict[str, Path] = field(default_factory=dict) provider: str = None provider_data: dict = field(default_factory=dict) @@ -26,7 +26,7 @@ class Addon: return { "name": self.name, "url": self.url, - "dirs": [str(d) for d in self.dirs], + "dirs": {name: str(path) for name, path in self.dirs.items()}, "provider": self.provider, "provider_data": self.provider_data } @@ -36,7 +36,7 @@ class Addon: return cls( name=data["name"], url=data["url"], - dirs=[Path(d) for d in data["dirs"]], + dirs={name: Path(path) for name, path in data["dirs"].items()}, provider=data["provider"], provider_data=data["provider_data"] ) @@ -51,7 +51,7 @@ class Addon: self.download_dir.mkdir(parents=True, exist_ok=True) def is_cached(self) -> bool: - return self.dirs and all(d.is_dir() for d in self.dirs) + return self.dirs and all(d.is_dir() for d in self.dirs.values()) def get_provider(self) -> providers.Provider: if self.provider is not None: @@ -74,36 +74,41 @@ class Addon: def install(self) -> None: logger.info("Installing %s", self) self.dirs = find_addon_dirs(self.download_dir) - for dir in self.dirs: - shutil.copytree(str(dir), config.ADDONS_DIR.joinpath(dir.name)) + for dir_name, path in self.dirs.items(): + shutil.copytree(str(path), config.ADDONS_DIR.joinpath(dir_name)) if self.name is None: # didn't get AddOn name from provider if len(self.dirs) == 1: - self.name = self.dirs[0].name + self.name = next(iter(self.dirs.keys())) else: print(f"Unable to identify AddOn name for {self.url}") self.name = input("AddOn name: ") def uninstall(self) -> None: logger.info("Uninstalling %s", self) - for dir in self.dirs: - shutil.rmtree(config.ADDONS_DIR.joinpath(dir.name), ignore_errors=True) + for dir_name in self.dirs.keys(): + shutil.rmtree(config.ADDONS_DIR.joinpath(dir_name), ignore_errors=True) -def find_addon_dirs(path: Path) -> List[Path]: +def find_addon_dirs(path: Path) -> Dict[str, Path]: """ - Find and return AddOns in the given path. - - Each WoW AddOn is identified by a .toc-file whose filename matches its containing folder. The algorithm traverses - the path in a breadth-first fashion, returning all found AddOns in the first level which contains any AddOns. - This ensures we find AddOns 'Foo' and 'Foo_Config'. + WoW AddOns are identified by a .toc file whose filename matches its containing folder. The algorithm traverses the + given directory path in a breadth-first fashion, returning all AddOns in the first level which contains any such + directory/.toc-pairs. This ensures that we, as an example, find both AddOns 'Foo' and 'Foo_Config'. + Occasionally, the downloaded directory will contain no directory/.toc-pairs which conform to the Blizzard AddOn + standard; e.g. in the case of 'foo-master' from git with 'Foo.toc' in the root. Therefore, as a special case, if the + first level in the given path contains a single .toc file, this path will be returned along with its proper name, so + it can be renamed on install to Interface/AddOns. """ level = [path] while level: - addon_dirs = [] + addon_dirs = {} for p in level: - if any(toc.stem == p.name for toc in p.glob("*.toc")): - addon_dirs.append(p) + tocs = list(p.glob("*.toc")) + if any(toc.stem == p.name for toc in tocs): + addon_dirs[p.name] = p + elif len(level) == 1 and len(tocs) == 1: + addon_dirs[tocs[0].stem] = p if addon_dirs: logger.debug("Found AddOn dirs: %s in '%s'", addon_dirs, path) return addon_dirs @@ -126,7 +131,7 @@ def load_installed_addons() -> List[Addon]: logger.debug("Loading list of installed AddOns from %s", config.CONFIG_FILE) try: with config.CONFIG_FILE.open() as cf: - return [Addon.from_json(a) for a in json.load(cf)["installed_addons"]] + return [Addon.from_json(addon) for addon in json.load(cf)["installed_addons"]] except FileNotFoundError: return [] @@ -134,4 +139,4 @@ def load_installed_addons() -> List[Addon]: def save_installed_addons(addons: Iterable[Addon]) -> None: logger.debug("Saving list of installed AddOns to %s", config.CONFIG_FILE) with config.CONFIG_FILE.open("w") as cf: - json.dump(dict(installed_addons=[a.to_json for a in addons]), cf) + json.dump(dict(installed_addons=[addon.to_json for addon in addons]), cf) diff --git a/wau/cli.py b/wau/cli.py index 056b0cb..2f15e45 100644 --- a/wau/cli.py +++ b/wau/cli.py @@ -141,7 +141,7 @@ class CLI: "Name": addon.name, "Provider": addon.get_provider().__name__, "URL": addon.url, - "Directories": textwrap.fill(", ".join(d.name for d in addon.dirs), width=40) + "Directories": textwrap.fill(", ".join(addon.dirs.keys()), width=40) }) from tabulate import tabulate print(tabulate(table, headers="keys", tablefmt="orgtbl"))