diff --git a/bin/brew-file b/bin/brew-file index 8861198c..373f9e37 100755 --- a/bin/brew-file +++ b/bin/brew-file @@ -199,8 +199,12 @@ class BrewHelper: def __post_init__(self) -> None: self.log = logging.getLogger(__name__) + self.all_info: dict[str, dict[str, Any]] | None = None self.info: dict[str, dict[str, Any]] | None = None - self.all_formulae: dict[str, Any] | None = None + + self.formulae: list[str] | None = None + self.casks: list[str] | None = None + self.formula_aliases: dict[str, dict[str, str]] | None = None self.cask_aliases: dict[str, dict[str, str]] | None = None self.taps: dict[str, Any] | None = None @@ -289,11 +293,54 @@ class BrewHelper: self.opt[name] = lines[0] return self.opt[name] - def get_info(self) -> dict[str, Any]: - """Get info of installed brew package.""" - if self.info is None: + def get_formulae(self) -> list[str]: + if self.formulae is None: _, lines = self.proc( - cmd="brew info --json=v2 --installed", + cmd="brew formulae", + print_cmd=False, + print_out=False, + exit_on_err=True, + separate_err=True, + ) + # test formulae in a deep direcotyr was found for hashicorp/tap + self.formulae = [ + x for x in lines if len(x.split("/")[-1]) in (1, 3) + ] + return self.formulae + + def get_casks(self) -> list[str]: + if self.casks is None: + _, lines = self.proc( + cmd="brew casks", + print_cmd=False, + print_out=False, + exit_on_err=True, + separate_err=True, + ) + self.casks = lines + return self.casks + + def get_packages(self) -> list[str]: + formulae = self.get_formulae() + casks = self.get_casks() + return formulae + casks + + def get_all_info(self) -> dict[str, Any]: + """Get info of all available brew package.""" + if self.all_info is None: + ret, lines = self.proc( + cmd="brew info --json=v2 --eval-all", + print_cmd=False, + print_out=False, + exit_on_err=False, + separate_err=False, + ) + if ret != 0: + formulae = self.get_formulae() + casks = self.get_casks() + + _, lines = self.proc( + cmd=f"brew info --json=v2 --cask {' '.join(casks)}", print_cmd=False, print_out=False, exit_on_err=True, @@ -301,26 +348,63 @@ class BrewHelper: ) lines = lines[lines.index("{") :] data = json.loads("".join(lines)) - self.info = { - "formulae": {x["name"]: x for x in data["formulae"]}, + self.all_info = { + "formulae": {}, "casks": {x["token"]: x for x in data["casks"]}, } - return self.info + while ret != 0: + ret, lines = self.proc( + cmd=f"brew info --json=v2 --formula {' '.join(formulae)}", + print_cmd=False, + print_out=False, + exit_on_err=False, + separate_err=False, + ) + if ret == 0: + break + formulae_updated = 0 + for line in lines: + # Remove package from list if it has no URL + # https://github.com/hashicorp/homebrew-tap/issues/258 + if "formula requires at least a URL" in line: + formula = line.split()[1] + if formula in formulae: + formulae.remove(formula) + formulae_updated = 1 + if formula.split("/")[-1] in formulae: + formulae.remove(formula.split("/")[-1]) + formulae_updated = 1 + continue + if formulae_updated: + continue + msg = "Failed to get info of all packages.\n\n" + msg += "\n".join(lines) + raise RuntimeError(msg) - def get_all_formulae(self) -> dict[str, Any]: - """Get info of all formulae.""" - if self.all_formulae is None: + lines = lines[lines.index("{") :] + data = json.loads("".join(lines)) + self.all_info["formulae"] = { + x["name"]: x for x in data["formulae"] + } + return self.all_info + + def get_info(self) -> dict[str, Any]: + """Get info of installed brew package.""" + if self.info is None: _, lines = self.proc( - cmd="brew info --json=v1 --eval-all", + cmd="brew info --json=v2 --installed", print_cmd=False, print_out=False, exit_on_err=True, separate_err=True, ) - lines = lines[lines.index("[") :] + lines = lines[lines.index("{") :] data = json.loads("".join(lines)) - self.all_formulae = {x["name"]: x for x in data} - return self.all_formulae + self.info = { + "formulae": {x["name"]: x for x in data["formulae"]}, + "casks": {x["token"]: x for x in data["casks"]}, + } + return self.info def get_formula_list(self) -> list[str]: info = self.get_info() @@ -2869,30 +2953,25 @@ class BrewFile: # First, get App Store applications appstore_list = self.get_appstore_dict() - # Check installed casks untill brew info --eval-all contains core/cask for AMI - # once fixed, compare info['token']['version'] and installed to check latest - cask_list = self.helper.get_cask_list() + # Get all available formulae/casks information + all_info = self.helper.get_all_info() - # Get cask information - info = self.helper.get_info()["casks"] casks: dict[str, dict[str, str | bool]] = {} apps: dict[str, str] = {} installed_casks: dict[str, list[str]] = {self.opt["cask_repo"]: []} - for cask in info: - apps_in_cask = [] - installed = cask in cask_list + for cask, info in all_info["casks"].items(): + installed = False latest = False - if installed: - if info[cask]["installed"] is None: - latest = True - else: - latest = info[cask]["installed"] == info[cask]["version"] - installed_casks[info[cask]["tap"]] = installed_casks.get( - info[cask]["tap"], [] + apps_in_cask = [] + if "installed" in info and info["installed"]: + installed = True + latest = info["installed"] == info["version"] + installed_casks[info["tap"]] = installed_casks.get( + info["tap"], [] ) + [cask] - if "artifacts" in info[cask]: - for artifact in info[cask]["artifacts"]: + if "artifacts" in info: + for artifact in info["artifacts"]: if "app" in artifact: for a in artifact["app"]: if isinstance(a, str): @@ -2906,7 +2985,7 @@ class BrewFile: ): apps_in_cask.append(a) casks[cask] = { - "tap": info[cask]["tap"], + "tap": info["tap"], "installed": installed, "latest": latest, } @@ -2914,9 +2993,6 @@ class BrewFile: for a in apps_in_cask: if a not in apps or installed: apps[a] = cask - # brew - formulae = self.helper.get_formula_list() - formulae_all = self.helper.get_all_formulae() # Set applications directories app_dirs = self.opt["appdirlist"] @@ -2996,9 +3072,11 @@ class BrewFile: has_cask_apps[cask_tap] = has_cask_apps.get( cask_tap, [] ) + [(app_path, token)] - elif token in formulae_all: - brew_tap = cast(str, formulae_all[token]["tap"]) - if token in formulae: + elif token in all_info["formulae"]: + brew_tap = cast( + str, all_info["formulae"][token]["tap"] + ) + if all_info["formulae"][token]["installed"]: check = "brew" brew_apps[brew_tap] = brew_apps.get( brew_tap, [] diff --git a/src/brew_file/brew_file.py b/src/brew_file/brew_file.py index 92c34e4e..2f0415af 100644 --- a/src/brew_file/brew_file.py +++ b/src/brew_file/brew_file.py @@ -1854,30 +1854,25 @@ def check_cask(self) -> None: # First, get App Store applications appstore_list = self.get_appstore_dict() - # Check installed casks untill brew info --eval-all contains core/cask for AMI - # once fixed, compare info['token']['version'] and installed to check latest - cask_list = self.helper.get_cask_list() + # Get all available formulae/casks information + all_info = self.helper.get_all_info() - # Get cask information - info = self.helper.get_info()["casks"] casks: dict[str, dict[str, str | bool]] = {} apps: dict[str, str] = {} installed_casks: dict[str, list[str]] = {self.opt["cask_repo"]: []} - for cask in info: - apps_in_cask = [] - installed = cask in cask_list + for cask, info in all_info["casks"].items(): + installed = False latest = False - if installed: - if info[cask]["installed"] is None: - latest = True - else: - latest = info[cask]["installed"] == info[cask]["version"] - installed_casks[info[cask]["tap"]] = installed_casks.get( - info[cask]["tap"], [] + apps_in_cask = [] + if "installed" in info and info["installed"]: + installed = True + latest = info["installed"] == info["version"] + installed_casks[info["tap"]] = installed_casks.get( + info["tap"], [] ) + [cask] - if "artifacts" in info[cask]: - for artifact in info[cask]["artifacts"]: + if "artifacts" in info: + for artifact in info["artifacts"]: if "app" in artifact: for a in artifact["app"]: if isinstance(a, str): @@ -1891,7 +1886,7 @@ def check_cask(self) -> None: ): apps_in_cask.append(a) casks[cask] = { - "tap": info[cask]["tap"], + "tap": info["tap"], "installed": installed, "latest": latest, } @@ -1899,9 +1894,6 @@ def check_cask(self) -> None: for a in apps_in_cask: if a not in apps or installed: apps[a] = cask - # brew - formulae = self.helper.get_formula_list() - formulae_all = self.helper.get_all_formulae() # Set applications directories app_dirs = self.opt["appdirlist"] @@ -1981,9 +1973,11 @@ def check_cask(self) -> None: has_cask_apps[cask_tap] = has_cask_apps.get( cask_tap, [] ) + [(app_path, token)] - elif token in formulae_all: - brew_tap = cast(str, formulae_all[token]["tap"]) - if token in formulae: + elif token in all_info["formulae"]: + brew_tap = cast( + str, all_info["formulae"][token]["tap"] + ) + if all_info["formulae"][token]["installed"]: check = "brew" brew_apps[brew_tap] = brew_apps.get( brew_tap, [] diff --git a/src/brew_file/brew_helper.py b/src/brew_file/brew_helper.py index fd58f965..0b6f3fd5 100644 --- a/src/brew_file/brew_helper.py +++ b/src/brew_file/brew_helper.py @@ -43,8 +43,12 @@ class BrewHelper: def __post_init__(self) -> None: self.log = logging.getLogger(__name__) + self.all_info: dict[str, dict[str, Any]] | None = None self.info: dict[str, dict[str, Any]] | None = None - self.all_formulae: dict[str, Any] | None = None + + self.formulae: list[str] | None = None + self.casks: list[str] | None = None + self.formula_aliases: dict[str, dict[str, str]] | None = None self.cask_aliases: dict[str, dict[str, str]] | None = None self.taps: dict[str, Any] | None = None @@ -133,11 +137,54 @@ def brew_val(self, name: str) -> Any: self.opt[name] = lines[0] return self.opt[name] - def get_info(self) -> dict[str, Any]: - """Get info of installed brew package.""" - if self.info is None: + def get_formulae(self) -> list[str]: + if self.formulae is None: _, lines = self.proc( - cmd="brew info --json=v2 --installed", + cmd="brew formulae", + print_cmd=False, + print_out=False, + exit_on_err=True, + separate_err=True, + ) + # test formulae in a deep direcotyr was found for hashicorp/tap + self.formulae = [ + x for x in lines if len(x.split("/")[-1]) in (1, 3) + ] + return self.formulae + + def get_casks(self) -> list[str]: + if self.casks is None: + _, lines = self.proc( + cmd="brew casks", + print_cmd=False, + print_out=False, + exit_on_err=True, + separate_err=True, + ) + self.casks = lines + return self.casks + + def get_packages(self) -> list[str]: + formulae = self.get_formulae() + casks = self.get_casks() + return formulae + casks + + def get_all_info(self) -> dict[str, Any]: + """Get info of all available brew package.""" + if self.all_info is None: + ret, lines = self.proc( + cmd="brew info --json=v2 --eval-all", + print_cmd=False, + print_out=False, + exit_on_err=False, + separate_err=False, + ) + if ret != 0: + formulae = self.get_formulae() + casks = self.get_casks() + + _, lines = self.proc( + cmd=f"brew info --json=v2 --cask {' '.join(casks)}", print_cmd=False, print_out=False, exit_on_err=True, @@ -145,26 +192,63 @@ def get_info(self) -> dict[str, Any]: ) lines = lines[lines.index("{") :] data = json.loads("".join(lines)) - self.info = { - "formulae": {x["name"]: x for x in data["formulae"]}, + self.all_info = { + "formulae": {}, "casks": {x["token"]: x for x in data["casks"]}, } - return self.info + while ret != 0: + ret, lines = self.proc( + cmd=f"brew info --json=v2 --formula {' '.join(formulae)}", + print_cmd=False, + print_out=False, + exit_on_err=False, + separate_err=False, + ) + if ret == 0: + break + formulae_updated = 0 + for line in lines: + # Remove package from list if it has no URL + # https://github.com/hashicorp/homebrew-tap/issues/258 + if "formula requires at least a URL" in line: + formula = line.split()[1] + if formula in formulae: + formulae.remove(formula) + formulae_updated = 1 + if formula.split("/")[-1] in formulae: + formulae.remove(formula.split("/")[-1]) + formulae_updated = 1 + continue + if formulae_updated: + continue + msg = "Failed to get info of all packages.\n\n" + msg += "\n".join(lines) + raise RuntimeError(msg) - def get_all_formulae(self) -> dict[str, Any]: - """Get info of all formulae.""" - if self.all_formulae is None: + lines = lines[lines.index("{") :] + data = json.loads("".join(lines)) + self.all_info["formulae"] = { + x["name"]: x for x in data["formulae"] + } + return self.all_info + + def get_info(self) -> dict[str, Any]: + """Get info of installed brew package.""" + if self.info is None: _, lines = self.proc( - cmd="brew info --json=v1 --eval-all", + cmd="brew info --json=v2 --installed", print_cmd=False, print_out=False, exit_on_err=True, separate_err=True, ) - lines = lines[lines.index("[") :] + lines = lines[lines.index("{") :] data = json.loads("".join(lines)) - self.all_formulae = {x["name"]: x for x in data} - return self.all_formulae + self.info = { + "formulae": {x["name"]: x for x in data["formulae"]}, + "casks": {x["token"]: x for x in data["casks"]}, + } + return self.info def get_formula_list(self) -> list[str]: info = self.get_info()