@take-out/scripts 0.1.37 → 0.1.38-1772433507984

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,497 @@
1
+ // run-group: run commands with proper process group management
2
+ //
3
+ // usage:
4
+ // run-group [flags] <command> [args...]
5
+ // run-group -p [flags] <cmd1> --- <cmd2> --- ...
6
+ //
7
+ // flags:
8
+ // -p, --parallel run commands in parallel (separated by ---)
9
+ // -k, --keep-going don't kill others on failure
10
+ // -t, --timing show timing info
11
+ // -n, --name <name> set display name (for single command)
12
+ // -q, --quiet suppress command output
13
+ //
14
+ // in parallel mode, names are taken from first arg of each command
15
+ //
16
+ // compile: clang -O2 -o run-group run-group.c
17
+
18
+ #include <stdio.h>
19
+ #include <stdlib.h>
20
+ #include <string.h>
21
+ #include <unistd.h>
22
+ #include <signal.h>
23
+ #include <sys/wait.h>
24
+ #include <sys/time.h>
25
+ #include <sys/select.h>
26
+ #include <errno.h>
27
+ #include <fcntl.h>
28
+
29
+ #define MAX_CHILDREN 64
30
+ #define PIPE_BUF_SIZE 4096
31
+
32
+ // colors
33
+ #define RESET "\x1b[0m"
34
+ #define BOLD "\x1b[1m"
35
+ #define DIM "\x1b[2m"
36
+ #define GREEN "\x1b[32m"
37
+ #define RED "\x1b[31m"
38
+ #define YELLOW "\x1b[33m"
39
+
40
+ static const char *prefix_colors[] = {
41
+ "\x1b[36m", // cyan
42
+ "\x1b[35m", // magenta
43
+ "\x1b[32m", // green
44
+ "\x1b[33m", // yellow
45
+ "\x1b[34m", // blue
46
+ "\x1b[31m", // red
47
+ };
48
+ #define NUM_COLORS 6
49
+
50
+ typedef struct {
51
+ pid_t pid;
52
+ char *name;
53
+ int color_idx;
54
+ int stdout_fd;
55
+ int stderr_fd;
56
+ struct timeval start;
57
+ char stdout_buf[PIPE_BUF_SIZE];
58
+ char stderr_buf[PIPE_BUF_SIZE];
59
+ int stdout_len;
60
+ int stderr_len;
61
+ } child_t;
62
+
63
+ static child_t children[MAX_CHILDREN];
64
+ static int num_children = 0;
65
+ static int keep_going = 0;
66
+ static int show_timing = 0;
67
+ static int quiet = 0;
68
+ static volatile sig_atomic_t shutting_down = 0;
69
+
70
+ static void kill_all_children(void) {
71
+ for (int i = 0; i < num_children; i++) {
72
+ if (children[i].pid > 0) {
73
+ kill(-children[i].pid, SIGTERM);
74
+ }
75
+ }
76
+ usleep(100000); // 100ms
77
+ for (int i = 0; i < num_children; i++) {
78
+ if (children[i].pid > 0) {
79
+ kill(-children[i].pid, SIGKILL);
80
+ }
81
+ }
82
+ }
83
+
84
+ static void signal_handler(int sig) {
85
+ shutting_down = 1;
86
+ kill_all_children();
87
+ signal(sig, SIG_DFL);
88
+ raise(sig);
89
+ }
90
+
91
+ static long elapsed_ms(struct timeval *start) {
92
+ struct timeval now;
93
+ gettimeofday(&now, NULL);
94
+ return (now.tv_sec - start->tv_sec) * 1000 + (now.tv_usec - start->tv_usec) / 1000;
95
+ }
96
+
97
+ static void print_duration(long ms) {
98
+ if (ms >= 60000) {
99
+ printf("%ldm %lds", ms / 60000, (ms % 60000) / 1000);
100
+ } else if (ms >= 1000) {
101
+ printf("%ld.%lds", ms / 1000, (ms % 1000) / 100);
102
+ } else {
103
+ printf("%ldms", ms);
104
+ }
105
+ }
106
+
107
+ static void print_prefixed_line(int color_idx, const char *name, const char *line, int is_stderr) {
108
+ const char *color = prefix_colors[color_idx % NUM_COLORS];
109
+ FILE *out = is_stderr ? stderr : stdout;
110
+ fprintf(out, "%s[%s]%s %s\n", color, name, RESET, line);
111
+ fflush(out);
112
+ }
113
+
114
+ static void flush_buffer(child_t *child, char *buf, int *len, int is_stderr) {
115
+ if (*len == 0) return;
116
+
117
+ char *start = buf;
118
+ char *end;
119
+
120
+ while ((end = memchr(start, '\n', *len - (start - buf))) != NULL) {
121
+ *end = '\0';
122
+ if (start[0] != '\0') { // skip empty lines
123
+ print_prefixed_line(child->color_idx, child->name, start, is_stderr);
124
+ }
125
+ start = end + 1;
126
+ }
127
+
128
+ // move remaining partial line to start of buffer
129
+ int remaining = *len - (start - buf);
130
+ if (remaining > 0 && start != buf) {
131
+ memmove(buf, start, remaining);
132
+ }
133
+ *len = remaining;
134
+ }
135
+
136
+ static void read_child_output(child_t *child) {
137
+ char tmp[1024];
138
+ ssize_t n;
139
+
140
+ // read stdout
141
+ while ((n = read(child->stdout_fd, tmp, sizeof(tmp))) > 0) {
142
+ int space = PIPE_BUF_SIZE - child->stdout_len - 1;
143
+ int to_copy = n < space ? n : space;
144
+ memcpy(child->stdout_buf + child->stdout_len, tmp, to_copy);
145
+ child->stdout_len += to_copy;
146
+ child->stdout_buf[child->stdout_len] = '\0';
147
+ flush_buffer(child, child->stdout_buf, &child->stdout_len, 0);
148
+ }
149
+
150
+ // read stderr
151
+ while ((n = read(child->stderr_fd, tmp, sizeof(tmp))) > 0) {
152
+ int space = PIPE_BUF_SIZE - child->stderr_len - 1;
153
+ int to_copy = n < space ? n : space;
154
+ memcpy(child->stderr_buf + child->stderr_len, tmp, to_copy);
155
+ child->stderr_len += to_copy;
156
+ child->stderr_buf[child->stderr_len] = '\0';
157
+ flush_buffer(child, child->stderr_buf, &child->stderr_len, 1);
158
+ }
159
+ }
160
+
161
+ static int run_single(char **argv, const char *name, int color_idx) {
162
+ struct timeval start;
163
+ gettimeofday(&start, NULL);
164
+
165
+ int stdout_pipe[2], stderr_pipe[2];
166
+ if (!quiet) {
167
+ pipe(stdout_pipe);
168
+ pipe(stderr_pipe);
169
+ }
170
+
171
+ pid_t pid = fork();
172
+ if (pid < 0) {
173
+ perror("fork");
174
+ return 1;
175
+ }
176
+
177
+ if (pid == 0) {
178
+ setpgid(0, 0);
179
+ if (!quiet) {
180
+ close(stdout_pipe[0]);
181
+ close(stderr_pipe[0]);
182
+ dup2(stdout_pipe[1], STDOUT_FILENO);
183
+ dup2(stderr_pipe[1], STDERR_FILENO);
184
+ close(stdout_pipe[1]);
185
+ close(stderr_pipe[1]);
186
+ }
187
+ execvp(argv[0], argv);
188
+ perror("exec");
189
+ _exit(127);
190
+ }
191
+
192
+ setpgid(pid, pid);
193
+
194
+ // track for signal handler cleanup
195
+ children[0].pid = pid;
196
+ children[0].name = (char*)name;
197
+ num_children = 1;
198
+
199
+ if (!quiet) {
200
+ close(stdout_pipe[1]);
201
+ close(stderr_pipe[1]);
202
+ fcntl(stdout_pipe[0], F_SETFL, O_NONBLOCK);
203
+ fcntl(stderr_pipe[0], F_SETFL, O_NONBLOCK);
204
+
205
+ child_t child = {
206
+ .pid = pid,
207
+ .name = (char*)name,
208
+ .color_idx = color_idx,
209
+ .stdout_fd = stdout_pipe[0],
210
+ .stderr_fd = stderr_pipe[0],
211
+ .stdout_len = 0,
212
+ .stderr_len = 0,
213
+ };
214
+
215
+ int status;
216
+ while (1) {
217
+ read_child_output(&child);
218
+
219
+ int result = waitpid(pid, &status, WNOHANG);
220
+ if (result == pid) break;
221
+ if (result < 0) break;
222
+
223
+ usleep(10000); // 10ms
224
+ }
225
+
226
+ // final read
227
+ read_child_output(&child);
228
+
229
+ // flush remaining
230
+ if (child.stdout_len > 0) {
231
+ child.stdout_buf[child.stdout_len] = '\0';
232
+ print_prefixed_line(color_idx, name, child.stdout_buf, 0);
233
+ }
234
+ if (child.stderr_len > 0) {
235
+ child.stderr_buf[child.stderr_len] = '\0';
236
+ print_prefixed_line(color_idx, name, child.stderr_buf, 1);
237
+ }
238
+
239
+ close(stdout_pipe[0]);
240
+ close(stderr_pipe[0]);
241
+
242
+ kill(-pid, SIGTERM);
243
+
244
+ int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) :
245
+ WIFSIGNALED(status) ? 128 + WTERMSIG(status) : 1;
246
+
247
+ if (show_timing) {
248
+ long ms = elapsed_ms(&start);
249
+ if (exit_code == 0) {
250
+ printf(GREEN "✓" RESET " " BOLD "%s" RESET " completed in " YELLOW, name);
251
+ } else {
252
+ printf(RED "✗" RESET " " BOLD "%s" RESET " failed after " YELLOW, name);
253
+ }
254
+ print_duration(ms);
255
+ printf(RESET "\n");
256
+ }
257
+
258
+ return exit_code;
259
+ } else {
260
+ int status;
261
+ waitpid(pid, &status, 0);
262
+ kill(-pid, SIGTERM);
263
+ return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
264
+ }
265
+ }
266
+
267
+ static void spawn_child(char **argv, const char *name, int color_idx) {
268
+ if (num_children >= MAX_CHILDREN) {
269
+ fprintf(stderr, "too many children\n");
270
+ exit(1);
271
+ }
272
+
273
+ int stdout_pipe[2], stderr_pipe[2];
274
+ if (!quiet) {
275
+ pipe(stdout_pipe);
276
+ pipe(stderr_pipe);
277
+ }
278
+
279
+ pid_t pid = fork();
280
+ if (pid < 0) {
281
+ perror("fork");
282
+ exit(1);
283
+ }
284
+
285
+ if (pid == 0) {
286
+ setpgid(0, 0);
287
+ if (!quiet) {
288
+ close(stdout_pipe[0]);
289
+ close(stderr_pipe[0]);
290
+ dup2(stdout_pipe[1], STDOUT_FILENO);
291
+ dup2(stderr_pipe[1], STDERR_FILENO);
292
+ close(stdout_pipe[1]);
293
+ close(stderr_pipe[1]);
294
+ }
295
+ execvp(argv[0], argv);
296
+ perror("exec");
297
+ _exit(127);
298
+ }
299
+
300
+ setpgid(pid, pid);
301
+
302
+ child_t *child = &children[num_children];
303
+ child->pid = pid;
304
+ child->name = strdup(name);
305
+ child->color_idx = color_idx;
306
+ gettimeofday(&child->start, NULL);
307
+ child->stdout_len = 0;
308
+ child->stderr_len = 0;
309
+
310
+ if (!quiet) {
311
+ close(stdout_pipe[1]);
312
+ close(stderr_pipe[1]);
313
+ fcntl(stdout_pipe[0], F_SETFL, O_NONBLOCK);
314
+ fcntl(stderr_pipe[0], F_SETFL, O_NONBLOCK);
315
+ child->stdout_fd = stdout_pipe[0];
316
+ child->stderr_fd = stderr_pipe[0];
317
+ }
318
+
319
+ num_children++;
320
+ }
321
+
322
+ static int wait_for_children(void) {
323
+ int max_exit = 0;
324
+ int remaining = num_children;
325
+
326
+ while (remaining > 0 && !shutting_down) {
327
+ // read from all children
328
+ if (!quiet) {
329
+ for (int i = 0; i < num_children; i++) {
330
+ if (children[i].pid > 0) {
331
+ read_child_output(&children[i]);
332
+ }
333
+ }
334
+ }
335
+
336
+ int status;
337
+ pid_t pid = waitpid(-1, &status, WNOHANG);
338
+
339
+ if (pid <= 0) {
340
+ usleep(10000); // 10ms
341
+ continue;
342
+ }
343
+
344
+ for (int i = 0; i < num_children; i++) {
345
+ if (children[i].pid == pid) {
346
+ // final read
347
+ if (!quiet) {
348
+ read_child_output(&children[i]);
349
+
350
+ // flush remaining
351
+ if (children[i].stdout_len > 0) {
352
+ children[i].stdout_buf[children[i].stdout_len] = '\0';
353
+ print_prefixed_line(children[i].color_idx, children[i].name,
354
+ children[i].stdout_buf, 0);
355
+ }
356
+ if (children[i].stderr_len > 0) {
357
+ children[i].stderr_buf[children[i].stderr_len] = '\0';
358
+ print_prefixed_line(children[i].color_idx, children[i].name,
359
+ children[i].stderr_buf, 1);
360
+ }
361
+
362
+ close(children[i].stdout_fd);
363
+ close(children[i].stderr_fd);
364
+ }
365
+
366
+ long ms = elapsed_ms(&children[i].start);
367
+ int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) :
368
+ WIFSIGNALED(status) ? 128 + WTERMSIG(status) : 1;
369
+
370
+ if (exit_code > max_exit) max_exit = exit_code;
371
+
372
+ if (show_timing) {
373
+ if (exit_code == 0) {
374
+ printf(GREEN "✓" RESET " " BOLD "%s" RESET " completed in " YELLOW,
375
+ children[i].name);
376
+ } else {
377
+ printf(RED "✗" RESET " " BOLD "%s" RESET " failed after " YELLOW,
378
+ children[i].name);
379
+ }
380
+ print_duration(ms);
381
+ printf(RESET "\n");
382
+
383
+ if (exit_code != 0 && !keep_going && !shutting_down) {
384
+ shutting_down = 1;
385
+ kill_all_children();
386
+ }
387
+ }
388
+
389
+ kill(-pid, SIGTERM);
390
+ children[i].pid = 0;
391
+ remaining--;
392
+ break;
393
+ }
394
+ }
395
+ }
396
+
397
+ return max_exit;
398
+ }
399
+
400
+ int main(int argc, char *argv[]) {
401
+ if (argc < 2) {
402
+ fprintf(stderr,
403
+ "usage: run-group [flags] <command> [args...]\n"
404
+ " run-group -p [flags] <cmd1> --- <cmd2> --- ...\n"
405
+ "\n"
406
+ "flags:\n"
407
+ " -p, --parallel run commands in parallel (separated by ---)\n"
408
+ " -k, --keep-going don't kill others on failure\n"
409
+ " -t, --timing show timing info\n"
410
+ " -n, --name <name> set display name\n"
411
+ " -q, --quiet suppress command output\n"
412
+ );
413
+ return 1;
414
+ }
415
+
416
+ signal(SIGINT, signal_handler);
417
+ signal(SIGTERM, signal_handler);
418
+ signal(SIGHUP, signal_handler);
419
+
420
+ int parallel = 0;
421
+ int cmd_start = 1;
422
+ const char *custom_name = NULL;
423
+
424
+ for (int i = 1; i < argc; i++) {
425
+ if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--parallel") == 0) {
426
+ parallel = 1;
427
+ cmd_start = i + 1;
428
+ } else if (strcmp(argv[i], "-k") == 0 || strcmp(argv[i], "--keep-going") == 0) {
429
+ keep_going = 1;
430
+ cmd_start = i + 1;
431
+ } else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--timing") == 0) {
432
+ show_timing = 1;
433
+ cmd_start = i + 1;
434
+ } else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) {
435
+ quiet = 1;
436
+ cmd_start = i + 1;
437
+ } else if ((strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--name") == 0) && i + 1 < argc) {
438
+ custom_name = argv[i + 1];
439
+ cmd_start = i + 2;
440
+ i++;
441
+ } else if (argv[i][0] != '-') {
442
+ break;
443
+ }
444
+ }
445
+
446
+ if (cmd_start >= argc) {
447
+ fprintf(stderr, "error: no command specified\n");
448
+ return 1;
449
+ }
450
+
451
+ if (parallel) {
452
+ // split on --- and run in parallel
453
+ // supports @name prefix for display names: @myname sh -c "cmd"
454
+ char **cmd_argv[MAX_CHILDREN];
455
+ char *cmd_names[MAX_CHILDREN];
456
+ int cmd_count = 0;
457
+ int cmd_argc = 0;
458
+ char *pending_name = NULL;
459
+ char **current_argv = malloc(sizeof(char*) * (argc + 1));
460
+
461
+ for (int i = cmd_start; i < argc; i++) {
462
+ if (strcmp(argv[i], "---") == 0) {
463
+ if (cmd_argc > 0) {
464
+ current_argv[cmd_argc] = NULL;
465
+ cmd_argv[cmd_count] = current_argv;
466
+ cmd_names[cmd_count] = pending_name ? pending_name : current_argv[0];
467
+ cmd_count++;
468
+ current_argv = malloc(sizeof(char*) * (argc + 1));
469
+ cmd_argc = 0;
470
+ pending_name = NULL;
471
+ }
472
+ } else if (cmd_argc == 0 && argv[i][0] == '@') {
473
+ // @name prefix - use as display name
474
+ pending_name = argv[i] + 1;
475
+ } else {
476
+ current_argv[cmd_argc++] = argv[i];
477
+ }
478
+ }
479
+ if (cmd_argc > 0) {
480
+ current_argv[cmd_argc] = NULL;
481
+ cmd_argv[cmd_count] = current_argv;
482
+ cmd_names[cmd_count] = pending_name ? pending_name : current_argv[0];
483
+ cmd_count++;
484
+ }
485
+
486
+ // spawn all
487
+ for (int i = 0; i < cmd_count; i++) {
488
+ spawn_child(cmd_argv[i], cmd_names[i], i);
489
+ }
490
+
491
+ return wait_for_children();
492
+ } else {
493
+ // single command
494
+ const char *name = custom_name ? custom_name : argv[cmd_start];
495
+ return run_single(argv + cmd_start, name, 0);
496
+ }
497
+ }