From 0be951c86444dc3ee517e9f194ca069deb99921c Mon Sep 17 00:00:00 2001 From: JGZYES Date: Fri, 4 Sep 2026 19:23:27 +0800 Subject: [PATCH] color: structured [PMM]:[LEVEL] output (error=red/success=green/info=gray/warn=yellow) - Add src/out.c + src/out.h: pmm_error/pmm_success/pmm_info/pmm_warn helpers. Each prefixes '[PMM]:[LEVEL]' and, only when the stream is a tty, wraps it in an ANSI colour (red/green/gray/yellow). On redirect/pipe the text is plain. - Replace all error/success/info/warn print statements across main.c, install.c, pdm.c, pmm.c, json.c with these helpers (http.c download progress bar left untouched so its \r overwrite stays intact). - Windows: enable ENABLE_VIRTUAL_TERMINAL_PROCESSING on stdout/stderr so ANSI colours render (added alongside the existing SetConsoleOutputCP). - Rewrite 3 install.c system() error-echo strings to colored \033[31m echoes. - Build: add src/out.c to Makefile and build.bat. Verified on WSL gcc: compiles clean. tty (script) shows ESC-coloured output for INFO/ERROR/SUCCESS; non-tty shows plain '[PMM]:[LEVEL]' text with no escapes. --- .gitignore | 3 ++ Makefile | 2 +- build.bat | 2 +- src/install.c | 83 ++++++++++++++++++++++---------------------- src/json.c | 7 ++-- src/main.c | 96 ++++++++++++++++++++++++++++++--------------------- src/out.c | 53 ++++++++++++++++++++++++++++ src/out.h | 21 +++++++++++ src/pdm.c | 43 ++++++++++++----------- src/pmm.c | 19 +++++----- 10 files changed, 213 insertions(+), 116 deletions(-) create mode 100644 src/out.c create mode 100644 src/out.h diff --git a/.gitignore b/.gitignore index e80e135..afc88ae 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ .vscode/ .idea/ +# tool workspace cache +.zcode/ + # The mirror registry is committed so the mirror is complete and downloadable. # Un-ignore the .pdm files there (*.pdm elsewhere stays ignored). Uses ** so it # still matches after the mirror moved to web/mirror/. diff --git a/Makefile b/Makefile index f911d09..9a6e31c 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CFLAGS ?= -O2 -Wall -static -Wextra -std=c11 PREFIX ?= /usr/local SRC = src/main.c src/json.c src/ini.c src/pmm.c src/http.c src/repo.c \ - src/install.c src/sha256.c src/sha1.c src/mirrors.c src/pdm.c + src/install.c src/sha256.c src/sha1.c src/mirrors.c src/pdm.c src/out.c all: pmm diff --git a/build.bat b/build.bat index af64449..7f7bfe4 100644 --- a/build.bat +++ b/build.bat @@ -1,6 +1,6 @@ @echo off rem One-shot Windows build: produces pmm.exe (needs MinGW gcc + curl in PATH) -set SRC=src\main.c src\json.c src\ini.c src\pmm.c src\http.c src\repo.c src\install.c src\sha256.c src\sha1.c src\mirrors.c src\pdm.c +set SRC=src\main.c src\json.c src\ini.c src\pmm.c src\http.c src\repo.c src\install.c src\sha256.c src\sha1.c src\mirrors.c src\pdm.c src\out.c gcc -O2 -Wall -static -Wextra -std=c11 -o pmm.exe %SRC% if errorlevel 1 (echo pmm build failed & exit /b 1) echo built pmm.exe diff --git a/src/install.c b/src/install.c index a531765..2f87c73 100644 --- a/src/install.c +++ b/src/install.c @@ -13,6 +13,7 @@ */ #include "install.h" #include "pmm.h" +#include "out.h" #include "http.h" #include "json.h" #include "sha256.h" @@ -116,16 +117,16 @@ static int verify_checksums(const char *url, const char *path, const char *name) extract_hex(text, name, expect, sizeof(expect)); free(text); if (!expect[0]) { - printf("pmm: warning: could not parse %s checksum file\n", algos[i].ext); + pmm_warn("warning: could not parse %s checksum file\n", algos[i].ext); continue; } /* case-insensitive compare */ if (strcasecmp(expect, hex) != 0) { - fprintf(stderr, "pmm: CHECKSUM MISMATCH (%s)!\n expected: %s\n actual: %s\n", + pmm_error("CHECKSUM MISMATCH (%s)!\n expected: %s\n actual: %s\n", algos[i].ext, expect, hex); return -1; } - printf("pmm: %s checksum OK: %s\n", algos[i].algo == 256 ? "sha256" : "sha1", hex); + pmm_success("%s checksum OK: %s\n", algos[i].algo == 256 ? "sha256" : "sha1", hex); } return 0; } @@ -418,29 +419,29 @@ int install_file(const char *url, const char *name) { char **cands = mirrors_download_candidates(ml, url, &ncand); int ok = -1; for (int i = 0; i < ncand; i++) { - printf("pmm: downloading %s%s\n", cands[i], + pmm_info("downloading %s%s\n", cands[i], i < ncand - 1 ? " (mirror)" : ""); if (http_download(cands[i], path) == 0) { ok = 0; break; } - fprintf(stderr, "pmm: download failed, trying next source...\n"); + pmm_warn("download failed, trying next source...\n"); remove(path); } for (int i = 0; i < ncand; i++) free(cands[i]); free(cands); mirrors_free(ml); if (ok != 0) { - fprintf(stderr, "pmm: all download sources failed: %s\n", url); + pmm_error("all download sources failed: %s\n", url); return -1; } - printf("pmm: downloaded %s\n", path); + pmm_success("downloaded %s\n", path); /* integrity: sha256 + sha1, verified against sidecar checksums when present */ char hex[128]; if (pmm_sha256_file(path, hex) == 0) - printf("pmm: sha256: %s\n", hex); + pmm_info("sha256: %s\n", hex); if (pmm_sha1_file(path, hex) == 0) - printf("pmm: sha1: %s\n", hex); + pmm_info("sha1: %s\n", hex); if (verify_checksums(url, path, name) != 0) { - fprintf(stderr, "pmm: refusing to install: checksum verification failed\n"); + pmm_error("refusing to install: checksum verification failed\n"); remove(path); return -1; } @@ -460,9 +461,9 @@ static int install_path(const char *path, const char *name) { /* A bare binary (no extension) must really be a native executable for this * OS; otherwise refuse so the caller can fall back to the os-named asset. */ if (strchr(bname, '.') == NULL) { - printf("pmm: verifying %s is a native %s binary...\n", bname, pmm_os_name(os)); + pmm_info("verifying %s is a native %s binary...\n", bname, pmm_os_name(os)); if (!native_binary_ok(path, os)) { - fprintf(stderr, "pmm: %s is not a usable %s binary; will fall back\n", + pmm_warn("%s is not a usable %s binary; will fall back\n", bname, pmm_os_name(os)); return -1; } @@ -478,12 +479,12 @@ static int install_path(const char *path, const char *name) { /* .pdm packages are installed through the deb-like manager */ if (has_suffix(bname, ".pdm") || has_suffix(bname, ".PDM")) { extern int pdm_install_file(const char *path); - printf("pmm: installing .pdm %s ...\n", bname); + pmm_info("installing .pdm %s ...\n", bname); if (pdm_install_file(path) != 0) { - fprintf(stderr, "pmm: .pdm install failed: %s\n", bname); + pmm_error(".pdm install failed: %s\n", bname); return -1; } - printf("pmm: installed .pdm %s\n", bname); + pmm_success("installed .pdm %s\n", bname); pmm_add_to_path(); return 0; } @@ -507,7 +508,7 @@ static int install_path(const char *path, const char *name) { "elif command -v ar >/dev/null 2>&1; then T=$(mktemp -d); " "(cd \"$T\" && ar p \"%s\" data.tar.gz | tar -xzf -); sudo cp -a \"$T\"/. /; rm -rf \"$T\"; " "elif command -v alien >/dev/null 2>&1; then sudo alien -i \"%s\"; " - "else echo 'pmm: cannot unpack .deb (need tar)' 1>&2; exit 1; fi", + "else echo -e \"\\033[31m[PMM]:[ERROR]cannot unpack .deb (need tar)\\033[0m\" 1>&2; exit 1; fi", path, path, path); } } else if (has_suffix(bname, ".rpm") && os == OS_LINUX) { @@ -550,9 +551,9 @@ static int install_path(const char *path, const char *name) { "if command -v rpm >/dev/null 2>&1; then sudo rpm -Uvh \"%s\"; " "elif command -v rpm2cpio >/dev/null 2>&1; then sudo rpm2cpio \"%s\" | (cd / && sudo cpio -idm --quiet); " "elif command -v alien >/dev/null 2>&1; then sudo alien -i \"%s\"; " - "else echo 'pmm: cannot unpack .rpm (need rpm/cpio)' 1>&2; exit 1; fi", + "else echo -e \"\\033[31m[PMM]:[ERROR]cannot unpack .rpm (need rpm/cpio)\\033[0m\" 1>&2; exit 1; fi", path, path, path); - if (system(cmd) != 0) { fprintf(stderr, "pmm: rpm install failed\n"); return -1; } + if (system(cmd) != 0) { pmm_error("rpm install failed\n"); return -1; } (void)dcmd; (void)ct; #else /* create the (possibly multi-level) stage dir first; pmm_cpio_unpack @@ -562,18 +563,18 @@ static int install_path(const char *path, const char *name) { char mkstag[1400]; snprintf(mkstag, sizeof(mkstag), "mkdir -p \"%s\"", fstage); if (system(mkstag) != 0) { - fprintf(stderr, "pmm: cannot create rpm stage dir: %s\n", fstage); + pmm_error("cannot create rpm stage dir: %s\n", fstage); return -1; } } /* unpack into stage (no sudo needed; stage is user-writable) */ if (pmm_cpio_unpack_cmd(fstage, dcmd) != 0) { - fprintf(stderr, "pmm: rpm payload decompress/cpio failed (need %s)\n", dc); + pmm_error("rpm payload decompress/cpio failed (need %s)\n", dc); return -1; } /* copy the unpacked payload into /, then clean up */ if (system(cmd) != 0) { - fprintf(stderr, "pmm: failed to copy rpm payload into /\n"); + pmm_error("failed to copy rpm payload into /\n"); return -1; } (void)ct; @@ -583,7 +584,7 @@ static int install_path(const char *path, const char *name) { "if command -v rpm >/dev/null 2>&1; then sudo rpm -Uvh \"%s\"; " "elif command -v rpm2cpio >/dev/null 2>&1; then sudo rpm2cpio \"%s\" | (cd / && sudo cpio -idm --quiet); " "elif command -v alien >/dev/null 2>&1; then sudo alien -i \"%s\"; " - "else echo 'pmm: cannot unpack .rpm (need gzip/cpio)' 1>&2; exit 1; fi", + "else echo -e \"\\033[31m[PMM]:[ERROR]cannot unpack .rpm (need gzip/cpio)\\033[0m\" 1>&2; exit 1; fi", path, path, path); } } else if (has_suffix(bname, ".apk") && os == OS_LINUX) { @@ -635,12 +636,12 @@ static int install_path(const char *path, const char *name) { snprintf(cmd, sizeof(cmd), "cp -f \"%s\" \"%s/\" 2>/dev/null || true", path, dest); } - printf("pmm: installing %s ...\n", bname); + pmm_info("installing %s ...\n", bname); if (run_cmd_quiet(cmd) != 0) { - fprintf(stderr, "pmm: install command failed: %s\n", cmd); + pmm_error("install command failed: %s\n", cmd); return -1; } - printf("pmm: installed %s\n", bname); + pmm_success("installed %s\n", bname); pmm_add_to_path(); return 0; } @@ -648,11 +649,11 @@ static int install_path(const char *path, const char *name) { /* Install a local file by its extension — e.g. `pmm install -dpkg foo.deb` * (Linux) or `pmm install -msi foo.msi` (Windows). Returns 0 on success. */ int install_local_file(const char *path) { - if (!path || !*path) { fprintf(stderr, "pmm: empty file path\n"); return -1; } + if (!path || !*path) { pmm_error("empty file path\n"); return -1; } FILE *chk = fopen(path, "rb"); - if (!chk) { fprintf(stderr, "pmm: cannot open file: %s\n", path); return -1; } + if (!chk) { pmm_error("cannot open file: %s\n", path); return -1; } fclose(chk); - printf("pmm: installing local file %s\n", path); + pmm_info("installing local file %s\n", path); return install_path(path, path); } @@ -781,8 +782,8 @@ int install_from_registry(const char *name, const char *spec, const char *mirror if (ml->items[i].registry && *ml->items[i].registry) has_reg = 1; if (!has_reg) { - fprintf(stderr, - "pmm: no registry mirror configured. Add to ~/.pmm/mirror.ini:\n" + pmm_error( + "no registry mirror configured. Add to ~/.pmm/mirror.ini:\n" " [name]\n registry = https://host/pmm\n" " (apt-style: mirrors are tried by priority; the first with the\n" " package wins, else plain 'pmm install --git owner/repo')\n"); @@ -804,7 +805,7 @@ int install_from_registry(const char *name, const char *spec, const char *mirror if (!seen[i] && ml->items[i].registry && *ml->items[i].registry) bases[nb++] = ml->items[i].registry; - if (nb == 0) { fprintf(stderr, "pmm: no registry mirrors\n"); mirrors_free(ml); return -1; } + if (nb == 0) { pmm_error("no registry mirrors\n"); mirrors_free(ml); return -1; } /* fetch the .json latest pointer (carries the variants list) */ char url[2048]; @@ -813,7 +814,7 @@ int install_from_registry(const char *name, const char *spec, const char *mirror char *used_base = NULL; for (int i = 0; i < nb; i++) { snprintf(url, sizeof(url), "%s/%s.json", bases[i], name); - printf("pmm: looking up %s in mirror %s\n", name, bases[i]); + pmm_info("looking up %s in mirror %s\n", name, bases[i]); body = http_get(url, &status); if (getenv("PMM_DEBUG")) fprintf(stderr, "[reg] %s -> body=%s status=%d\n", bases[i], body ? "set" : "NULL", status); @@ -824,14 +825,14 @@ int install_from_registry(const char *name, const char *spec, const char *mirror free(body); body = NULL; } if (!body) { - fprintf(stderr, "pmm: package '%s' not found in any registry mirror\n", name); + pmm_error("package '%s' not found in any registry mirror\n", name); mirrors_free(ml); return -1; } JsonValue *meta = json_parse(body); free(body); if (!meta || meta->type != JSON_OBJECT) { - fprintf(stderr, "pmm: bad registry entry for '%s' (%s)\n", name, used_base); + pmm_error("bad registry entry for '%s' (%s)\n", name, used_base); json_free(meta); mirrors_free(ml); return -1; @@ -869,19 +870,19 @@ int install_from_registry(const char *name, const char *spec, const char *mirror const char *s = json_str(v, "sha256"); free(want_sha); want_sha = s ? strdup(s) : NULL; } } if (!chosen) { - fprintf(stderr, "pmm: no version of '%s' for os=%s arch=%s%s%s\n", name, osn, arch, + pmm_error("no version of '%s' for os=%s arch=%s%s%s\n", name, osn, arch, (spec && *spec) ? " satisfying '" : "", (spec && *spec) ? spec : ""); json_free(meta); mirrors_free(ml); return -1; } - printf("pmm: selected %s@%s (os=%s arch=%s)\n", name, chosen, osn, arch); + pmm_info("selected %s@%s (os=%s arch=%s)\n", name, chosen, osn, arch); } else { /* legacy single-platform entry */ dl = json_str(meta, "url"); file = json_str(meta, "file"); const char *s = json_str(meta, "sha256"); want_sha = s ? strdup(s) : NULL; - printf("pmm: selected %s@%s (os=%s)\n", name, json_str(meta, "version"), osn); + pmm_info("selected %s@%s (os=%s)\n", name, json_str(meta, "version"), osn); } if (!dl) { - fprintf(stderr, "pmm: registry entry for '%s' has no url\n", name); + pmm_error("registry entry for '%s' has no url\n", name); json_free(meta); mirrors_free(ml); return -1; } char *url_cp = strdup(dl); @@ -897,12 +898,12 @@ int install_from_registry(const char *name, const char *spec, const char *mirror snprintf(path, sizeof(path), "%s/%s", cache, file_cp); if (pmm_sha256_file(path, hex) == 0) { if (strcasecmp(want_sha, hex) != 0) { - fprintf(stderr, "pmm: CHECKSUM MISMATCH (%s registry entry)!\n" + pmm_error("CHECKSUM MISMATCH (%s registry entry)!\n" " expected: %s\n actual: %s\n", name, want_sha, hex); remove(path); rc = -1; } else { - printf("pmm: registry sha256 OK: %s\n", hex); + pmm_success("registry sha256 OK: %s\n", hex); } } } @@ -916,6 +917,6 @@ static int install_dep_spec(const char *depstr) { for (int i = 0; i < dep_seen_n; i++) if (strcmp(dep_seen[i], name) == 0) return 0; /* cycle / duplicate */ if (dep_seen_n < 128) dep_seen[dep_seen_n++] = strdup(name); - printf("pmm: resolving dependency %s%s%s\n", name, spec[0] ? " " : "", spec); + pmm_info("resolving dependency %s%s%s\n", name, spec[0] ? " " : "", spec); return install_from_registry(name, spec[0] ? spec : NULL, NULL); } diff --git a/src/json.c b/src/json.c index dd6f944..3d1aaca 100644 --- a/src/json.c +++ b/src/json.c @@ -1,5 +1,6 @@ /* json.c - minimal recursive-descent JSON parser (no external deps) */ #include "json.h" +#include "out.h" #include #include #include @@ -11,7 +12,7 @@ static JsonValue *parse_value(Parser *ps); static JsonValue *jnew(JsonType t) { JsonValue *v = calloc(1, sizeof(JsonValue)); - if (!v) { fprintf(stderr, "pmm: out of memory\n"); exit(1); } + if (!v) { pmm_error("out of memory\n"); exit(1); } v->type = t; return v; } @@ -35,7 +36,7 @@ static void jadd(JsonValue *v, char *key, JsonValue *item) { if (v->type == JSON_OBJECT) v->keys = realloc(v->keys, v->capacity * sizeof(char *)); if (!v->items || (v->type == JSON_OBJECT && !v->keys)) { - fprintf(stderr, "pmm: out of memory\n"); exit(1); + pmm_error("out of memory\n"); exit(1); } } if (v->type == JSON_OBJECT) v->keys[v->count] = key; @@ -47,7 +48,7 @@ static void skip_ws(Parser *ps) { } static void fail(const char *msg) { - fprintf(stderr, "pmm: JSON parse error: %s\n", msg); + pmm_error("JSON parse error: %s\n", msg); exit(1); } diff --git a/src/main.c b/src/main.c index 0fbe0e9..4b2b78e 100644 --- a/src/main.c +++ b/src/main.c @@ -29,6 +29,7 @@ #include #include "pmm.h" +#include "out.h" #include "json.h" #include "ini.h" #include "http.h" @@ -194,13 +195,13 @@ static char *registry_fetch(const char *rel, int *outstatus) { /* pmm search — list registry packages matching keyword. */ static int cmd_search(int argc, char **argv) { - if (argc < 1) { fprintf(stderr, "pmm: usage: pmm search \n"); return 1; } + if (argc < 1) { pmm_error("usage: pmm search \n"); return 1; } int status = 0; char *body = registry_fetch("packages.json", &status); - if (!body) { fprintf(stderr, "pmm: no registry index (packages.json) available\n"); return 1; } + if (!body) { pmm_error("no registry index (packages.json) available\n"); return 1; } JsonValue *root = json_parse(body); free(body); - if (!root || root->type != JSON_ARRAY) { fprintf(stderr, "pmm: bad registry index\n"); json_free(root); return 1; } + if (!root || root->type != JSON_ARRAY) { pmm_error("bad registry index\n"); json_free(root); return 1; } const char *kw = argv[0]; int found = 0; for (int i = 0; i < root->count; i++) { @@ -210,13 +211,13 @@ static int cmd_search(int argc, char **argv) { if (ci_contains(name, kw)) { printf(" %s\n", name); found++; } } json_free(root); - if (!found) printf("pmm: no package matches '%s'\n", kw); + if (!found) pmm_info("no package matches '%s'\n", kw); return 0; } /* pmm info — registry package info, or a local .pdm's control. */ static int cmd_info(int argc, char **argv) { - if (argc < 1) { fprintf(stderr, "pmm: usage: pmm info \n"); return 1; } + if (argc < 1) { pmm_error("usage: pmm info \n"); return 1; } const char *pkg = argv[0]; /* local .pdm -> control info */ FILE *chk = fopen(pkg, "rb"); @@ -230,13 +231,13 @@ static int cmd_info(int argc, char **argv) { char rel[512]; snprintf(rel, sizeof(rel), "%s.json", pkg); int status = 0; char *body = registry_fetch(rel, &status); - if (!body) { fprintf(stderr, "pmm: package '%s' not found in registry\n", pkg); return 1; } + if (!body) { pmm_error("package '%s' not found in registry\n", pkg); return 1; } JsonValue *root = json_parse(body); free(body); - if (!root || root->type != JSON_OBJECT) { fprintf(stderr, "pmm: bad registry entry '%s'\n", pkg); json_free(root); return 1; } + if (!root || root->type != JSON_OBJECT) { pmm_error("bad registry entry '%s'\n", pkg); json_free(root); return 1; } const char *name = json_str(root, "name"); const char *ver = json_str(root, "version"); - printf("pmm: %s%s%s\n", name ? name : pkg, ver ? " " : "", ver ? ver : ""); + pmm_info("%s%s%s\n", name ? name : pkg, ver ? " " : "", ver ? ver : ""); JsonValue *vr = json_get(root, "variants"); if (vr && vr->count > 0) { for (int i = 0; i < vr->count; i++) { @@ -257,12 +258,12 @@ static int cmd_info(int argc, char **argv) { /* pmm verify — print sha256 (and sha1) of a downloaded package file. */ static int cmd_verify(int argc, char **argv) { - if (argc < 1) { fprintf(stderr, "pmm: usage: pmm verify \n"); return 1; } + if (argc < 1) { pmm_error("usage: pmm verify \n"); return 1; } const char *file = argv[0]; char hex[128]; - if (pmm_sha256_file(file, hex) == 0) printf("pmm: sha256 %s %s\n", hex, file); - else { fprintf(stderr, "pmm: cannot read %s\n", file); return 1; } - if (pmm_sha1_file(file, hex) == 0) printf("pmm: sha1 %s %s\n", hex, file); + if (pmm_sha256_file(file, hex) == 0) pmm_success("sha256 %s %s\n", hex, file); + else { pmm_error("cannot read %s\n", file); return 1; } + if (pmm_sha1_file(file, hex) == 0) pmm_success("sha1 %s %s\n", hex, file); return 0; } @@ -301,7 +302,7 @@ static void rm_tree(const char *dir) { static int cmd_cache_clean(void) { char dir[1024]; pmm_cache_dir(dir, sizeof(dir)); - printf("pmm: cleaning cache %s\n", dir); + pmm_info("cleaning cache %s\n", dir); rm_tree(dir); return 0; } @@ -324,7 +325,7 @@ static int cmd_install_git(int argc, char **argv, const char *flag) { if (strcmp(argv[i], "--host") == 0 && i + 1 < argc) { host = host_from_name(argv[++i]); if (host == HOST_UNKNOWN) { - fprintf(stderr, "pmm: unknown host '%s'\n", argv[i]); + pmm_error("unknown host '%s'\n", argv[i]); return 1; } } else if ((strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--branch") == 0) && i + 1 < argc) { @@ -334,8 +335,8 @@ static int cmd_install_git(int argc, char **argv, const char *flag) { } } if (!repo) { - fprintf(stderr, - "pmm: usage:\n" + pmm_error( + "usage:\n" " pmm install --git any git host (auto-detected API)\n" " pmm install --github GitHub\n" " pmm install --gitlab GitLab\n" @@ -348,22 +349,22 @@ static int cmd_install_git(int argc, char **argv, const char *flag) { RepoContext *ctx = repo_open(host, repo, mirror.api_base); if (!ctx) return 1; if (branch && *branch) { free(ctx->ref); ctx->ref = strdup(branch); } - printf("pmm: host=%s repo=%s%s%s\n", host_name(host), repo, + pmm_info("host=%s repo=%s%s%s\n", host_name(host), repo, mirror.name ? " mirror=" : "", mirror.name ? mirror.name : ""); PmmOS os = pmm_detect_os(); ReleaseAsset *asset = repo_latest_asset(ctx, os); if (!asset && host == HOST_AUTO) - fprintf(stderr, "pmm: could not reach a known release API for %s " + pmm_warn("could not reach a known release API for %s " "(tried gitea/gitlab/github shapes)\n", repo); if (!asset) { if (host != HOST_AUTO) - fprintf(stderr, "pmm: no suitable %s asset found in latest release of %s\n", + pmm_error("no suitable %s asset found in latest release of %s\n", pmm_os_name(os), repo); repo_close(ctx); return 1; } - printf("pmm: %s -> %s (tag %s)\n", pmm_os_name(os), asset->name, asset->tag); + pmm_info("%s -> %s (tag %s)\n", pmm_os_name(os), asset->name, asset->tag); int rc = install_file(asset->url, asset->name); /* fallback: the first pick may have been a cross-platform/wrong asset @@ -374,7 +375,7 @@ static int cmd_install_git(int argc, char **argv, const char *flag) { if (sub) { ReleaseAsset *fb = repo_asset_matching(ctx, os, sub); if (fb && strcmp(fb->name, asset->name) != 0) { - printf("pmm: retrying with %s (%s)\n", fb->name, pmm_os_name(os)); + pmm_warn("retrying with %s (%s)\n", fb->name, pmm_os_name(os)); rc = install_file(fb->url, fb->name); repo_asset_free(fb); } @@ -413,10 +414,10 @@ static int cmd_mirror(int argc, char **argv) { } if (strcmp(argv[0], "add") == 0 && argc >= 3) { FILE *f = fopen(path, "a"); - if (!f) { fprintf(stderr, "pmm: cannot write %s\n", path); return 1; } + if (!f) { pmm_error("cannot write %s\n", path); return 1; } fprintf(f, "\n[%s]\napi = %s\n", argv[1], argv[2]); fclose(f); - printf("pmm: mirror '%s' added\n", argv[1]); + pmm_success("mirror '%s' added\n", argv[1]); return 0; } if (strcmp(argv[0], "use") == 0 && argc >= 2) { @@ -433,20 +434,20 @@ static int cmd_mirror(int argc, char **argv) { fclose(rf); } FILE *wf = fopen(cfgpath, "w"); - if (!wf) { fprintf(stderr, "pmm: cannot write %s\n", cfgpath); return 1; } + if (!wf) { pmm_error("cannot write %s\n", cfgpath); return 1; } int wrote = 0; for (int i = 0; i < n; i++) fputs(lines[i], wf); fprintf(wf, "mirror = %s\n", argv[1]); (void)wrote; fclose(wf); - printf("pmm: active mirror set to '%s'\n", argv[1]); + pmm_success("active mirror set to '%s'\n", argv[1]); return 0; } if (strcmp(argv[0], "remove") == 0 && argc >= 2) { Ini *ini = ini_load(path); - if (!ini) { fprintf(stderr, "pmm: no mirror file\n"); return 1; } + if (!ini) { pmm_error("no mirror file\n"); return 1; } FILE *f = fopen(path, "w"); - if (!f) { fprintf(stderr, "pmm: cannot write %s\n", path); ini_free(ini); return 1; } + if (!f) { pmm_error("cannot write %s\n", path); ini_free(ini); return 1; } char cursec[512] = ""; for (IniEntry *e = ini->head; e; e = e->next) { if (strcmp(e->section, cursec) != 0) { @@ -459,10 +460,10 @@ static int cmd_mirror(int argc, char **argv) { } fclose(f); ini_free(ini); - printf("pmm: mirror '%s' removed\n", argv[1]); + pmm_success("mirror '%s' removed\n", argv[1]); return 0; } - fprintf(stderr, "pmm: usage: pmm mirror list|add|use|remove ...\n"); + pmm_error("usage: pmm mirror list|add|use|remove ...\n"); return 1; } @@ -534,6 +535,21 @@ int main(int argc, char **argv) { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); /* UTF-8 output so Chinese help/version isn't mojibake */ setvbuf(stdout, NULL, _IONBF, 0); + /* Enable ANSI/VT colour on modern Windows consoles (legacy gets it too via + * ENABLE_VIRTUAL_TERMINAL_PROCESSING). Without this, "\033[31m" shows as + * literal text. Do it for both stdout and stderr. */ + { + HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); + if (h && h != INVALID_HANDLE_VALUE) { + DWORD m = 0; + if (GetConsoleMode(h, &m)) SetConsoleMode(h, m | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + h = GetStdHandle(STD_ERROR_HANDLE); + if (h && h != INVALID_HANDLE_VALUE) { + DWORD m = 0; + if (GetConsoleMode(h, &m)) SetConsoleMode(h, m | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + } #endif if (argc < 2) { print_help(); return 0; } pmm_set_self_path(argv[0]); @@ -544,14 +560,14 @@ int main(int argc, char **argv) { print_help(); return 0; } if (strcmp(argv[1], "version") == 0 || strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0) { - printf("pmm %s (%s) [%s]\n", PMM_VERSION, pmm_os_name(pmm_detect_os()), pmm_detect_arch()); + pmm_info("pmm %s (%s) [%s]\n", PMM_VERSION, pmm_os_name(pmm_detect_os()), pmm_detect_arch()); return 0; } if (strcmp(argv[1], "list") == 0) { pdm_list_installed(); return 0; } if (strcmp(argv[1], "mirror") == 0) return cmd_mirror(argc - 2, argv + 2); /* pmm remove (also used by the Windows "Uninstall" registration) */ if (strcmp(argv[1], "remove") == 0) { - if (argc < 3) { fprintf(stderr, "pmm: usage: pmm remove \n"); return 1; } + if (argc < 3) { pmm_error("usage: pmm remove \n"); return 1; } return pdm_remove(argv[2]) == 0 ? 0 : 1; } /* pmm self-update: install the latest 'pmm' tool package (auto os+arch) */ @@ -559,13 +575,13 @@ int main(int argc, char **argv) { PmmConfig cfg; MirrorSel mirror; load_config(&cfg); load_mirror(&cfg, &mirror); - printf("pmm: self-update -> installing latest pmm (%s/%s)\n", + pmm_info("self-update -> installing latest pmm (%s/%s)\n", pmm_os_name(pmm_detect_os()), pmm_detect_arch()); int rc = install_from_registry("pmm", NULL, mirror.name); free(cfg.registry_url); free(cfg.mirror_name); free(mirror.name); free(mirror.api_base); if (rc == 0) - printf("pmm: updated. New tools are under %s/root/bin (pmm/pdm). Re-run from there, or add it to PATH.\n", + pmm_success("updated. New tools are under %s/root/bin (pmm/pdm). Re-run from there, or add it to PATH.\n", pmm_config_dir((char[1024]){0}, 1024)); return rc == 0 ? 0 : 1; } @@ -576,7 +592,7 @@ int main(int argc, char **argv) { strcmp(argv[2], "--forgejo") == 0)) return cmd_install_git(argc - 3, argv + 3, argv[2] + 2); if (argc < 3) { - fprintf(stderr, "pmm: usage: pmm install [pkg2 ...]\n" + pmm_error("usage: pmm install [pkg2 ...]\n" " | pmm install -dpkg install a .deb (Linux)\n" " | pmm install -rpm install an .rpm (any Linux, incl. Debian/RPM)\n" " | pmm install --git \n"); @@ -627,7 +643,7 @@ int main(int argc, char **argv) { } size_t la = strlen(pkg); if (la > 4 && (strcmp(pkg + la - 4, ".pdm") == 0 || strcmp(pkg + la - 4, ".PDM") == 0)) { - if (pdm_install_file(pkg) == 0) ok++; else fprintf(stderr, "pmm: failed to install %s\n", pkg); + if (pdm_install_file(pkg) == 0) ok++; else pmm_error("failed to install %s\n", pkg); continue; } /* direct URL install: pmm install https://.../foo.zip @@ -639,14 +655,14 @@ int main(int argc, char **argv) { else if (forced == 2) bn = "download.msi"; else if (forced == 3) bn = "download.rpm"; if (install_file(pkg, bn) == 0) ok++; - else fprintf(stderr, "pmm: failed to install %s\n", pkg); + else pmm_error("failed to install %s\n", pkg); continue; } /* local file install: forced -dpkg/-msi, or an existing installer file * (e.g. `pmm install foo.deb` / `pmm install foo.msi`) */ if (forced == 1 || forced == 2 || forced == 3 || has_installer_ext(pkg)) { if (install_local_file(pkg) == 0) ok++; - else fprintf(stderr, "pmm: failed to install %s\n", pkg); + else pmm_error("failed to install %s\n", pkg); continue; } if (install_from_registry(pkg, spec[0] ? spec : NULL, mirror.name) == 0) ok++; @@ -658,7 +674,7 @@ int main(int argc, char **argv) { if (strcmp(argv[1], "pack") == 0) { if (argc < 3) { - fprintf(stderr, "pmm: usage: pmm pack [output.pdm]\n"); + pmm_error("usage: pmm pack [output.pdm]\n"); return 1; } return pdm_pack(argv[2], argc >= 4 ? argv[3] : NULL) == 0 ? 0 : 1; @@ -668,13 +684,13 @@ int main(int argc, char **argv) { return cmd_search(argc - 2, argv + 2); if (strcmp(argv[1], "cache") == 0) { if (argc >= 3 && strcmp(argv[2], "clean") == 0) return cmd_cache_clean(); - fprintf(stderr, "pmm: usage: pmm cache clean\n"); return 1; + pmm_error("usage: pmm cache clean\n"); return 1; } if (strcmp(argv[1], "info") == 0) return cmd_info(argc - 2, argv + 2); if (strcmp(argv[1], "verify") == 0) return cmd_verify(argc - 2, argv + 2); - fprintf(stderr, "pmm: unknown command '%s' (try 'pmm help')\n", argv[1]); + pmm_error("unknown command '%s' (try 'pmm help')\n", argv[1]); return 1; } diff --git a/src/out.c b/src/out.c new file mode 100644 index 0000000..3b4304a --- /dev/null +++ b/src/out.c @@ -0,0 +1,53 @@ +/* out.c - colored/structured console output implementation */ +#include "out.h" +#include +#include +#include + +#ifdef _WIN32 +#include +#define IS_TTY_FD(fd) _isatty(fd) +#else +#include +#define IS_TTY_FD(fd) isatty(fd) +#endif + +/* 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 + * the plain "[PMM]:[LEVEL]" prefix; colour only wraps it when the tty test + * passes. */ +static void pmmsg(FILE *stream, int fd, const char *level, int wrap_color, const char *fmt, va_list ap) { + char buf[4096]; + vsnprintf(buf, sizeof(buf), fmt, ap); + + int tty = IS_TTY_FD(fd); + if (tty && wrap_color) + fprintf(stream, "\033[%dm[PMM]:[%s]%s\033[0m\n", wrap_color, level, buf); + else + fprintf(stream, "[PMM]:[%s]%s\n", level, buf); +} + +void pmm_error(const char *fmt, ...) { + va_list ap; va_start(ap, fmt); + pmmsg(stderr, 2, "ERROR", 31, fmt, ap); /* red */ + va_end(ap); +} + +void pmm_success(const char *fmt, ...) { + va_list ap; va_start(ap, fmt); + pmmsg(stdout, 1, "SUCCESS", 32, fmt, ap); /* green */ + va_end(ap); +} + +void pmm_info(const char *fmt, ...) { + va_list ap; va_start(ap, fmt); + pmmsg(stdout, 1, "INFO", 2, fmt, ap); /* grey (SGR 2 = dim) */ + va_end(ap); +} + +void pmm_warn(const char *fmt, ...) { + va_list ap; va_start(ap, fmt); + pmmsg(stderr, 2, "WARN", 33, fmt, ap); /* yellow */ + va_end(ap); +} diff --git a/src/out.h b/src/out.h new file mode 100644 index 0000000..2350072 --- /dev/null +++ b/src/out.h @@ -0,0 +1,21 @@ +/* out.h - colored/structured console output + * + * Every user-facing message goes through one of these four helpers so PMM gets + * a consistent "[PMM]:[LEVEL]" prefix and, on a real terminal, ANSI colour: + * pmm_error -> stderr, red [PMM]:[ERROR] + * pmm_success -> stdout, green [PMM]:[SUCCESS] + * pmm_info -> stdout, grey [PMM]:[INFO] + * pmm_warn -> stderr, yellow [PMM]:[WARN] + * + * Colour is emitted ONLY when the output stream is a terminal (isatty); when + * redirecting to a file/pipe the text is printed plain with no escape codes. + */ +#ifndef PMM_OUT_H +#define PMM_OUT_H + +void pmm_error(const char *fmt, ...); +void pmm_success(const char *fmt, ...); +void pmm_info(const char *fmt, ...); +void pmm_warn(const char *fmt, ...); + +#endif \ No newline at end of file diff --git a/src/pdm.c b/src/pdm.c index e79a308..a2e390c 100644 --- a/src/pdm.c +++ b/src/pdm.c @@ -1,6 +1,7 @@ /* pdm.c - .pdm pack/install/remove (deb-like, built on system tar) */ #include "pdm.h" #include "pmm.h" +#include "out.h" #include "install.h" #include "sha256.h" #include @@ -136,13 +137,13 @@ int pdm_pack(const char *dir, const char *out) { snprintf(cp_, sizeof(cp_), "%s/pdm-control", dir); char *control = read_file(cp_, NULL); if (!control) { - fprintf(stderr, "pdm: %s has no pdm-control file\n", dir); + pmm_error("%s has no pdm-control file\n", dir); return -1; } char *pkg = control_get(control, "Package"); char *ver = control_get(control, "Version"); if (!pkg || !*pkg) { - fprintf(stderr, "pdm: pdm-control missing 'Package:' field\n"); + pmm_error("pdm-control missing 'Package:' field\n"); free(control); return -1; } if (!ver || !*ver) ver = strdup("0.0.0"); @@ -163,13 +164,13 @@ int pdm_pack(const char *dir, const char *out) { /* control.tar.gz (contains pdm-control; packed from the source dir) */ snprintf(cmd, sizeof(cmd), "tar -czf \"%s/control.tar.gz\" -C \"%s\" pdm-control", stage, dir); - if (system(cmd) != 0) { fprintf(stderr, "pdm: tar (control) failed\n"); rmtree(stage); return -1; } + if (system(cmd) != 0) { pmm_error("tar (control) failed\n"); rmtree(stage); return -1; } /* data.tar.gz (everything except pdm-control and the scratch dir) */ snprintf(cmd, sizeof(cmd), "tar -czf \"%s/data.tar.gz\" -C \"%s\" --exclude pdm-control --exclude %s .", stage, dir, stage); - if (system(cmd) != 0) { fprintf(stderr, "pdm: tar (data) failed\n"); rmtree(stage); return -1; } + if (system(cmd) != 0) { pmm_error("tar (data) failed\n"); rmtree(stage); return -1; } /* sha256sums of the two members */ char sums_path[1100], hex[128]; @@ -188,9 +189,9 @@ int pdm_pack(const char *dir, const char *out) { snprintf(cmd, sizeof(cmd), "tar -cf \"%s\" -C \"%s\" control.tar.gz data.tar.gz sha256sums", out, stage); int rc = system(cmd); rmtree(stage); - if (rc != 0) { fprintf(stderr, "pdm: failed to write %s\n", out); return -1; } + if (rc != 0) { pmm_error("failed to write %s\n", out); return -1; } - printf("pdm: packed %s (%s %s) -> %s\n", dir, pkg, ver, out); + pmm_success("packed %s (%s %s) -> %s\n", dir, pkg, ver, out); free(control); free(pkg); if (strcmp(ver, "0.0.0") != 0) free(ver); return 0; @@ -354,7 +355,7 @@ static void flatten_bin(const char *dir) { } int pdm_info(const char *pdmfile) { - printf("pdm: %s\n", base_name(pdmfile)); + pmm_info("%s\n", base_name(pdmfile)); /* Copy into the pm dir + chdir, then use relative names (works with both * MSYS GNU tar and System32 bsdtar). */ char home[1024]; @@ -373,7 +374,7 @@ int pdm_info(const char *pdmfile) { int pdm_install_file(const char *pdmfile) { if (!ends_with(pdmfile, ".pdm")) { - fprintf(stderr, "pdm: '%s' is not a .pdm file\n", pdmfile); + pmm_error("'%s' is not a .pdm file\n", pdmfile); return -1; } char home[1024], db[1024], root[1024], cmd[2600]; @@ -393,11 +394,11 @@ int pdm_install_file(const char *pdmfile) { char tmpname[1400], tmprel[] = "_pmm_install.pdm"; snprintf(tmpname, sizeof(tmpname), "%s/_pmm_install.pdm", home); if (copy_file(pdmfile, tmpname) != 0) { - fprintf(stderr, "pdm: cannot read %s\n", pdmfile); + pmm_error("cannot read %s\n", pdmfile); return -1; } if (chdir_save(home) != 0) { remove(tmpname); return -1; } - if (system(NULL) == 0) { fprintf(stderr, "pdm: cannot run tar\n"); chdir_restore(); remove(tmpname); return -1; } + if (system(NULL) == 0) { pmm_error("cannot run tar\n"); chdir_restore(); remove(tmpname); return -1; } const char *stage = "installed/.stage"; rmtree(stage); @@ -406,7 +407,7 @@ int pdm_install_file(const char *pdmfile) { /* extract members (all relative) */ snprintf(cmd, sizeof(cmd), "tar -xf \"%s\" -C \"%s\"", tmprel, stage); if (system(cmd) != 0) { - fprintf(stderr, "pdm: not a valid .pdm archive: %s\n", pdmfile); + pmm_error("not a valid .pdm archive: %s\n", pdmfile); chdir_restore(); remove(tmpname); return -1; } @@ -420,21 +421,21 @@ int pdm_install_file(const char *pdmfile) { char mp[1200]; snprintf(mp, sizeof(mp), "installed/.stage/%s", fname); if (pmm_sha256_file(mp, hex) != 0 || strcasecmp(hex, expect) != 0) { - fprintf(stderr, "pdm: CHECKSUM MISMATCH for %s in %s\n", fname, pdmfile); + pmm_error("CHECKSUM MISMATCH for %s in %s\n", fname, pdmfile); fclose(sf); chdir_restore(); remove(tmpname); return -1; } } fclose(sf); - printf("pdm: sha256 checksums OK\n"); + pmm_success("sha256 checksums OK\n"); } else { - printf("pdm: warning: no sha256sums member, skipping integrity check\n"); + pmm_warn("warning: no sha256sums member, skipping integrity check\n"); } /* read control */ snprintf(cmd, sizeof(cmd), "tar -xzOf \"%s/control.tar.gz\" pdm-control", stage); FILE *cf = PMM_POPEN_READ(cmd); if (!cf) { - fprintf(stderr, "pdm: missing control.tar.gz in %s\n", pdmfile); + pmm_error("missing control.tar.gz in %s\n", pdmfile); chdir_restore(); remove(tmpname); return -1; } char ctl[4096]; @@ -447,7 +448,7 @@ int pdm_install_file(const char *pdmfile) { char *pkg = control_get(ctl, "Package"); char *ver = control_get(ctl, "Version"); if (!pkg || !*pkg) { - fprintf(stderr, "pdm: package has no Package: field\n"); + pmm_error("package has no Package: field\n"); chdir_restore(); remove(tmpname); return -1; } @@ -455,7 +456,7 @@ int pdm_install_file(const char *pdmfile) { const char *tgt = flat ? "." : "root"; snprintf(cmd, sizeof(cmd), "tar -xzf \"%s/data.tar.gz\" -C \"%s\"", stage, tgt); if (system(cmd) != 0) { - fprintf(stderr, "pdm: data extraction failed\n"); + pmm_error("data extraction failed\n"); chdir_restore(); remove(tmpname); return -1; } @@ -486,7 +487,7 @@ int pdm_install_file(const char *pdmfile) { } PMM_PCLOSE_READ(tf); } else { - fprintf(stderr, "pdm: warning: could not record file list\n"); + pmm_warn("warning: could not record file list\n"); } fclose(dbf); } @@ -494,7 +495,7 @@ int pdm_install_file(const char *pdmfile) { rmtree(stage); chdir_restore(); remove(tmpname); - printf("pdm: installed %s %s into %s\n", pkg, ver ? ver : "0.0.0", root); + pmm_success("installed %s %s into %s\n", pkg, ver ? ver : "0.0.0", root); /* register in the Windows per-user "Installed Apps" (Uninstall) hive, * like an MSI installer would, so it shows in Settings > Installed apps */ @@ -553,7 +554,7 @@ int pdm_remove(const char *name) { long len = 0; char *info = read_file(ipath, &len); if (!info) { - fprintf(stderr, "pdm: package '%s' is not installed\n", name); + pmm_error("package '%s' is not installed\n", name); return -1; } char *files = strstr(info, "Files:\n"); @@ -581,7 +582,7 @@ int pdm_remove(const char *name) { } remove(ipath); free(info); - printf("pdm: removed %s (%d files)\n", name, n); + pmm_success("removed %s (%d files)\n", name, n); pmm_reg_uninstall_clear(name); /* drop it from Installed Apps */ return 0; } diff --git a/src/pmm.c b/src/pmm.c index b31b8fb..5c1a4c3 100644 --- a/src/pmm.c +++ b/src/pmm.c @@ -1,5 +1,6 @@ /* pmm.c - platform & config-dir helpers */ #include "pmm.h" +#include "out.h" #include #include #include @@ -129,7 +130,7 @@ void pmm_set_install_drive(const char *letter) { mkdir_p(dir); FILE *f = fopen(state, "w"); if (f) { fprintf(f, "%c", c); fclose(f); } - printf("pmm: install drive set to %c:\n", c); + pmm_success("install drive set to %c:\n", c); } void pmm_set_install_path(const char *path) { @@ -139,7 +140,7 @@ void pmm_set_install_path(const char *path) { size_t l = strlen(g_base_path); while (l > 1 && (g_base_path[l-1] == '/' || g_base_path[l-1] == '\\')) g_base_path[--l] = '\0'; mkdir_p(g_base_path); - printf("pmm: install path set to %s\n", g_base_path); + pmm_success("install path set to %s\n", g_base_path); } int pmm_flat_mode(void) { return g_base_path[0] ? 1 : 0; } @@ -407,9 +408,9 @@ void pmm_add_to_path(void) { if (room <= 0) break; /* no space left: stop */ snprintf(next + l, room + 1, "%s", list[i]); added++; - printf("pmm: adding to PATH: %s\n", list[i]); + pmm_info("adding to PATH: %s\n", list[i]); } - if (!added) { printf("pmm: PATH already contains PMM dirs\n"); return; } + if (!added) { pmm_info("PATH already contains PMM dirs\n"); return; } #ifdef _WIN32 { @@ -417,7 +418,7 @@ void pmm_add_to_path(void) { /* drive letters and ';' are fine for setx; quote the value */ snprintf(cmd, sizeof(cmd), "setx Path \"%s\" >nul 2>&1", next); system(cmd); - printf("pmm: PATH updated for new shells (reopen a terminal or run: setx) (user-level)\n"); + pmm_success("PATH updated for new shells (reopen a terminal or run: setx) (user-level)\n"); } #else { @@ -432,7 +433,7 @@ void pmm_add_to_path(void) { fclose(f); } (void)she; - printf("pmm: updated %s (reload your shell)\n", rcp); + pmm_success("updated %s (reload your shell)\n", rcp); } #endif } @@ -513,9 +514,9 @@ int pmm_reg_uninstall(const char *pkg, const char *ver, const char *pub, int rc = system(cmd); remove(ps1); if (rc != 0) - fprintf(stderr, "pmm: warning: could not register '%s' in Installed Apps\n", pkg); + pmm_warn("warning: could not register '%s' in Installed Apps\n", pkg); else - printf("pmm: registered '%s' %s in Installed Apps (uninstall via pmm remove %s)\n", + pmm_success("registered '%s' %s in Installed Apps (uninstall via pmm remove %s)\n", pkg, ver ? ver : "", pkg); return rc == 0 ? 0 : -1; #endif @@ -531,6 +532,6 @@ void pmm_reg_uninstall_clear(const char *pkg) { "%%SystemRoot%%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -Command \"Remove-Item -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s' -Recurse -Force -ErrorAction SilentlyContinue\" 2>nul", pkg); system(cmd); - printf("pmm: removed '%s' from Installed Apps\n", pkg); + pmm_success("removed '%s' from Installed Apps\n", pkg); #endif }