zpmod  b19981f
High-performance Zsh module for script optimization and filesystem helpers
zpmod_builtin.c
Go to the documentation of this file.
1 /* SPDX-License-Identifier: MIT */
2 /* Canonical module header ordering */
3 #include "zpmod.mdh"
4 #include "zpmod.pro"
5 #include "zpmod_vendor_shims.h"
6 /* System headers after gateway */
7 #include "zpmod_bundle.h"
8 #include "zpmod_compaudit.h"
9 #include "zpmod_emoji.h"
10 #include "zpmod_fs.h"
11 #include "zpmod_rehash.h"
12 #include "zpmod_source.h"
13 #include "zpmod_utils.h"
14 #include <limits.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 
19 /* Parse escaped delimiter specifications like \n, \t, \0, \r */
20 static int parse_delim(const char *a) {
21  if (!a || !*a) {
22  return '\n';
23  }
24  if (a[0] == '\\') {
25  switch (a[1]) {
26  case 'n':
27  return '\n';
28  case 't':
29  return '\t';
30  case '0':
31  return '\0';
32  case 'r':
33  return '\r';
34  default:
35  return (unsigned char)a[1];
36  }
37  }
38  return (unsigned char)a[0];
39 }
40 
41 /*
42  * Append text to $ZI_REPORTS[<plugin>].
43  * Notes:
44  * - Uses zsh memory allocators (zalloc/zsfree)
45  * - Emits user-facing diagnostics via zwarnnam (emoji inside function)
46  */
47 // NOLINTBEGIN(bugprone-easily-swappable-parameters)
48 static int zp_append_report(const char *nam /* reporting name */,
49  const char *target, const char *body,
50  int body_len) {
51  if (!body_len) {
52  return 0; /* noop */
53  }
54 
55  /* Lookup associative array parameter */
56  Param pm = (Param)paramtab->getnode(paramtab, "ZI_REPORTS");
57  if (!pm) {
58  zwarnnam(nam, "%s$ZI_REPORTS is not declared (zpmod not loaded?).",
59  zp_icon("❌ "));
60  return 1;
61  }
62  HashTable ht = pm->u.hash;
63  HashNode hn = gethashnode2(ht, target);
64  Param val_pm = (Param)hn;
65  if (!val_pm) {
66  zwarnnam(nam, "%sunknown plugin: %s", zp_icon("❌ "), target);
67  return 1;
68  }
69 
70  char *target_string = val_pm->u.str;
71  const int target_len = target_string ? (int)strlen(target_string) : 0;
72  const int new_len = target_len + body_len;
73 
74  char *newbuf = (char *)zalloc((new_len + 1) * sizeof(char));
75  if (!newbuf) {
76  zwarnnam(nam, "%sout of memory", zp_icon("❌ "));
77  return 1;
78  }
79  if (target_len) {
80  memcpy(newbuf, target_string, (size_t)target_len);
81  }
82  if (body_len) {
83  memcpy(newbuf + target_len, body, (size_t)body_len);
84  }
85  newbuf[new_len] = '\0';
86 
87  if (val_pm->u.str) {
88  zsfree(val_pm->u.str);
89  }
90  val_pm->u.str = newbuf;
91  return 0;
92 }
93 // NOLINTEND(bugprone-easily-swappable-parameters)
94 
96 void zpmod_usage(void) {
97  /* Section headings use emojis; lines use a consistent indent and columns for
98  * alignment. */
99  fprintf(stdout, "%s Usage:\n", zp_icon("📘 "));
100  fprintf(stdout, " zpmod [--help|-h] [--version|-V]\n");
101  fprintf(stdout, " zpmod report-append <plugin-id> <text>\n");
102  fprintf(stdout, " zpmod source-study [-l]\n");
103  fprintf(stdout, " zpmod dir-list [-a] [-d|-f] out_array dir\n");
104  fprintf(stdout, " zpmod path-stat [-L] [-f fields] out_array in_array\n");
105  fprintf(stdout, " zpmod path-warmup [-q] [--prune-missing] [--dry-run]\n");
106  fprintf(stdout, " zpmod compaudit-cache [--rebuild] [--show] [--json]\n");
107  fprintf(stdout, " zpmod rehash-diff\n");
108  fprintf(stdout, " zpmod bundle-build --from DIR --out FILE [--max KB]\n");
109  fprintf(stdout, " zpmod read-file [-m] [-d delim|-0] var file\n\n");
110 
111  fprintf(stdout, "%s Subcommands:\n", zp_icon("🧰 "));
112  /* Fixed-width label column for readability (max subcommand length ~12). */
113  fprintf(stdout, " %-14s %s\n", "report-append",
114  "Append <text> to $ZI_REPORTS[<plugin-id>].");
115  fprintf(stdout, " %-14s %s\n", "source-study",
116  "Show sourced files with durations (ms).");
117  fprintf(stdout, " %-14s %s\n", "dir-list",
118  "List entries in directory into array.");
119  fprintf(stdout, " %-14s %s\n", "path-stat",
120  "Batch stat for input array into output array.");
121  fprintf(stdout, " %-14s %s\n", "path-warmup",
122  "Touch PATH dirs to warm kernel VFS caches.");
123  fprintf(stdout, " %-14s %s\n\n", "read-file",
124  "Read file into scalar or split into array.");
125 
126  fprintf(stdout, "%s Options:\n", zp_icon("⚙️ "));
127  fflush(stdout);
128 }
129 
130 /* Subcommand helpers to keep bin_zpmod simple */
131 static int cmd_report_append(char *nam, char **argv) {
132  char *target = NULL;
133  char *body = NULL;
134  int target_len = 0;
135  int body_len = 0;
136  target = *argv++;
137  if (!target) {
138  zwarnnam(
139  nam,
140  "report-append: missing plugin ID (e.g., z-shell/zbrowse). See -h.");
141  return 1;
142  }
143  target = zp_unmetafy_zalloc(target, &target_len);
144  if (!target) {
145  zwarnnam(nam, "out of memory");
146  return 1;
147  }
148  body = *argv++;
149  if (!body) {
150  zwarnnam(nam, "report-append: missing text to append. See -h.");
151  zfree(target, target_len);
152  return 1;
153  }
154  body_len = (int)strlen(body);
155  {
156  int rc = zp_append_report(nam, target, body, body_len);
157  zfree(target, target_len);
158  return rc;
159  }
160 }
161 
162 int bin_zpmod(char *nam, char **argv, Options ops, int func) {
163  (void)func; /* unused */
164  char *subcmd = NULL;
165  int ret = 0;
166  if (OPT_ISSET(ops, 'V') ||
167  (argv && argv[0] &&
168  (!strcmp(argv[0], "--version") || !strcmp(argv[0], "-V")))) {
169  fprintf(stdout, "%szpmod %s (git: %s)\n", zp_icon("🧩 "), ZPMOD_VERSION_STR,
170  ZPMOD_GIT_DESCRIBE_STR);
171  fflush(stdout);
172  return 0;
173  }
174  if (OPT_ISSET(ops, 'h')) {
175  zpmod_usage();
176  return 0;
177  }
178  if (!*argv) {
179  zwarnnam(nam, "missing subcommand. See -h.");
180  return 1;
181  }
182  subcmd = *argv++;
183  if (0 == strcmp(subcmd, "report-append")) {
184  ret = cmd_report_append(nam, argv);
185  } else if (0 == strcmp(subcmd, "source-study")) {
186  ret = cmd_source_study(nam, argv);
187  } else if (0 == strcmp(subcmd, "source-hot")) {
188  ret = cmd_source_hot(nam, argv);
189  } else if (0 == strcmp(subcmd, "path-warmup")) {
190  int quiet = 0;
191  int prune_missing = 0;
192  int dry_run = 0;
193  while (*argv && argv[0][0] == '-') {
194  if (strcmp(argv[0], "-q") == 0 || strcmp(argv[0], "--quiet") == 0) {
195  quiet = 1;
196  argv++;
197  continue;
198  }
199  if (strcmp(argv[0], "--prune-missing") == 0) {
200  prune_missing = 1;
201  argv++;
202  continue;
203  }
204  if (strcmp(argv[0], "--dry-run") == 0) {
205  dry_run = 1;
206  argv++;
207  continue;
208  }
209  if (strcmp(argv[0], "--") == 0) {
210  argv++;
211  break;
212  }
213  break;
214  }
215  (void)argv; /* no trailing args */
216  int n = zp_path_warmup_core(nam, quiet, prune_missing, dry_run);
217  (void)n; /* could print if not quiet */
218  ret = 0;
219  } else if (0 == strcmp(subcmd, "fpath-index")) {
220  ret = cmd_fpath_index(nam, argv);
221  } else if (0 == strcmp(subcmd, "compaudit-cache")) {
222  int rebuild = 0;
223  int show = 0;
224  int json = 0;
225  while (*argv && argv[0][0] == '-') {
226  if (!strcmp(argv[0], "--rebuild")) {
227  rebuild = 1;
228  argv++;
229  continue;
230  }
231  if (!strcmp(argv[0], "--show")) {
232  show = 1;
233  argv++;
234  continue;
235  }
236  if (!strcmp(argv[0], "--json")) {
237  json = 1;
238  argv++;
239  continue;
240  }
241  if (!strcmp(argv[0], "--")) {
242  argv++;
243  break;
244  }
245  break;
246  }
247  ret = zp_compaudit_cache_core(nam, rebuild, show, json);
248  } else if (0 == strcmp(subcmd, "rehash-diff")) {
249  ret = zp_rehash_diff_core(nam);
250  } else if (0 == strcmp(subcmd, "bundle-build")) {
251  const char *from_dir = NULL;
252  const char *out_path = NULL;
253  long max_kb = 0;
254  while (*argv) {
255  if (!strcmp(argv[0], "--from")) {
256  if (argv[1]) {
257  from_dir = argv[1];
258  argv += 2;
259  continue;
260  }
261  zwarnnam(nam, "bundle-build: --from requires value");
262  ret = 1;
263  break;
264  }
265  if (!strcmp(argv[0], "--out")) {
266  if (argv[1]) {
267  out_path = argv[1];
268  argv += 2;
269  continue;
270  }
271  zwarnnam(nam, "bundle-build: --out requires value");
272  ret = 1;
273  break;
274  }
275  if (!strcmp(argv[0], "--max")) {
276  if (argv[1]) {
277  char *end = NULL;
278  long v = strtol(argv[1], &end, 10);
279  if (*end == '\0' && v >= 0) {
280  max_kb = v;
281  } else {
282  zwarnnam(nam, "bundle-build: invalid --max value: %s", argv[1]);
283  ret = 1;
284  }
285  argv += 2;
286  continue;
287  }
288  zwarnnam(nam, "bundle-build: --max requires value");
289  ret = 1;
290  break;
291  }
292  if (!strcmp(argv[0], "--")) {
293  argv++;
294  break;
295  }
296  break;
297  }
298  if (ret == 0) {
299  ret = zp_bundle_build_core(nam, from_dir, out_path, max_kb);
300  }
301  } else if (0 == strcmp(subcmd, "dir-list")) {
302  ret = cmd_dirlist(nam, argv);
303  } else if (0 == strcmp(subcmd, "path-stat")) {
304  ret = cmd_pathstat(nam, argv);
305  } else if (0 == strcmp(subcmd, "read-file")) {
306  ret = cmd_readfile(nam, argv);
307  } else {
308  zwarnnam(nam, "unknown subcommand: %s. See -h.", subcmd);
309  }
310  return ret;
311 }
312 
313 int cmd_source_study(char *nam, char **argv) {
314  int report_count = 10;
315  int threshold_ms = 0;
316  int clear_history = 0;
317  while (*argv) {
318  if (strcmp(argv[0], "--") == 0) {
319  argv++;
320  break;
321  }
322  if (strcmp(argv[0], "-l") == 0) {
323  clear_history = 1;
324  argv++;
325  continue;
326  }
327  if (argv[0][0] == '-') {
328  zwarnnam(nam, "source-study: unknown option: %s", argv[0]);
329  return 1;
330  }
331  char *endptr = NULL;
332  long val = strtol(argv[0], &endptr, 10);
333  if (*endptr == '\0' && val >= 0 && val <= INT_MAX) {
334  report_count = (int)val;
335  argv++;
336  } else {
337  zwarnnam(nam, "source-study: invalid report count: %s", argv[0]);
338  return 1;
339  }
340  }
341  if (!zp_source_study_core) {
342  /* Weak symbol not present: treat as gracefully unavailable */
343  return 0; /* success (no data) */
344  }
345  return zp_source_study_core(nam, report_count, threshold_ms, clear_history);
346 }
347 
348 int cmd_dirlist(char *nam, char **argv) {
349  int inc_all = 0;
350  int only_dirs = 0;
351  int only_files = 0;
352  while (*argv && argv[0][0] == '-') {
353  if (!strcmp(argv[0], "-a")) {
354  inc_all = 1;
355  argv++;
356  continue;
357  }
358  if (!strcmp(argv[0], "-d")) {
359  only_dirs = 1;
360  argv++;
361  continue;
362  }
363  if (!strcmp(argv[0], "-f")) {
364  only_files = 1;
365  argv++;
366  continue;
367  }
368  if (!strcmp(argv[0], "--")) {
369  argv++;
370  break;
371  }
372  break;
373  }
374  if (!argv[0] || !argv[1]) {
375  zwarnnam(nam, "dir-list: usage: zpmod dir-list [-a] [-d|-f] out_array dir");
376  return 1;
377  }
378  return zp_dirlist_core(nam, argv[0], argv[1], inc_all, only_dirs, only_files);
379 }
380 
381 int cmd_pathstat(char *nam, char **argv) {
382  int follow = 0;
383  char *fields = NULL;
384  while (*argv && argv[0][0] == '-' && argv[0][1]) {
385  if (strcmp(argv[0], "--") == 0) {
386  argv++;
387  break;
388  }
389  if (strcmp(argv[0], "-L") == 0) {
390  follow = 1;
391  argv++;
392  continue;
393  }
394  if (argv[0][1] == 'f') {
395  char **cursor = &argv[0];
396  int tk = zp_take_opt_with_arg(&cursor, 'f', &fields);
397  if (tk == -1) {
398  zwarnnam(nam, "%spathstat: -f requires fields", zp_icon("❌ "));
399  return 1;
400  }
401  if (tk == 1) {
402  argv = cursor;
403  continue;
404  }
405  }
406  break;
407  }
408  if (!argv[0] || !argv[1]) {
409  zwarnnam(nam,
410  "%spathstat: usage: zpmod path-stat [-L] [-f fields] out_array "
411  "in_array",
412  zp_icon("❌ "));
413  return 1;
414  }
415  return zp_pathstat_core(nam, argv[0], argv[1], follow, fields);
416 }
417 
418 int cmd_readfile(char *nam, char **argv) {
419  int use_mmap = 0;
420  int split = 0;
421  int delim = '\n';
422  while (*argv && argv[0][0] == '-') {
423  if (strcmp(argv[0], "--mmap") == 0) {
424  use_mmap = 1;
425  argv++;
426  continue;
427  }
428  if (strcmp(argv[0], "-0") == 0) {
429  split = 1;
430  delim = '\0';
431  argv++;
432  continue;
433  }
434  if (strcmp(argv[0], "--") == 0) {
435  argv++;
436  break;
437  }
438  if (argv[0][1] == 'd') {
439  /* Allow forms: -d X or -dX */
440  if (argv[0][2]) {
441  const char *a = argv[0] + 2;
442  split = 1;
443  if (a[0] == '\\' && a[1]) {
444  switch (a[1]) {
445  case 'n':
446  delim = '\n';
447  break;
448  case 't':
449  delim = '\t';
450  break;
451  case '0':
452  delim = '\0';
453  break;
454  case 'r':
455  delim = '\r';
456  break;
457  default:
458  delim = (unsigned char)a[1];
459  break;
460  }
461  } else {
462  delim = (unsigned char)a[0];
463  }
464  argv++;
465  continue;
466  }
467  if (argv[1]) {
468  const char *a = argv[1];
469  split = 1;
470  if (a[0] == '\\') {
471  if (a[1]) {
472  switch (a[1]) {
473  case 'n':
474  delim = '\n';
475  break;
476  case 't':
477  delim = '\t';
478  break;
479  case '0':
480  delim = '\0';
481  break;
482  case 'r':
483  delim = '\r';
484  break;
485  default:
486  delim = (unsigned char)a[1];
487  break;
488  }
489  } else {
490  delim = '\\';
491  }
492  } else {
493  delim = (unsigned char)a[0];
494  }
495  argv += 2; /* consume -d and arg */
496  continue;
497  }
498  zwarnnam(nam, "read-file: -d requires delimiter");
499  return 1;
500  }
501  break; /* unknown option */
502  }
503  if (!argv[0] || !argv[1]) {
504  zwarnnam(
505  nam,
506  "read-file: usage: zpmod read-file [-m|--mmap] [-0|-d delim] var file");
507  return 1;
508  }
509  return zp_readfile_core(nam, argv[0], argv[1], use_mmap, split, delim);
510 }
511 
512 /* Expose builtins table from this TU */
513 static struct builtin self_builtins[] = {
514  BUILTIN("zpmod", 0, bin_zpmod, 0, -1, 0, "hV", NULL),
515 };
516 
517 struct builtin *zp_get_self_builtins(size_t *count) {
518  if (count) {
519  *count = sizeof(self_builtins) / sizeof(*self_builtins);
520  }
521  return self_builtins;
522 }
int zp_compaudit_cache_core(char *nam, int rebuild, int show, int json)
const char * zp_icon(const char *s)
Return icon string if enabled, empty string otherwise.
Definition: emoji.c:58
int cmd_fpath_index(char *nam, char **argv)
Implements zpmod fpath-index with intelligent skip detection.
Definition: fpath.c:57
int zp_path_warmup_core(const char *nam, int quiet, int prune_missing, int dry_run)
Implements path-warmup functionality for executable discovery and path pruning.
Definition: fs.c:254
int zp_readfile_core(char *nam, char *outname, char *path, int use_mmap, int split, int delim)
See zpmod_fs.h for contract.
Definition: fs.c:444
int zp_dirlist_core(char *nam, char *outname, char *dir, int inc_all, int only_dirs, int only_files)
See zpmod_fs.h for contract.
Definition: fs.c:382
int zp_pathstat_core(char *nam, char *outname, char *inname, int follow, char *fields)
See zpmod_fs.h for contract.
Definition: fs.c:36
int zp_rehash_diff_core(char *nam)
Definition: rehash_diff.c:241
int cmd_source_hot(char *nam, char **argv)
Implements zpmod source-hot.
Definition: source_hot.c:37
int zp_source_study_core(const char *nam, int report_count, int threshold_ms, int clear_history)
int zp_take_opt_with_arg(char ***argvp, char opt, char **out_arg)
Consume a short option with required argument from argv.
Definition: utils.c:35
char * zp_unmetafy_zalloc(const char *to_copy, int *new_len)
Duplicate and unmetafy a zsh string with zalloc; see header for details.
Definition: utils.c:76
Module declaration header (mdh) for zpmod.
Prototype stub for zpmod when building out-of-tree.
int cmd_dirlist(char *nam, char **argv)
static int zp_append_report(const char *nam, const char *target, const char *body, int body_len)
Definition: zpmod_builtin.c:48
int bin_zpmod(char *nam, char **argv, Options ops, int func)
static struct builtin self_builtins[]
static int cmd_report_append(char *nam, char **argv)
struct builtin * zp_get_self_builtins(size_t *count)
int cmd_source_study(char *nam, char **argv)
static int parse_delim(const char *a)
Definition: zpmod_builtin.c:20
void zpmod_usage(void)
Print usage for the zpmod builtin.
Definition: zpmod_builtin.c:96
int cmd_readfile(char *nam, char **argv)
int cmd_pathstat(char *nam, char **argv)
Startup bundle builder interface.
int zp_bundle_build_core(char *nam, const char *from_dir, const char *out_path, long max_kb)
Interface for cached compaudit security verdicts.
Optional terminal/locale detection for emoji support in messages.
Filesystem helpers used by builtins and zpmod subcommands.
void * zalloc(size_t size)
void zsfree(char *ptr)
void zfree(void *ptr, size_t size)
HashTable paramtab
void zwarnnam(const char *, const char *,...)
struct hashtable * HashTable
HashNode gethashnode2(HashTable, const char *)
struct hashnode * HashNode
Incremental PATH diff rehash entrypoint.
Public interfaces for source-study and source overrides.
Utility helpers shared across module components.
Local, non-invasive shims to suppress benign vendor header warnings.