Compare commits

..

No commits in common. "d08e0bebcc11032450a7f10e39924cec1dc8fce0" and "f44712e53c1689f807c78964c823c9d8268d06de" have entirely different histories.

3 changed files with 39 additions and 39 deletions

View file

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

View file

@ -98,7 +98,7 @@ class CLI:
args.func(args) args.func(args)
def install(self, args) -> None: def install(self, args) -> None:
already_installed_urls = {addon.url for addon in self.installed_addons} already_installed_urls = {a.url for a in self.installed_addons}
for url in set(args.urls): # set removes duplicates for url in set(args.urls): # set removes duplicates
if url in already_installed_urls: if url in already_installed_urls:
print(f"{url} is already installed") print(f"{url} is already installed")
@ -111,8 +111,13 @@ class CLI:
addons.save_installed_addons(self.installed_addons) addons.save_installed_addons(self.installed_addons)
def remove(self, args) -> None: def remove(self, args) -> None:
for addon in self.installed_addons: if args.all:
if args.all or addon.name in args.addons: remove = self.installed_addons.copy()
else:
remove = [addon
for addon in self.installed_addons
if addon.name in args.addons]
for addon in remove:
print(f"Removing {addon.name}") print(f"Removing {addon.name}")
addon.uninstall() addon.uninstall()
shutil.rmtree(addon.download_dir, ignore_errors=True) shutil.rmtree(addon.download_dir, ignore_errors=True)
@ -131,12 +136,12 @@ class CLI:
def list(self, args) -> None: def list(self, args) -> None:
table = [] table = []
for addon in sorted(self.installed_addons, key=lambda a: a.name.lower()): for addon in self.installed_addons:
table.append({ table.append({
"Name": addon.name, "Name": addon.name,
"Provider": addon.get_provider().__name__, "Provider": addon.get_provider().__name__,
"URL": addon.url, "URL": addon.url,
"Directories": textwrap.fill(", ".join(addon.dirs.keys()), width=40) "Directories": textwrap.fill(", ".join(d.name for d in addon.dirs), width=40)
}) })
from tabulate import tabulate from tabulate import tabulate
print(tabulate(table, headers="keys", tablefmt="orgtbl")) print(tabulate(table, headers="keys", tablefmt="orgtbl"))

View file

@ -28,7 +28,7 @@ class GitHub(Web):
latest_release = http.open( latest_release = http.open(
url=f"{cls.api_url}/repos/{repo_owner}/{repo_name}/releases/latest" url=f"{cls.api_url}/repos/{repo_owner}/{repo_name}/releases/latest"
).json() ).json()
for asset in sorted(latest_release["assets"], key=lambda a: "classic" in a["name"].lower(), reverse=True): for asset in latest_release["assets"]:
if Path(asset["name"]).suffix == ".zip": if Path(asset["name"]).suffix == ".zip":
return super().download(addon, asset["browser_download_url"]) return super().download(addon, asset["browser_download_url"])
raise FileNotFoundError("No zip file found for latest release") raise FileNotFoundError("No zip file found for latest release")