Created
May 12, 2026 18:45
-
-
Save jabbalaci/18285a138017f5ecf8801f1707c849bc to your computer and use it in GitHub Desktop.
nimpkg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # Author: Laszlo Szathmary, alias Jabba Laci, 2026 | |
| import json | |
| import os | |
| import shlex | |
| from pathlib import Path | |
| from pprint import pprint | |
| from subprocess import PIPE, STDOUT, Popen | |
| from time import sleep | |
| import requests | |
| RED = "\033[31m" | |
| GREEN = "\033[32m" | |
| BLUE = "\033[34m" | |
| BOLD = "\033[1m" | |
| RESET = "\033[0m" | |
| HOME = Path.home() | |
| NIMBLE = f"{HOME}/.nimble" | |
| NIMBLE_BIN = f"{NIMBLE}/bin" | |
| PACKAGES_JSON = f"{NIMBLE}/packages_official.json" | |
| # {0}: owner, {1}: repo name; example: https://api.github.com/repos/jabbalaci/nimbang/tags | |
| GITHUB_API = "https://api.github.com/repos/{0}/{1}/tags" | |
| # API endpoint for releases (example): https://api.github.com/repos/nim-lang/langserver/releases/latest | |
| DONT_USE_API_KEY = 0 | |
| USE_API_KEY = 1 | |
| GITHUB_ACCESS_MODE = DONT_USE_API_KEY | |
| def get_simple_cmd_output(cmd, stderr=STDOUT): | |
| args = shlex.split(cmd) | |
| return Popen(args, stdout=PIPE, stderr=stderr).communicate()[0].decode("utf8") | |
| def get_installed_packages() -> list[str]: | |
| cmd = "nimble list -i" | |
| return get_simple_cmd_output(cmd).strip().splitlines() | |
| def get_binaries() -> list[str]: | |
| return sorted(os.listdir(NIMBLE_BIN)) | |
| def get_package_info(name: str) -> dict | None: | |
| def clean(s: str) -> str: | |
| return s.strip().replace('"', "") | |
| d = {} | |
| cmd = f"nimble dump {name}" | |
| for line in get_simple_cmd_output(cmd).strip().splitlines(): | |
| try: | |
| left, right = line.split(":") | |
| except ValueError: | |
| # print("") | |
| # print(f"Warning: problem with the package {name}") | |
| # print(f"Tip: check `nimble dump {name}`") | |
| return None | |
| if left == "name": | |
| d["package_name"] = clean(right) | |
| assert d["package_name"] == name # they should be the same | |
| if left == "version": | |
| d["current_version"] = clean(right) | |
| if left == "bin": | |
| d["bin"] = sorted(clean(right).split(", ")) | |
| # | |
| # | |
| return d | |
| def build_package_db(installed: list[str]) -> dict: | |
| print("Getting package details", end="") | |
| result = {} | |
| for pkg in installed: | |
| print(".", end="", flush=True) | |
| d = get_package_info(pkg) | |
| if d and len(d["bin"][0]) > 0: | |
| result[pkg] = d | |
| # | |
| print() | |
| return result | |
| def get_tag_name(api_key: str, owner_name: str, repo_name: str) -> str: | |
| print(".", end="", flush=True) | |
| url = GITHUB_API.format(owner_name, repo_name) | |
| headers = {"Authorization": f"Bearer {api_key}"} if api_key else {} | |
| try: | |
| r = requests.get(url, headers=headers) | |
| sleep(0.1) # to avoid bashing the server | |
| li = r.json() | |
| d = li[0] | |
| tag = d["name"].replace("v", "") | |
| return tag | |
| except: | |
| print("") | |
| print("Warning: couldn't get the tag info from here:", url) | |
| return "" | |
| # | |
| def get_repo_info(package_names: list[str]) -> dict: | |
| print("Getting latest tag versions", end="") | |
| result = {} | |
| with open(PACKAGES_JSON) as f: | |
| li = json.load(f) | |
| # | |
| api_key = os.environ.get("GITHUB_API_KEY", "") | |
| for d in li: | |
| if d["name"] in package_names: | |
| url = d["url"] | |
| context = {"url": url} | |
| if url.startswith("https://github.com"): | |
| parts = url.split("/") | |
| context["repo_name"] = parts[-1] | |
| context["owner_name"] = parts[-2] | |
| context["latest_tag_name"] = get_tag_name( | |
| api_key, context["owner_name"], context["repo_name"] | |
| ) | |
| # | |
| result[d["name"]] = context | |
| # | |
| # | |
| print() | |
| return result | |
| def make_report(db: dict, repo_info: dict) -> None: | |
| template = """ | |
| {0}: local version: {1} {2} latest_tag: {3} {4} | |
| """.strip() | |
| to_update = [] | |
| for pkg_name, local_d in db.items(): | |
| current_version = local_d["current_version"] | |
| repo_d = repo_info[pkg_name] | |
| latest_tag = repo_d["latest_tag_name"] | |
| s1 = current_version | |
| s3 = latest_tag | |
| s0 = ( | |
| BOLD + GREEN + pkg_name + RESET | |
| if s1 == s3 | |
| else BOLD + RED + pkg_name + RESET | |
| ) | |
| if s3 == "": | |
| s3 = BOLD + RED + "???" + RESET | |
| s2 = "==" if s1 == s3 else "!=" | |
| s4 = BOLD + GREEN + "[OK]" + RESET if s1 == s3 else BOLD + RED + "[XXX]" + RESET | |
| print(template.format(s0, s1, s2, s3, s4)) | |
| if s1 != s3: | |
| to_update.append(pkg_name) | |
| # | |
| # | |
| print("---") | |
| for pkg_name in to_update: | |
| repo_d = repo_info[pkg_name] | |
| latest_tag = repo_d["latest_tag_name"] | |
| if latest_tag: | |
| print(f"* nimble install {pkg_name}@{latest_tag}") | |
| # | |
| # | |
| def check_api_key() -> None: | |
| if GITHUB_ACCESS_MODE == DONT_USE_API_KEY: | |
| print("# not using an API key") | |
| else: | |
| print("# using an API key") | |
| # | |
| if GITHUB_ACCESS_MODE == USE_API_KEY: | |
| try: | |
| api_key = os.environ["GITHUB_API_KEY"] | |
| except KeyError: | |
| print("Error: the env. variable GITHUB_API_KEY is not available") | |
| exit(2) | |
| def main(): | |
| check_api_key() | |
| # | |
| installed: list[str] = get_installed_packages()[3:] | |
| bins: list[str] = get_binaries() | |
| # print(installed) | |
| # print("---") | |
| # print(bins) | |
| # print("---") | |
| # d: dict | None = get_package_info("zigcc") | |
| # pprint(d) | |
| # print("---") | |
| db: dict = build_package_db(installed) | |
| pprint(db) | |
| print("---") | |
| repo_info: dict = get_repo_info(list(db.keys())) | |
| pprint(repo_info) | |
| print("---") | |
| make_report(db, repo_info) | |
| ############################################################################## | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment