From 51a88a4678be15774a8b55a524f455a3e33fbc2c Mon Sep 17 00:00:00 2001 From: JGZYES Date: Fri, 4 Sep 2026 20:13:42 +0800 Subject: [PATCH] feature: pmm upgrade + pmm info enhancement + color/quiet/verbose flags - pmm upgrade [--yes]: scan ~/.pmm/installed/*.info, look up each package's latest registry version for this os/arch, and upgrade with a per-package prompt (or automatically with --yes). Adds cmp_version + installed_version helpers and wires the 'upgrade' subcommand. - pmm info: also print package description (when present) and an indented versions list (or url/sha256 for legacy single-platform entries). - Output flags: --no-color (also PMM_NO_COLOR=1), -q/--quiet (only warn+error), --verbose. Added pmm_no_color + pmm_log_level globals to out.c/out.h and a consume_global_flags() pre-dispatch pass in main.c so every subcommand honors them. info/success are suppressed only in quiet mode (verbose keeps them). - help: document upgrade, --no-color, -q/--quiet, --verbose. Verified on WSL gcc: compiles clean; no-color strips ANSI on tty, -q silences info/success while keeping errors, --verbose keeps info, info lists description + versions, upgrade recognizes up-to-date packages. --- src/main.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/out.c | 8 ++- src/out.h | 12 ++++ 3 files changed, 180 insertions(+), 3 deletions(-) diff --git a/src/main.c b/src/main.c index 4b2b78e..8ac8faf 100644 --- a/src/main.c +++ b/src/main.c @@ -238,24 +238,159 @@ static int cmd_info(int argc, char **argv) { const char *name = json_str(root, "name"); const char *ver = json_str(root, "version"); pmm_info("%s%s%s\n", name ? name : pkg, ver ? " " : "", ver ? ver : ""); + const char *desc = json_str(root, "description"); + if (desc) pmm_info("description: %s\n", desc); JsonValue *vr = json_get(root, "variants"); if (vr && vr->count > 0) { + printf(" versions:\n"); for (int i = 0; i < vr->count; i++) { JsonValue *v = json_at(vr, i); if (!v) continue; const char *vv = json_str(v, "version"); const char *osn = json_str(v, "os"); const char *archn = json_str(v, "arch"); - printf(" %s %s/%s\n", vv ? vv : "?", osn ? osn : "-", archn ? archn : "-"); + printf(" %-12s %s/%s\n", vv ? vv : "?", osn ? osn : "-", archn ? archn : "-"); } } else { const char *u = json_str(root, "url"); + const char *s256 = json_str(root, "sha256"); printf(" url: %s\n", u ? u : "-"); + if (s256) printf(" sha256: %s\n", s256); } json_free(root); return 0; } +/* Compare two dotted versions ("1.0.51" vs "1.0.5"), numeric component-wise. + * Returns >0 if a is newer, <0 if b newer, 0 if equal. Mirrors install.c vcmp. */ +static int cmp_version(const char *a, const char *b) { + const char *pa = a, *pb = b; + while (*pa || *pb) { + double va = 0, vb = 0; int ha = 0, hb = 0; + while (*pa && *pa != '.' && *pa != '-') { va = va * 10 + (*pa - '0'); pa++; ha = 1; } + while (*pb && *pb != '.' && *pb != '-') { vb = vb * 10 + (*pb - '0'); pb++; hb = 1; } + if (ha != hb) return ha < hb ? -1 : 1; + if (va != vb) return va < vb ? -1 : 1; + if (*pa == '.') pa++; if (*pb == '.') pb++; + } + return 0; +} + +/* Read "Package:" and "Version:" from the first lines of an installed .info. + * The header block is authoritative (the embedded ctl repeats them lower). */ +static void installed_version(const char *info, char *pkg, size_t pkgsz, char *ver, size_t versz) { + const char *p = info; + pkg[0] = ver[0] = '\0'; + while (p && *p) { + const char *eol = strchr(p, '\n'); + size_t ll = eol ? (size_t)(eol - p) : strlen(p); + char line[512]; + if (ll >= sizeof(line)) ll = sizeof(line) - 1; + memcpy(line, p, ll); line[ll] = '\0'; + char *colon = strchr(line, ':'); + if (colon) { + *colon = '\0'; + const char *key = line, *val = colon + 1; + while (*val == ' ' || *val == '\t') val++; + char *e = val + strlen(val); + while (e > val && (e[-1] == ' ' || e[-1] == '\r')) *--e = '\0'; + if (strcmp(key, "Package") == 0) snprintf(pkg, pkgsz, "%s", val); + else if (strcmp(key, "Version") == 0) snprintf(ver, versz, "%s", val); + } + if (!eol) break; + p = eol + 1; + } +} + +/* pmm upgrade — for each installed .pdm, look up the registry latest version + * and offer to upgrade. With --yes, upgrade without prompting. */ +static int cmd_upgrade(int argc, char **argv) { + int yes = 0; + for (int i = 0; i < argc; i++) + if (strcmp(argv[i], "--yes") == 0 || strcmp(argv[i], "-y") == 0) yes = 1; + + char home[1024]; + pmm_config_dir(home, sizeof(home)); + char db[1200]; + snprintf(db, sizeof(db), "%s/installed", home); + + DIR *d = opendir(db); + if (!d) { pmm_info("no installed packages.\n"); return 0; } + + PmmConfig cfg; MirrorSel mirror; + load_config(&cfg); load_mirror(&cfg, &mirror); + + struct dirent *e; + int upgraded = 0, checked = 0; + while ((e = readdir(d)) != NULL) { + size_t ln = strlen(e->d_name); + if (ln < 5 || strcmp(e->d_name + ln - 5, ".info") != 0) continue; + char ipath[1400], info[4096]; + snprintf(ipath, sizeof(ipath), "%s/%s", db, e->d_name); + FILE *fp = fopen(ipath, "rb"); + if (!fp) continue; + size_t got = fread(info, 1, sizeof(info) - 1, fp); + fclose(fp); + info[got] = '\0'; + + char pkg[256], curver[128]; + installed_version(info, pkg, sizeof(pkg), curver, sizeof(curver)); + if (!pkg[0]) continue; + checked++; + + /* fetch latest for this package (current os/arch) */ + char rel[512]; snprintf(rel, sizeof(rel), "%s.json", pkg); + int status = 0; + char *body = registry_fetch(rel, &status); + if (!body) { pmm_warn("skipping %s: no registry entry\n", pkg); continue; } + JsonValue *root = json_parse(body); + free(body); + if (!root) continue; + + const char *osn = pmm_os_name(pmm_detect_os()); + const char *arn = pmm_detect_arch(); + const char *best = NULL; + JsonValue *vr = json_get(root, "variants"); + if (vr && vr->count > 0) { + for (int i = 0; i < vr->count; i++) { + JsonValue *v = json_at(vr, i); + if (!v || v->type != JSON_OBJECT) continue; + const char *vos = json_str(v, "os"); + if (!vos || (strcmp(vos, osn) != 0 && strcmp(vos, "any") != 0)) continue; + const char *va = json_str(v, "arch"); + if (va && va[0] && strcmp(va, arn) != 0 && strcmp(va, "any") != 0) continue; + const char *vv = json_str(v, "version"); + if (!vv) continue; + if (!best || cmp_version(vv, best) > 0) best = vv; + } + } else { + best = json_str(root, "version"); + } + json_free(root); + if (!best) continue; + + if (cmp_version(best, curver) <= 0) { pmm_info("%s %s is up to date (latest %s)\n", pkg, curver, best); continue; } + + pmm_info("%s: %s -> %s\n", pkg, curver, best); + if (!yes) { + printf(" upgrade? [y/N] "); + fflush(stdout); + char ans[8] = ""; + if (!fgets(ans, sizeof(ans), stdin)) ans[0] = '\0'; + if (!(ans[0] == 'y' || ans[0] == 'Y')) continue; + } + if (install_from_registry(pkg, NULL, mirror.name) == 0) { upgraded++; } + else pmm_error("failed to upgrade %s\n", pkg); + } + closedir(d); + + free(cfg.registry_url); free(cfg.mirror_name); + free(mirror.name); free(mirror.api_base); + if (checked == 0) pmm_info("no installed .pdm packages.\n"); + else pmm_success("upgraded %d package(s).\n", upgraded); + return 0; +} + /* pmm verify — print sha256 (and sha1) of a downloaded package file. */ static int cmd_verify(int argc, char **argv) { if (argc < 1) { pmm_error("usage: pmm verify \n"); return 1; } @@ -485,6 +620,7 @@ static void print_help(void) { printf(" pmm list list installed packages\n"); printf(" pmm remove uninstall a package\n"); printf(" pmm self-update update pmm (auto os/arch)\n"); + printf(" pmm upgrade [--yes] upgrade all installed packages\n"); printf(" pmm install --git any git host, API auto-detected\n"); printf(" pmm install --github GitHub latest release\n"); printf(" pmm install --gitlab GitLab latest release\n"); @@ -495,7 +631,10 @@ static void print_help(void) { printf(" pmm version | help\n\n"); printf("options:\n"); printf(" -p install under :\\\\.pmm (e.g. -pd -> D:\\\\.pmm, -pc -> C:\\\\.pmm)\n"); - printf(" 第一个 -p 会被记住,后续命令无需再写;用 -pc 切回 C 盘\n\n"); + printf(" 第一个 -p 会被记住,后续命令无需再写;用 -pc 切回 C 盘\n"); + printf(" --no-color omit ANSI colours even on a terminal (PMM_NO_COLOR=1 too)\n"); + printf(" -q, --quiet only print errors/warnings (suppress info/success)\n"); + printf(" --verbose print extra detail\n\n"); printf("config: /pmm.json | pmm.ini | pmm.conf (base = :\\\\.pmm 或 ~/.pmm)\n"); printf("mirrors: /mirror.ini | mirror.conf\n"); printf("asset mapping: windows=exe/msi/zip/7z linux=deb/rpm/appimage/tar.* macos=dmg/pkg\n"); @@ -531,6 +670,22 @@ static int consume_drive_flag(int argc, char **argv) { return w; } +/* Strip global output flags (--no-color/-q/--quiet/--verbose) from argv before + * dispatch, so every subcommand gets the same behaviour without each having to + * parse them. Also honours PMM_NO_COLOR=1 env. Returns the new argc. */ +static int consume_global_flags(int argc, char **argv) { + int w = 1; + for (int r = 1; r < argc; r++) { + const char *a = argv[r]; + if (strcmp(a, "--no-color") == 0) { pmm_no_color = 1; continue; } + if (strcmp(a, "-q") == 0 || strcmp(a, "--quiet") == 0) { pmm_log_level = 1; continue; } + if (strcmp(a, "--verbose") == 0) { pmm_log_level = 2; continue; } + argv[w++] = argv[r]; + } + if (getenv("PMM_NO_COLOR")) pmm_no_color = 1; + return w; +} + int main(int argc, char **argv) { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); /* UTF-8 output so Chinese help/version isn't mojibake */ @@ -554,6 +709,7 @@ int main(int argc, char **argv) { if (argc < 2) { print_help(); return 0; } pmm_set_self_path(argv[0]); argc = consume_drive_flag(argc, argv); + argc = consume_global_flags(argc, argv); if (argc < 2) { print_help(); return 0; } if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { @@ -586,6 +742,9 @@ int main(int argc, char **argv) { return rc == 0 ? 0 : 1; } + if (strcmp(argv[1], "upgrade") == 0) + return cmd_upgrade(argc - 2, argv + 2); + if (strcmp(argv[1], "install") == 0) { if (argc >= 3 && (strcmp(argv[2], "--git") == 0 || strcmp(argv[2], "--github") == 0 || strcmp(argv[2], "--gitlab") == 0 || strcmp(argv[2], "--gitea") == 0 || diff --git a/src/out.c b/src/out.c index ea7c9c8..0361d0c 100644 --- a/src/out.c +++ b/src/out.c @@ -12,6 +12,10 @@ #define IS_TTY_FD(fd) isatty(fd) #endif +/* Global output flags (extern in out.h). Set from main.c flags / env. */ +int pmm_no_color = 0; /* --no-color / PMM_NO_COLOR=1: never emit ANSI */ +int pmm_log_level = 0; /* 0 normal, 1 quiet (-q), 2 verbose (--verbose) */ + /* Emit a message with an optional ANSI colour and a [PMM]:[LEVEL] prefix. * fd_stream: 1 for stdout (info/success), 2 for stderr (error/warn). * colour is the ANSI SGR number (0 = no colour), OR "" style. We always write @@ -27,7 +31,7 @@ static void pmmsg(FILE *stream, int fd, const char *level, int wrap_color, const size_t len = strlen(buf); while (len && (buf[len-1] == '\n' || buf[len-1] == '\r')) buf[--len] = '\0'; - int tty = IS_TTY_FD(fd); + int tty = IS_TTY_FD(fd) && !pmm_no_color; if (tty && wrap_color) fprintf(stream, "\033[%dm[PMM]:[%s]%s\033[0m\n", wrap_color, level, buf); else @@ -41,12 +45,14 @@ void pmm_error(const char *fmt, ...) { } void pmm_success(const char *fmt, ...) { + if (pmm_log_level == 1) return; /* quiet: suppress success; verbose(2) keeps it */ va_list ap; va_start(ap, fmt); pmmsg(stdout, 1, "SUCCESS", 32, fmt, ap); /* green */ va_end(ap); } void pmm_info(const char *fmt, ...) { + if (pmm_log_level == 1) return; /* quiet: suppress info; verbose(2) keeps it */ va_list ap; va_start(ap, fmt); pmmsg(stdout, 1, "INFO", 2, fmt, ap); /* grey (SGR 2 = dim) */ va_end(ap); diff --git a/src/out.h b/src/out.h index 2350072..f4e8f9c 100644 --- a/src/out.h +++ b/src/out.h @@ -13,6 +13,18 @@ #ifndef PMM_OUT_H #define PMM_OUT_H +#include + +/* Global output flags consulted by the pmm_* helpers. + * pmm_no_color : 1 = never emit ANSI colour (even on a tty). Set by --no-color + * or PMM_NO_COLOR=1. + * pmm_log_level : 0 = normal (INFO+SUCCESS+WARN+ERROR prints). + * 1 = quiet (-q/--quiet): only WARN and ERROR print. + * 2 = verbose (--verbose): INFO/SUCCESS print plus extra debug. + */ +extern int pmm_no_color; +extern int pmm_log_level; + void pmm_error(const char *fmt, ...); void pmm_success(const char *fmt, ...); void pmm_info(const char *fmt, ...);