镜像自地址
https://github.com/JGZYES/ParlzPackageManger.git
已同步 2026-09-11 08:52:45 +08:00
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.
这个提交包含在:
+161
-2
@@ -238,24 +238,159 @@ static int cmd_info(int argc, char **argv) {
|
|||||||
const char *name = json_str(root, "name");
|
const char *name = json_str(root, "name");
|
||||||
const char *ver = json_str(root, "version");
|
const char *ver = json_str(root, "version");
|
||||||
pmm_info("%s%s%s\n", name ? name : pkg, ver ? " " : "", ver ? ver : "");
|
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");
|
JsonValue *vr = json_get(root, "variants");
|
||||||
if (vr && vr->count > 0) {
|
if (vr && vr->count > 0) {
|
||||||
|
printf(" versions:\n");
|
||||||
for (int i = 0; i < vr->count; i++) {
|
for (int i = 0; i < vr->count; i++) {
|
||||||
JsonValue *v = json_at(vr, i);
|
JsonValue *v = json_at(vr, i);
|
||||||
if (!v) continue;
|
if (!v) continue;
|
||||||
const char *vv = json_str(v, "version");
|
const char *vv = json_str(v, "version");
|
||||||
const char *osn = json_str(v, "os");
|
const char *osn = json_str(v, "os");
|
||||||
const char *archn = json_str(v, "arch");
|
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 {
|
} else {
|
||||||
const char *u = json_str(root, "url");
|
const char *u = json_str(root, "url");
|
||||||
|
const char *s256 = json_str(root, "sha256");
|
||||||
printf(" url: %s\n", u ? u : "-");
|
printf(" url: %s\n", u ? u : "-");
|
||||||
|
if (s256) printf(" sha256: %s\n", s256);
|
||||||
}
|
}
|
||||||
json_free(root);
|
json_free(root);
|
||||||
return 0;
|
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 <file> — print sha256 (and sha1) of a downloaded package file. */
|
/* pmm verify <file> — print sha256 (and sha1) of a downloaded package file. */
|
||||||
static int cmd_verify(int argc, char **argv) {
|
static int cmd_verify(int argc, char **argv) {
|
||||||
if (argc < 1) { pmm_error("usage: pmm verify <file>\n"); return 1; }
|
if (argc < 1) { pmm_error("usage: pmm verify <file>\n"); return 1; }
|
||||||
@@ -485,6 +620,7 @@ static void print_help(void) {
|
|||||||
printf(" pmm list list installed packages\n");
|
printf(" pmm list list installed packages\n");
|
||||||
printf(" pmm remove <pkg> uninstall a package\n");
|
printf(" pmm remove <pkg> uninstall a package\n");
|
||||||
printf(" pmm self-update update pmm (auto os/arch)\n");
|
printf(" pmm self-update update pmm (auto os/arch)\n");
|
||||||
|
printf(" pmm upgrade [--yes] upgrade all installed packages\n");
|
||||||
printf(" pmm install --git <repo-url.git> any git host, API auto-detected\n");
|
printf(" pmm install --git <repo-url.git> any git host, API auto-detected\n");
|
||||||
printf(" pmm install --github <owner/repo> GitHub latest release\n");
|
printf(" pmm install --github <owner/repo> GitHub latest release\n");
|
||||||
printf(" pmm install --gitlab <owner/repo> GitLab latest release\n");
|
printf(" pmm install --gitlab <owner/repo> GitLab latest release\n");
|
||||||
@@ -495,7 +631,10 @@ static void print_help(void) {
|
|||||||
printf(" pmm version | help\n\n");
|
printf(" pmm version | help\n\n");
|
||||||
printf("options:\n");
|
printf("options:\n");
|
||||||
printf(" -p<drive> install under <DRIVE>:\\\\.pmm (e.g. -pd -> D:\\\\.pmm, -pc -> C:\\\\.pmm)\n");
|
printf(" -p<drive> install under <DRIVE>:\\\\.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: <base>/pmm.json | pmm.ini | pmm.conf (base = <drive>:\\\\.pmm 或 ~/.pmm)\n");
|
printf("config: <base>/pmm.json | pmm.ini | pmm.conf (base = <drive>:\\\\.pmm 或 ~/.pmm)\n");
|
||||||
printf("mirrors: <base>/mirror.ini | mirror.conf\n");
|
printf("mirrors: <base>/mirror.ini | mirror.conf\n");
|
||||||
printf("asset mapping: windows=exe/msi/zip/7z linux=deb/rpm/appimage/tar.* macos=dmg/pkg\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;
|
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) {
|
int main(int argc, char **argv) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
SetConsoleOutputCP(CP_UTF8); /* UTF-8 output so Chinese help/version isn't mojibake */
|
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; }
|
if (argc < 2) { print_help(); return 0; }
|
||||||
pmm_set_self_path(argv[0]);
|
pmm_set_self_path(argv[0]);
|
||||||
argc = consume_drive_flag(argc, argv);
|
argc = consume_drive_flag(argc, argv);
|
||||||
|
argc = consume_global_flags(argc, argv);
|
||||||
if (argc < 2) { print_help(); return 0; }
|
if (argc < 2) { print_help(); return 0; }
|
||||||
|
|
||||||
if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 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;
|
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 (strcmp(argv[1], "install") == 0) {
|
||||||
if (argc >= 3 && (strcmp(argv[2], "--git") == 0 || strcmp(argv[2], "--github") == 0 ||
|
if (argc >= 3 && (strcmp(argv[2], "--git") == 0 || strcmp(argv[2], "--github") == 0 ||
|
||||||
strcmp(argv[2], "--gitlab") == 0 || strcmp(argv[2], "--gitea") == 0 ||
|
strcmp(argv[2], "--gitlab") == 0 || strcmp(argv[2], "--gitea") == 0 ||
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
#define IS_TTY_FD(fd) isatty(fd)
|
#define IS_TTY_FD(fd) isatty(fd)
|
||||||
#endif
|
#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.
|
/* 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).
|
* 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
|
* 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);
|
size_t len = strlen(buf);
|
||||||
while (len && (buf[len-1] == '\n' || buf[len-1] == '\r')) buf[--len] = '\0';
|
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)
|
if (tty && wrap_color)
|
||||||
fprintf(stream, "\033[%dm[PMM]:[%s]%s\033[0m\n", wrap_color, level, buf);
|
fprintf(stream, "\033[%dm[PMM]:[%s]%s\033[0m\n", wrap_color, level, buf);
|
||||||
else
|
else
|
||||||
@@ -41,12 +45,14 @@ void pmm_error(const char *fmt, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void pmm_success(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);
|
va_list ap; va_start(ap, fmt);
|
||||||
pmmsg(stdout, 1, "SUCCESS", 32, fmt, ap); /* green */
|
pmmsg(stdout, 1, "SUCCESS", 32, fmt, ap); /* green */
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pmm_info(const char *fmt, ...) {
|
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);
|
va_list ap; va_start(ap, fmt);
|
||||||
pmmsg(stdout, 1, "INFO", 2, fmt, ap); /* grey (SGR 2 = dim) */
|
pmmsg(stdout, 1, "INFO", 2, fmt, ap); /* grey (SGR 2 = dim) */
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|||||||
@@ -13,6 +13,18 @@
|
|||||||
#ifndef PMM_OUT_H
|
#ifndef PMM_OUT_H
|
||||||
#define PMM_OUT_H
|
#define PMM_OUT_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* 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_error(const char *fmt, ...);
|
||||||
void pmm_success(const char *fmt, ...);
|
void pmm_success(const char *fmt, ...);
|
||||||
void pmm_info(const char *fmt, ...);
|
void pmm_info(const char *fmt, ...);
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户