solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
flags.c
1
17#include "../include/flags.h"
18#include "../include/arena.h"
19#include "../include/str_utils.h"
20
21#define INITIAL_CAPACITY 64
22#define ERR_BUF_SIZE 128
23#define MAX_FLAG_NAME_LEN 128
24#define MAX_DEFAULT_STR 64
25
33struct Flag {
34 FlagDataType type;
35 char* name;
36 char short_name;
37 char* description;
38 void* value_ptr;
39 void* default_ptr;
40 bool required;
41 bool is_present;
42 FlagValidator validator;
43};
44
52struct FlagParser {
53 Arena* arena;
54 char* name;
55 char* description;
56 char* footer;
58 Flag* flags;
59 size_t flag_count;
60 size_t flag_capacity;
62 FlagParser** subcommands;
63 size_t cmd_count;
64 size_t cmd_capacity;
66 void (*handler)(void* user_data);
67 void (*pre_invoke)(void* user_data);
69 char** positional_args;
70 size_t pos_count;
71 size_t pos_capacity;
73 char last_error[ERR_BUF_SIZE];
74 FlagParser* active_subcommand;
75};
76
77// --- Memory Helpers ---
78
86static void* copy_default_value(Arena* arena, FlagDataType type, void* value_ptr) {
87 if (!value_ptr) return NULL;
88
89 switch (type) {
90 case TYPE_BOOL: {
91 bool* copy = ARENA_ALLOC(arena, bool);
92 if (copy) *copy = *(bool*)value_ptr;
93 return copy;
94 }
95 case TYPE_CHAR: {
96 char* copy = ARENA_ALLOC(arena, char);
97 if (copy) *copy = *(char*)value_ptr;
98 return copy;
99 }
100 case TYPE_STRING: {
101 char* str = *(char**)value_ptr;
102 return str ? arena_strdup(arena, str) : NULL;
103 }
104 case TYPE_INT8: {
105 int8_t* copy = ARENA_ALLOC(arena, int8_t);
106 if (copy) *copy = *(int8_t*)value_ptr;
107 return copy;
108 }
109 case TYPE_UINT8: {
110 uint8_t* copy = ARENA_ALLOC(arena, uint8_t);
111 if (copy) *copy = *(uint8_t*)value_ptr;
112 return copy;
113 }
114 case TYPE_INT16: {
115 int16_t* copy = ARENA_ALLOC(arena, int16_t);
116 if (copy) *copy = *(int16_t*)value_ptr;
117 return copy;
118 }
119 case TYPE_UINT16: {
120 uint16_t* copy = ARENA_ALLOC(arena, uint16_t);
121 if (copy) *copy = *(uint16_t*)value_ptr;
122 return copy;
123 }
124 case TYPE_INT32: {
125 int32_t* copy = ARENA_ALLOC(arena, int32_t);
126 if (copy) *copy = *(int32_t*)value_ptr;
127 return copy;
128 }
129 case TYPE_UINT32: {
130 uint32_t* copy = ARENA_ALLOC(arena, uint32_t);
131 if (copy) *copy = *(uint32_t*)value_ptr;
132 return copy;
133 }
134 case TYPE_INT64: {
135 int64_t* copy = ARENA_ALLOC(arena, int64_t);
136 if (copy) *copy = *(int64_t*)value_ptr;
137 return copy;
138 }
139 case TYPE_UINT64: {
140 uint64_t* copy = ARENA_ALLOC(arena, uint64_t);
141 if (copy) *copy = *(uint64_t*)value_ptr;
142 return copy;
143 }
144 case TYPE_SIZE_T: {
145 size_t* copy = ARENA_ALLOC(arena, size_t);
146 if (copy) *copy = *(size_t*)value_ptr;
147 return copy;
148 }
149 case TYPE_FLOAT: {
150 float* copy = ARENA_ALLOC(arena, float);
151 if (copy) *copy = *(float*)value_ptr;
152 return copy;
153 }
154 case TYPE_DOUBLE: {
155 double* copy = ARENA_ALLOC(arena, double);
156 if (copy) *copy = *(double*)value_ptr;
157 return copy;
158 }
159 default:
160 return NULL;
161 }
162}
163
171static void format_default_value(FlagDataType type, void* default_ptr, char* buf, size_t buf_size) {
172 if (!default_ptr || !buf || buf_size == 0) {
173 buf[0] = '\0';
174 return;
175 }
176
177 switch (type) {
178 case TYPE_BOOL:
179 snprintf(buf, buf_size, "%s", *(bool*)default_ptr ? "true" : "false");
180 break;
181 case TYPE_CHAR:
182 snprintf(buf, buf_size, "'%c'", *(char*)default_ptr);
183 break;
184 case TYPE_STRING: {
185 char* str = (char*)default_ptr;
186 snprintf(buf, buf_size, "\"%s\"", str ? str : "");
187 break;
188 }
189 case TYPE_INT8:
190 snprintf(buf, buf_size, "%d", *(int8_t*)default_ptr);
191 break;
192 case TYPE_UINT8:
193 snprintf(buf, buf_size, "%u", *(uint8_t*)default_ptr);
194 break;
195 case TYPE_INT16:
196 snprintf(buf, buf_size, "%d", *(int16_t*)default_ptr);
197 break;
198 case TYPE_UINT16:
199 snprintf(buf, buf_size, "%u", *(uint16_t*)default_ptr);
200 break;
201 case TYPE_INT32:
202 snprintf(buf, buf_size, "%d", *(int32_t*)default_ptr);
203 break;
204 case TYPE_UINT32:
205 snprintf(buf, buf_size, "%u", *(uint32_t*)default_ptr);
206 break;
207 case TYPE_INT64:
208 snprintf(buf, buf_size, "%lld", (long long)*(int64_t*)default_ptr);
209 break;
210 case TYPE_UINT64:
211 snprintf(buf, buf_size, "%llu", (unsigned long long)*(uint64_t*)default_ptr);
212 break;
213 case TYPE_SIZE_T:
214 snprintf(buf, buf_size, "%zu", *(size_t*)default_ptr);
215 break;
216 case TYPE_FLOAT:
217 snprintf(buf, buf_size, "%.6g", *(float*)default_ptr);
218 break;
219 case TYPE_DOUBLE:
220 snprintf(buf, buf_size, "%.6g", *(double*)default_ptr);
221 break;
222 default:
223 buf[0] = '\0';
224 break;
225 }
226}
227
228// --- Lifecycle ---
229
238FlagParser* flag_parser_new(const char* name, const char* description) {
239 // Create arena with default size
240 Arena* arena = arena_create(0);
241 if (!arena) {
242 perror("arena_create");
243 exit(1);
244 }
245
246 FlagParser* fp = ARENA_ALLOC_ZERO(arena, FlagParser);
247 if (!fp) {
248 perror("arena_alloc");
249 exit(1);
250 }
251
252 fp->arena = arena;
253 fp->name = arena_strdup(arena, name);
254 fp->description = arena_strdup(arena, description);
255 return fp;
256}
257
264void flag_parser_free(FlagParser* fp) {
265 if (!fp) return;
266 arena_destroy(fp->arena);
267}
268
274void flag_parser_set_footer(FlagParser* parser, const char* footer) {
275 if (!parser) return;
276 parser->footer = arena_strdup(parser->arena, footer);
277}
278
287void flag_set_pre_invoke(FlagParser* fp, void (*pre_invoke)(void* user_data)) {
288 if (fp) {
289 fp->pre_invoke = pre_invoke;
290 }
291}
292
293// --- Registration ---
294
316Flag* flag_add(FlagParser* fp, FlagDataType type, const char* name, char short_name, const char* desc, void* value_ptr,
317 bool required) {
318 if (!fp || !name || !value_ptr) return NULL;
319
320 if (fp->flag_count >= fp->flag_capacity) {
321 size_t new_cap = (fp->flag_capacity == 0) ? INITIAL_CAPACITY : fp->flag_capacity * 2;
322 Flag* new_flags = ARENA_ALLOC_ARRAY(fp->arena, Flag, new_cap);
323 if (!new_flags) exit(1);
324
325 if (fp->flags) {
326 memcpy(new_flags, fp->flags, fp->flag_count * sizeof(Flag));
327 }
328 fp->flags = new_flags;
329 fp->flag_capacity = new_cap;
330 }
331
332 Flag* f = &fp->flags[fp->flag_count++];
333 f->type = type;
334 f->name = arena_strdup(fp->arena, name);
335 f->short_name = short_name;
336 f->description = arena_strdup(fp->arena, desc);
337 f->value_ptr = value_ptr;
338 f->required = required;
339 f->is_present = false;
340 f->validator = NULL;
341
342 // Copy the default value for display in help
343 f->default_ptr = copy_default_value(fp->arena, type, value_ptr);
344 return f;
345}
346
365FlagParser* flag_add_subcommand(FlagParser* fp, const char* name, const char* desc, void (*handler)(void* data)) {
366 if (!fp || !name) return NULL;
367 if (fp->cmd_count >= fp->cmd_capacity) {
368 size_t new_cap = (fp->cmd_capacity == 0) ? INITIAL_CAPACITY : fp->cmd_capacity * 2;
369 FlagParser** new_cmds = ARENA_ALLOC_ARRAY(fp->arena, FlagParser*, new_cap);
370 if (!new_cmds) exit(1);
371
372 if (fp->subcommands) {
373 memcpy(new_cmds, fp->subcommands, fp->cmd_count * sizeof(FlagParser*));
374 }
375 fp->subcommands = new_cmds;
376 fp->cmd_capacity = new_cap;
377 }
378
379 // Subcommands share the same arena as the root
380 FlagParser* sub = ARENA_ALLOC_ZERO(fp->arena, FlagParser);
381 sub->arena = fp->arena;
382 sub->name = arena_strdup(fp->arena, name);
383 sub->description = arena_strdup(fp->arena, desc);
384 sub->handler = handler;
385 sub->pre_invoke = NULL;
386 fp->subcommands[fp->cmd_count++] = sub;
387 return sub;
388}
389
390// --- Subcommand Invocation ---
391
402bool flag_invoke_subcommand(FlagParser* fp, void (*pre_invoke)(void* user_data), void* user_data) {
403 if (!fp || !fp->active_subcommand) {
404 return false;
405 }
406
407 FlagParser* sub = fp->active_subcommand;
408
409 // Run pre-invocation callback if provided
410 if (pre_invoke) {
411 pre_invoke(user_data);
412 }
413
414 // Invoke the subcommand handler if it exists
415 if (sub->handler) {
416 sub->handler(user_data);
417 }
418
419 return true;
420}
421
443void flag_set_validator(Flag* flag, FlagValidator validator) {
444 if (flag) flag->validator = validator;
445}
446
447// --- Parsing Internals ---
448
455static void set_error(FlagParser* fp, const char* fmt, ...) {
456 if (!fp) return;
457 va_list args;
458 va_start(args, fmt);
459 vsnprintf(fp->last_error, ERR_BUF_SIZE, fmt, args);
460 va_end(args);
461}
462
469static Flag* find_flag_long(FlagParser* fp, const char* name) {
470 if (!fp || !name) return NULL;
471 for (size_t i = 0; i < fp->flag_count; i++) {
472 if (strcmp(fp->flags[i].name, name) == 0) return &fp->flags[i];
473 }
474 return NULL;
475}
476
483static Flag* find_flag_short(FlagParser* fp, char c) {
484 if (!fp || !c) return NULL;
485 for (size_t i = 0; i < fp->flag_count; i++) {
486 if (fp->flags[i].short_name == c) return &fp->flags[i];
487 }
488 return NULL;
489}
490
498static bool check_range_int(long long val, long long min, long long max) { return (val >= min && val <= max); }
499
506static bool check_range_uint(unsigned long long val, unsigned long long max) { return (val <= max); }
507
516static FlagStatus parse_value(FlagParser* fp, Flag* flag, const char* str) {
517 if (!str) return FLAG_ERROR_MISSING_VALUE;
518 char* endptr = NULL;
519 errno = 0;
520
521 switch (flag->type) {
522 case TYPE_BOOL: {
523 bool val = true;
524 if (strcasecmp(str, "false") == 0 || strcmp(str, "0") == 0) val = false;
525 *(bool*)flag->value_ptr = val;
526 break;
527 }
528 case TYPE_CHAR:
529 if (!str || strlen(str) == 0) return FLAG_ERROR_MISSING_VALUE;
530 if (strlen(str) != 1) return FLAG_ERROR_INVALID_ARGUMENT;
531 *(char*)flag->value_ptr = str[0];
532 break;
533 case TYPE_STRING:
534 // Don't free the old value - we don't know if it's malloc'd or static,
535 // and we are moving to arena-managed strings.
536 // if (*(char**)flag->value_ptr) free(*(char**)flag->value_ptr);
537 *(char**)flag->value_ptr = arena_strdup(fp->arena, str);
538 break;
539
540 // Signed Integers
541 case TYPE_INT8:
542 case TYPE_INT16:
543 case TYPE_INT32:
544 case TYPE_INT64: {
545 long long val = strtoll(str, &endptr, 10);
546 if (endptr == str || *endptr != '\0' || errno == ERANGE) return FLAG_ERROR_INVALID_NUMBER;
547
548 if (flag->type == TYPE_INT8 && !check_range_int(val, INT8_MIN, INT8_MAX)) return FLAG_ERROR_INVALID_NUMBER;
549 if (flag->type == TYPE_INT16 && !check_range_int(val, INT16_MIN, INT16_MAX))
550 return FLAG_ERROR_INVALID_NUMBER;
551 if (flag->type == TYPE_INT32 && !check_range_int(val, INT32_MIN, INT32_MAX))
552 return FLAG_ERROR_INVALID_NUMBER;
553
554 // Assign based on type size
555 if (flag->type == TYPE_INT8)
556 *(int8_t*)flag->value_ptr = (int8_t)val;
557 else if (flag->type == TYPE_INT16)
558 *(int16_t*)flag->value_ptr = (int16_t)val;
559 else if (flag->type == TYPE_INT32)
560 *(int32_t*)flag->value_ptr = (int32_t)val;
561 else
562 *(int64_t*)flag->value_ptr = (int64_t)val;
563 break;
564 }
565
566 // Unsigned Integers
567 case TYPE_UINT8:
568 case TYPE_UINT16:
569 case TYPE_UINT32:
570 case TYPE_UINT64:
571 case TYPE_SIZE_T: {
572 if (str[0] == '-') return FLAG_ERROR_INVALID_NUMBER;
573 unsigned long long val = strtoull(str, &endptr, 10);
574 if (endptr == str || *endptr != '\0' || errno == ERANGE) return FLAG_ERROR_INVALID_NUMBER;
575
576 if (flag->type == TYPE_UINT8 && !check_range_uint(val, UINT8_MAX)) return FLAG_ERROR_INVALID_NUMBER;
577 if (flag->type == TYPE_UINT16 && !check_range_uint(val, UINT16_MAX)) return FLAG_ERROR_INVALID_NUMBER;
578 if (flag->type == TYPE_UINT32 && !check_range_uint(val, UINT32_MAX)) return FLAG_ERROR_INVALID_NUMBER;
579 if (flag->type == TYPE_SIZE_T && !check_range_uint(val, SIZE_MAX)) return FLAG_ERROR_INVALID_NUMBER;
580
581 if (flag->type == TYPE_UINT8)
582 *(uint8_t*)flag->value_ptr = (uint8_t)val;
583 else if (flag->type == TYPE_UINT16)
584 *(uint16_t*)flag->value_ptr = (uint16_t)val;
585 else if (flag->type == TYPE_UINT32)
586 *(uint32_t*)flag->value_ptr = (uint32_t)val;
587 else if (flag->type == TYPE_SIZE_T)
588 *(size_t*)flag->value_ptr = (size_t)val;
589 else
590 *(uint64_t*)flag->value_ptr = (uint64_t)val;
591 break;
592 }
593
594 case TYPE_FLOAT: {
595 float val = strtof(str, &endptr);
596 if (endptr == str || *endptr != '\0' || errno == ERANGE) return FLAG_ERROR_INVALID_NUMBER;
597 *(float*)flag->value_ptr = val;
598 break;
599 }
600 case TYPE_DOUBLE: {
601 double val = strtod(str, &endptr);
602 if (endptr == str || *endptr != '\0' || errno == ERANGE) return FLAG_ERROR_INVALID_NUMBER;
603 *(double*)flag->value_ptr = val;
604 break;
605 }
606 default:
607 return FLAG_ERROR_INVALID_ARGUMENT;
608 }
609 return FLAG_OK;
610}
611
612// --- Main Parse Logic ---
613
633FlagStatus flag_parse(FlagParser* fp, int argc, char** argv) {
634 if (!fp || argc < 1) return FLAG_ERROR_INVALID_ARGUMENT;
635
636 int i = 1;
637 bool end_of_opts = false;
638
639 while (i < argc) {
640 char* arg = argv[i];
641
642 // 1. End of Options
643 if (!end_of_opts && strcmp(arg, "--") == 0) {
644 end_of_opts = true;
645 i++;
646 continue;
647 }
648
649 // 2. Positionals (or Subcommands)
650 if (end_of_opts || arg[0] != '-') {
651 // Check subcommands
652 if (!end_of_opts && fp->cmd_count > 0) {
653 for (size_t c = 0; c < fp->cmd_count; c++) {
654 if (strcmp(fp->subcommands[c]->name, arg) == 0) {
655 fp->active_subcommand = fp->subcommands[c];
656 // Recursive call for subcommand
657 return flag_parse(fp->active_subcommand, argc - i, argv + i);
658 }
659 }
660 }
661
662 // Add positional
663 if (fp->pos_count >= fp->pos_capacity) {
664 size_t new_cap = (fp->pos_capacity == 0) ? INITIAL_CAPACITY : fp->pos_capacity * 2;
665 char** new_pos = ARENA_ALLOC_ARRAY(fp->arena, char*, new_cap);
666 if (!new_pos) exit(1);
667
668 if (fp->positional_args) {
669 memcpy(new_pos, fp->positional_args, fp->pos_count * sizeof(char*));
670 }
671 fp->positional_args = new_pos;
672 fp->pos_capacity = new_cap;
673 }
674 fp->positional_args[fp->pos_count++] = arg;
675 i++;
676 continue;
677 }
678
679 // 3. Long Flags
680 if (strncmp(arg, "--", 2) == 0) {
681 char* name_start = arg + 2;
682 if (strcmp(name_start, "help") == 0) {
684 exit(0);
685 }
686
687 char* eq = strchr(name_start, '=');
688 char name_buf[MAX_FLAG_NAME_LEN];
689 const char* val_str = NULL;
690 const char* lookup_name = name_start;
691
692 if (eq) {
693 size_t len = (size_t)(eq - name_start);
694 if (len >= MAX_FLAG_NAME_LEN) len = MAX_FLAG_NAME_LEN - 1;
695 strncpy(name_buf, name_start, len);
696 name_buf[len] = '\0';
697 lookup_name = name_buf;
698 val_str = eq + 1;
699 }
700
701 Flag* f = find_flag_long(fp, lookup_name);
702 if (!f) {
703 set_error(fp, "Unknown flag: --%s", lookup_name);
704 return FLAG_ERROR_UNKNOWN_FLAG;
705 }
706
707 f->is_present = true;
708
709 if (f->type == TYPE_BOOL) {
710 bool b = true;
711 if (val_str && (strcasecmp(val_str, "false") == 0 || strcmp(val_str, "0") == 0)) b = false;
712 *(bool*)f->value_ptr = b;
713 } else {
714 if (!val_str) {
715 if (i + 1 < argc && argv[i + 1][0] != '-')
716 val_str = argv[++i];
717 else {
718 set_error(fp, "Flag --%s requires a value", f->name);
719 return FLAG_ERROR_MISSING_VALUE;
720 }
721 }
722 FlagStatus s = parse_value(fp, f, val_str);
723 if (s != FLAG_OK) {
724 set_error(fp, "Invalid value for --%s: '%s' (Type mismatch or overflow)", f->name, val_str);
725 return s;
726 }
727 }
728 i++;
729 continue;
730 }
731
732 // 4. Short Flags
733 if (arg[0] == '-') {
734 size_t len = strlen(arg);
735 for (size_t k = 1; k < len; k++) {
736 char c = arg[k];
737 Flag* f = find_flag_short(fp, c);
738 if (!f) {
739 set_error(fp, "Unknown short flag: -%c", c);
740 return FLAG_ERROR_UNKNOWN_FLAG;
741 }
742 f->is_present = true;
743
744 if (f->type == TYPE_BOOL) {
745 *(bool*)f->value_ptr = true;
746 } else {
747 const char* val_str = NULL;
748 if (k + 1 < len) {
749 val_str = &arg[k + 1];
750 if (*val_str == '=') val_str++;
751 FlagStatus s = parse_value(fp, f, val_str);
752 if (s != FLAG_OK) {
753 set_error(fp, "Invalid value for -%c (Type mismatch or overflow)", c);
754 return s;
755 }
756 break; // consumed rest
757 } else {
758 if (i + 1 < argc && argv[i + 1][0] != '-') {
759 val_str = argv[++i];
760 FlagStatus s = parse_value(fp, f, val_str);
761 if (s != FLAG_OK) {
762 set_error(fp, "Invalid value for -%c (Type mismatch or overflow)", c);
763 return s;
764 }
765 } else {
766 set_error(fp, "Flag -%c requires a value", c);
767 return FLAG_ERROR_MISSING_VALUE;
768 }
769 }
770 }
771 }
772 i++;
773 continue;
774 }
775 }
776
777 // Validation
778 for (size_t k = 0; k < fp->flag_count; k++) {
779 Flag* f = &fp->flags[k];
780 if (f->required && !f->is_present) {
781 set_error(fp, "Missing required flag: --%s", f->name);
782 return FLAG_ERROR_REQUIRED_MISSING;
783 }
784
785 if (f->is_present && f->validator) {
786 if (f->value_ptr == NULL) {
787 set_error(fp, "Validation failed for --%s: %s", f->name, "value is NULL");
788 return FLAG_ERROR_VALIDATION;
789 }
790
791 const char* err = NULL;
792 if (!f->validator(f->value_ptr, &err)) {
793 set_error(fp, "Validation failed for --%s: %s", f->name, err ? err : "invalid");
794 return FLAG_ERROR_VALIDATION;
795 }
796 }
797 }
798
799 return FLAG_OK;
800}
801
834FlagStatus flag_parse_and_invoke(FlagParser* fp, int argc, char** argv, void* user_data) {
835 if (!fp || argc < 1) {
836 return FLAG_ERROR_INVALID_ARGUMENT;
837 }
838
839 // Parse all arguments
840 FlagStatus status = flag_parse(fp, argc, argv);
841 if (status != FLAG_OK) {
842 return status;
843 }
844
845 // Run pre-invocation callback (Global setup)
846 if (fp->pre_invoke) {
847 fp->pre_invoke(user_data);
848 }
849
850 // Find the deepest active subcommand (The "Leaf" command)
851 FlagParser* target = fp;
852 while (target->active_subcommand) {
853 target = target->active_subcommand;
854 }
855
856 // Run the handler for the target command
857 if (target->handler) {
858 target->handler(user_data);
859 }
860
861 return FLAG_OK;
862}
863
864// --- Usage / Help ---
865
871static const char* type_to_str(FlagDataType t) {
872 switch (t) {
873 case TYPE_BOOL:
874 return "";
875 case TYPE_CHAR:
876 return "CHAR";
877 case TYPE_STRING:
878 return "STR";
879 case TYPE_INT8:
880 return "INT8";
881 case TYPE_UINT8:
882 return "UINT8";
883 case TYPE_INT16:
884 return "INT16";
885 case TYPE_UINT16:
886 return "UINT16";
887 case TYPE_INT32:
888 return "INT";
889 case TYPE_UINT32:
890 return "UINT";
891 case TYPE_INT64:
892 return "INT64";
893 case TYPE_UINT64:
894 return "UINT64";
895 case TYPE_SIZE_T:
896 return "SIZE";
897 case TYPE_FLOAT:
898 return "FLOAT";
899 case TYPE_DOUBLE:
900 return "DBL";
901 default:
902 return "VAL";
903 }
904}
905
909static size_t calculate_flag_width(FlagParser* fp) {
910 size_t max_width = 0;
911 for (size_t i = 0; i < fp->flag_count; i++) {
912 // Calculation: " -s, --long-name=TYPE"
913 size_t w = 6; // indent(2) + "-x, " (4)
914 w += strlen(fp->flags[i].name) + 2; // "--" + name
915
916 if (fp->flags[i].type != TYPE_BOOL) {
917 w += 1 + strlen(type_to_str(fp->flags[i].type)); // "=" + TYPE
918 }
919
920 if (w > max_width) max_width = w;
921 }
922 return max_width;
923}
924
928static size_t calculate_cmd_width(FlagParser* fp) {
929 size_t max_width = 0;
930 for (size_t i = 0; i < fp->cmd_count; i++) {
931 size_t w = strlen(fp->subcommands[i]->name) + 4; // indent(2) + name + padding(2)
932 if (w > max_width) max_width = w;
933 }
934 return max_width;
935}
936
940static void print_flag_row(Flag* f, size_t max_width) {
941 char left[256];
942 int pos = 0;
943
944 // Short name column
945 if (f->short_name) {
946 pos += snprintf(left + pos, sizeof(left) - (size_t)pos, " -%c, ", f->short_name);
947 } else {
948 pos += snprintf(left + pos, sizeof(left) - (size_t)pos, " ");
949 }
950
951 // Long name column
952 pos += snprintf(left + pos, sizeof(left) - (size_t)pos, "--%s", f->name);
953
954 // Type column
955 if (f->type != TYPE_BOOL) {
956 pos += snprintf(left + pos, sizeof(left) - (size_t)pos, "=%s", type_to_str(f->type));
957 }
958
959 // Print Left Column aligned, then Description
960 printf("%-*s %s", (int)max_width, left, f->description ? f->description : "");
961
962 // 4. Metadata (Required / Default)
963 if (f->required) {
964 printf(" (Required)");
965 } else if (f->default_ptr) {
966 char default_str[MAX_DEFAULT_STR];
967 format_default_value(f->type, f->default_ptr, default_str, sizeof(default_str));
968 if (default_str[0] != '\0') {
969 printf(" (default: %s)", default_str);
970 }
971 }
972 printf("\n");
973}
974
978static void print_help_internal(FlagParser* fp) {
979 if (!fp) return;
980
981 // --- 1. Header & Description ---
982 printf("\n%s\n", fp->description ? fp->description : fp->name);
983
984 printf("\nUsage:\n %s", fp->name);
985 if (fp->flag_count > 0) printf(" [flags]");
986 if (fp->cmd_count > 0) printf(" [command]");
987 printf("\n");
988
989 // Flags (Current level only) ---
990 if (fp->flag_count > 0) {
991 printf("\nFlags:\n");
992 size_t width = calculate_flag_width(fp);
993 if (width < 20) width = 20;
994
995 for (size_t i = 0; i < fp->flag_count; i++) {
996 print_flag_row(&fp->flags[i], width);
997 }
998 }
999
1000 // Subcommands (Immediate children only) ---
1001 if (fp->cmd_count > 0) {
1002 printf("\nAvailable Commands:\n");
1003 size_t width = calculate_cmd_width(fp);
1004 if (width < 20) width = 20; // Minimum width
1005
1006 for (size_t i = 0; i < fp->cmd_count; i++) {
1007 FlagParser* sub = fp->subcommands[i];
1008 printf(" %-*s%s\n", (int)(width - 2), sub->name, sub->description ? sub->description : "");
1009 }
1010 }
1011
1012 // --- 4. Footer ---
1013 if (fp->footer) {
1014 printf("\n%s\n", fp->footer);
1015 } else if (fp->cmd_count > 0) {
1016 printf("\nUse \"%s [command] --help\" for more information about a command.\n", fp->name);
1017 }
1018}
1019
1027void flag_print_usage(FlagParser* fp) {
1028 if (!fp) return;
1029
1030 // Traverse to the deepest active subcommand to show context-sensitive help
1031 FlagParser* leaf = fp;
1032 while (leaf->active_subcommand) {
1033 leaf = leaf->active_subcommand;
1034 }
1035
1036 print_help_internal(leaf);
1037}
1038
1039// --- Accessors ---
1040
1049const char* flag_get_error(FlagParser* fp) {
1050 if (!fp) return "";
1051
1052 FlagParser* root = fp;
1053
1054 if (root->active_subcommand) {
1055 root = fp->active_subcommand;
1056 while (root->active_subcommand) root = root->active_subcommand;
1057 }
1058 return root->last_error;
1059}
1060
1062FlagParser* flag_active_subcommand(FlagParser* parser) {
1063 FlagParser* target = parser;
1064 while (target && target->active_subcommand) {
1065 target = target->active_subcommand;
1066 }
1067 return target;
1068}
1069
1075int flag_positional_count(FlagParser* fp) { return fp ? (int)fp->pos_count : 0; }
1076
1090const char* flag_positional_at(FlagParser* fp, int i) {
1091 return (fp && i >= 0 && (size_t)i < fp->pos_count) ? fp->positional_args[i] : NULL;
1092}
1093
1109bool flag_is_present(FlagParser* fp, const char* name) {
1110 Flag* f = find_flag_long(fp, name);
1111 return f ? f->is_present : false;
1112}
1113
1119const char* flag_status_str(FlagStatus s) {
1120 switch (s) {
1121 case FLAG_OK:
1122 return "OK";
1123 case FLAG_ERROR_MISSING_VALUE:
1124 return "Missing Value";
1125 case FLAG_ERROR_UNKNOWN_FLAG:
1126 return "Unknown Flag";
1127 case FLAG_ERROR_INVALID_NUMBER:
1128 return "Invalid Number";
1129 case FLAG_ERROR_VALIDATION:
1130 return "Validation Failed";
1131 case FLAG_ERROR_REQUIRED_MISSING:
1132 return "Required Flag Missing";
1133 case FLAG_ERROR_UNKNOWN_SUBCOMMAND:
1134 return "Unknown subcommand";
1135 case FLAG_ERROR_INVALID_ARGUMENT:
1136 return "Invalid Argument";
1137 default:
1138 return "Error";
1139 }
1140}
1141
1142// ================ Shell completion =====================================
1143#include <ctype.h>
1144
1145// --- Completion Generation Internals ---
1146
1147// Static context to hold completion configuration
1148// This is necessary because the handler signature is fixed (void* user_data)
1149// and we need access to the root parser and specific flags during the callback.
1150static struct {
1151 FlagParser* root;
1152 char* shell;
1153 char* output;
1154} _comp_ctx = {0};
1155
1162static void write_safe_str(FILE* f, const char* str, int quote_style) {
1163 if (!str || !f) return;
1164
1165 if (quote_style == 1) fputc('\'', f);
1166 if (quote_style == 2) fputc('"', f);
1167
1168 for (const char* p = str; *p; p++) {
1169 if (quote_style == 1 && *p == '\'') {
1170 // Escape single quote in single-quoted string: close, escaped quote, reopen
1171 fprintf(f, "'\\''");
1172 } else if (quote_style == 2 && (*p == '"' || *p == '\\' || *p == '$' || *p == '`')) {
1173 // Escape special chars in double-quoted string
1174 fputc('\\', f);
1175 fputc(*p, f);
1176 } else if (quote_style == 0 && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '|' || *p == '&' || *p == ';' ||
1177 *p == '<' || *p == '>' || *p == '(' || *p == ')' || *p == '$' || *p == '`' ||
1178 *p == '\\' || *p == '"' || *p == '\'' || *p == '*' || *p == '?')) {
1179 // Escape special shell chars when not quoted
1180 fputc('\\', f);
1181 fputc(*p, f);
1182 } else {
1183 fputc(*p, f);
1184 }
1185 }
1186
1187 if (quote_style == 1) fputc('\'', f);
1188 if (quote_style == 2) fputc('"', f);
1189}
1190
1194static void write_shell_identifier(FILE* f, const char* str) {
1195 if (!str || !f) return;
1196
1197 // First char must be letter or underscore
1198 if (!isalpha(*str) && *str != '_') {
1199 fputc('_', f);
1200 }
1201
1202 for (const char* p = str; *p; p++) {
1203 if (isalnum(*p) || *p == '_') {
1204 fputc(*p, f);
1205 } else {
1206 fputc('_', f);
1207 }
1208 }
1209}
1210
1217static void write_bash_subcommand_cases(FILE* f, FlagParser* p, const char* prefix) {
1218 if (!p) return;
1219
1220 // Build the full command path for this level
1221 char full_name[512];
1222 if (prefix && *prefix) {
1223 snprintf(full_name, sizeof(full_name), "%s_%s", prefix, p->name);
1224 } else {
1225 snprintf(full_name, sizeof(full_name), "%s", p->name);
1226 }
1227
1228 // Write case for this command (skip root as it's handled separately)
1229 if (p != _comp_ctx.root && (p->flag_count > 0 || p->cmd_count > 0)) {
1230 fprintf(f, " ");
1231 write_safe_str(f, p->name, 0);
1232 fprintf(f, ")\n");
1233
1234 // Handle flags that need arguments
1235 if (p->flag_count > 0) {
1236 fprintf(f, " case \"$prev\" in\n");
1237 for (size_t i = 0; i < p->flag_count; i++) {
1238 Flag* flag = &p->flags[i];
1239 if (flag->type != TYPE_BOOL) {
1240 fprintf(f, " --");
1241 write_safe_str(f, flag->name, 0);
1242 if (flag->short_name && isprint(flag->short_name)) {
1243 fprintf(f, "|-");
1244 fputc(flag->short_name, f);
1245 }
1246 fprintf(f, ")\n");
1247
1248 // Type-specific completion hints
1249 switch (flag->type) {
1250 case TYPE_STRING:
1251 fprintf(f, " # File/directory completion\n");
1252 fprintf(f, " COMPREPLY=( $(compgen -f -- \"$cur\") )\n");
1253 break;
1254 default:
1255 fprintf(f, " # Value expected\n");
1256 fprintf(f, " return 0\n");
1257 break;
1258 }
1259
1260 fprintf(f, " return 0\n");
1261 fprintf(f, " ;;\n");
1262 }
1263 }
1264 fprintf(f, " esac\n\n");
1265 }
1266
1267 // Complete with this command's flags and immediate subcommands
1268 fprintf(f, " local flags=\"");
1269 for (size_t i = 0; i < p->flag_count; i++) {
1270 fprintf(f, "--");
1271 write_safe_str(f, p->flags[i].name, 0);
1272 fprintf(f, " ");
1273 }
1274 fprintf(f, "\"\n");
1275
1276 if (p->cmd_count > 0) {
1277 fprintf(f, " local subcommands=\"");
1278 for (size_t i = 0; i < p->cmd_count; i++) {
1279 write_safe_str(f, p->subcommands[i]->name, 0);
1280 fprintf(f, " ");
1281 }
1282 fprintf(f, "\"\n");
1283 fprintf(f, " COMPREPLY=( $(compgen -W \"$flags $subcommands\" -- \"$cur\") )\n");
1284 } else {
1285 fprintf(f, " COMPREPLY=( $(compgen -W \"$flags\" -- \"$cur\") )\n");
1286 }
1287
1288 fprintf(f, " return 0\n");
1289 fprintf(f, " ;;\n");
1290 }
1291
1292 // Recurse into children with updated prefix
1293 for (size_t i = 0; i < p->cmd_count; i++) {
1294 write_bash_subcommand_cases(f, p->subcommands[i], full_name);
1295 }
1296}
1297
1301static void write_bash_all_subcommands_list(FILE* f, FlagParser* p) {
1302 if (!p) return;
1303 for (size_t i = 0; i < p->cmd_count; i++) {
1304 write_safe_str(f, p->subcommands[i]->name, 0);
1305 fprintf(f, " ");
1306 write_bash_all_subcommands_list(f, p->subcommands[i]);
1307 }
1308}
1309
1313static void gen_bash_completion(FlagParser* fp, FILE* f) {
1314 if (!fp || !f) return;
1315
1316 char* bin_name = fp->name;
1317
1318 fprintf(f, "#!/usr/bin/env bash\n");
1319 fprintf(f, "# Bash completion for %s\n", bin_name ? bin_name : "program");
1320 fprintf(f, "# Generated by flags.c completion generator\n\n");
1321
1322 fprintf(f, "_");
1323 write_shell_identifier(f, bin_name);
1324 fprintf(f, "_completion() {\n");
1325 fprintf(f, " local cur prev words cword\n");
1326 fprintf(f, " _init_completion || return\n\n");
1327
1328 fprintf(f, " local global_flags=\"");
1329 if (fp->flag_count > 0) {
1330 for (size_t i = 0; i < fp->flag_count; i++) {
1331 fprintf(f, "--");
1332 write_safe_str(f, fp->flags[i].name, 0);
1333 fprintf(f, " ");
1334 }
1335 }
1336 fprintf(f, "--help\"\n");
1337
1338 // Collect ALL subcommands (recursive) for context detection
1339 fprintf(f, " local subcommands=\"");
1340 write_bash_all_subcommands_list(f, fp);
1341 fprintf(f, "\"\n\n");
1342
1343 // Handle Global Flags Arguments
1344 fprintf(f, " # Handle flags that require arguments\n");
1345 fprintf(f, " case \"$prev\" in\n");
1346 for (size_t i = 0; i < fp->flag_count; i++) {
1347 Flag* flag = &fp->flags[i];
1348 if (flag->type != TYPE_BOOL) {
1349 fprintf(f, " --");
1350 write_safe_str(f, flag->name, 0);
1351 if (flag->short_name && isprint(flag->short_name)) {
1352 fprintf(f, "|-");
1353 fputc(flag->short_name, f);
1354 }
1355 fprintf(f, ")\n");
1356
1357 // Type-specific hints
1358 if (flag->type == TYPE_STRING) {
1359 fprintf(f, " COMPREPLY=( $(compgen -f -- \"$cur\") )\n");
1360 }
1361
1362 fprintf(f, " return 0\n");
1363 fprintf(f, " ;;\n");
1364 }
1365 }
1366 fprintf(f, " esac\n\n");
1367
1368 // Context detection - find the active subcommand
1369 fprintf(f, " # Detect active subcommand context\n");
1370 fprintf(f, " local cmd_context=\"\"\n");
1371 fprintf(f, " local i\n");
1372 fprintf(f, " for ((i=1; i < cword; i++)); do\n");
1373 fprintf(f, " local word=\"${words[i]}\"\n");
1374 fprintf(f, " # Skip flags\n");
1375 fprintf(f, " if [[ \"$word\" != -* ]]; then\n");
1376 fprintf(f, " # Check if it's a known subcommand\n");
1377 fprintf(f, " for cmd in $subcommands; do\n");
1378 fprintf(f, " if [[ \"$word\" == \"$cmd\" ]]; then\n");
1379 fprintf(f, " cmd_context=\"$cmd\"\n");
1380 fprintf(f, " break 2\n");
1381 fprintf(f, " fi\n");
1382 fprintf(f, " done\n");
1383 fprintf(f, " fi\n");
1384 fprintf(f, " done\n\n");
1385
1386 // If no subcommand active, show globals + top-level subs
1387 fprintf(f, " if [[ -z \"$cmd_context\" ]]; then\n");
1388 if (fp->cmd_count > 0) {
1389 fprintf(f, " local top_level_subs=\"");
1390 for (size_t i = 0; i < fp->cmd_count; i++) {
1391 write_safe_str(f, fp->subcommands[i]->name, 0);
1392 fprintf(f, " ");
1393 }
1394 fprintf(f, "\"\n");
1395 fprintf(f, " COMPREPLY=( $(compgen -W \"$top_level_subs $global_flags\" -- \"$cur\") )\n");
1396 } else {
1397 fprintf(f, " COMPREPLY=( $(compgen -W \"$global_flags\" -- \"$cur\") )\n");
1398 }
1399 fprintf(f, " return 0\n");
1400 fprintf(f, " fi\n\n");
1401
1402 // Subcommand-specific completions
1403 if (fp->cmd_count > 0) {
1404 fprintf(f, " # Handle subcommand-specific completions\n");
1405 fprintf(f, " case \"$cmd_context\" in\n");
1406 write_bash_subcommand_cases(f, fp, NULL);
1407 fprintf(f, " *)\n");
1408 fprintf(f, " COMPREPLY=( $(compgen -W \"$global_flags\" -- \"$cur\") )\n");
1409 fprintf(f, " ;;\n");
1410 fprintf(f, " esac\n\n");
1411 }
1412
1413 fprintf(f, " return 0\n");
1414 fprintf(f, "}\n\n");
1415
1416 fprintf(f, "complete -F _");
1417 write_shell_identifier(f, bin_name);
1418 fprintf(f, "_completion ");
1419 write_safe_str(f, bin_name, 0);
1420 fprintf(f, "\n");
1421}
1422
1423// --- Zsh Generation ---
1424
1428static void write_zsh_args(FILE* f, FlagParser* p, int indent) {
1429 if (!p || !f) return;
1430
1431 for (size_t i = 0; i < p->flag_count; i++) {
1432 Flag* flag = &p->flags[i];
1433
1434 // Build description with escaping for Zsh
1435 char desc[512] = {0};
1436 if (flag->description) {
1437 size_t k = 0;
1438 for (const char* c = flag->description; *c && k < 500; c++) {
1439 // Escape special Zsh characters in descriptions
1440 if (*c == '[' || *c == ']' || *c == '\'' || *c == '\\' || *c == ':') {
1441 desc[k++] = '\\';
1442 }
1443 desc[k++] = *c;
1444 }
1445 desc[k] = '\0';
1446 }
1447
1448 // Print with proper indentation
1449 for (int j = 0; j < indent; j++) fprintf(f, " ");
1450
1451 // Determine if flag needs an argument
1452 if (flag->type == TYPE_BOOL) {
1453 fprintf(f, "'--");
1454 write_safe_str(f, flag->name, 0);
1455 fprintf(f, "[%s]'", desc);
1456 } else {
1457 // Flags with arguments
1458 const char* arg_type = "value";
1459 switch (flag->type) {
1460 case TYPE_STRING:
1461 arg_type = "file:_files";
1462 break;
1463 case TYPE_INT8:
1464 case TYPE_INT16:
1465 case TYPE_INT32:
1466 case TYPE_INT64:
1467 arg_type = "integer";
1468 break;
1469 case TYPE_UINT8:
1470 case TYPE_UINT16:
1471 case TYPE_UINT32:
1472 case TYPE_UINT64:
1473 case TYPE_SIZE_T:
1474 arg_type = "unsigned integer";
1475 break;
1476 case TYPE_FLOAT:
1477 case TYPE_DOUBLE:
1478 arg_type = "number";
1479 break;
1480 default:
1481 arg_type = "value";
1482 break;
1483 }
1484
1485 fprintf(f, "'--");
1486 write_safe_str(f, flag->name, 0);
1487 fprintf(f, "[%s]:", desc);
1488
1489 if (flag->type == TYPE_STRING) {
1490 fprintf(f, ":_files'");
1491 } else {
1492 fprintf(f, ":%s:'", arg_type);
1493 }
1494 }
1495
1496 fprintf(f, " \\\n");
1497 }
1498}
1499
1503static void write_zsh_subcommand_cases(FILE* f, FlagParser* p, int depth) {
1504 if (!p || p->cmd_count == 0) return;
1505
1506 for (size_t i = 0; i < p->cmd_count; i++) {
1507 FlagParser* sub = p->subcommands[i];
1508
1509 for (int j = 0; j < depth; j++) fprintf(f, " ");
1510 write_safe_str(f, sub->name, 0);
1511 fprintf(f, ")\n");
1512
1513 for (int j = 0; j < depth; j++) fprintf(f, " ");
1514 fprintf(f, " _arguments -C \\\n");
1515
1516 // Write flags for this subcommand
1517 write_zsh_args(f, sub, depth + 2);
1518
1519 // Add nested subcommands if any
1520 if (sub->cmd_count > 0) {
1521 for (int j = 0; j < depth + 2; j++) fprintf(f, " ");
1522 fprintf(f, "'1:command:((");
1523 for (size_t k = 0; k < sub->cmd_count; k++) {
1524 if (k > 0) fprintf(f, " ");
1525 write_safe_str(f, sub->subcommands[k]->name, 0);
1526 fprintf(f, "\\:");
1527 if (sub->subcommands[k]->description) {
1528 write_safe_str(f, sub->subcommands[k]->description, 0);
1529 }
1530 }
1531 fprintf(f, "))' \\\n");
1532
1533 for (int j = 0; j < depth + 2; j++) fprintf(f, " ");
1534 fprintf(f, "'*::arg:->args' \\\n");
1535 }
1536
1537 for (int j = 0; j < depth; j++) fprintf(f, " ");
1538 fprintf(f, " && ret=0\n");
1539
1540 // Handle nested state
1541 if (sub->cmd_count > 0) {
1542 for (int j = 0; j < depth; j++) fprintf(f, " ");
1543 fprintf(f, " case $state in\n");
1544 for (int j = 0; j < depth; j++) fprintf(f, " ");
1545 fprintf(f, " args)\n");
1546 for (int j = 0; j < depth; j++) fprintf(f, " ");
1547 fprintf(f, " case $line[1] in\n");
1548
1549 write_zsh_subcommand_cases(f, sub, depth + 4);
1550
1551 for (int j = 0; j < depth; j++) fprintf(f, " ");
1552 fprintf(f, " esac\n");
1553 for (int j = 0; j < depth; j++) fprintf(f, " ");
1554 fprintf(f, " ;;\n");
1555 for (int j = 0; j < depth; j++) fprintf(f, " ");
1556 fprintf(f, " esac\n");
1557 }
1558
1559 for (int j = 0; j < depth; j++) fprintf(f, " ");
1560 fprintf(f, " ;;\n");
1561 }
1562}
1563
1567static void gen_zsh_completion(FlagParser* fp, FILE* f) {
1568 if (!fp || !f) return;
1569
1570 char* bin_name = fp->name;
1571
1572 fprintf(f, "#compdef ");
1573 write_safe_str(f, bin_name, 0);
1574 fprintf(f, "\n");
1575 fprintf(f, "# Generated by flags.c completion generator\n\n");
1576
1577 fprintf(f, "_");
1578 write_shell_identifier(f, bin_name);
1579 fprintf(f, "() {\n");
1580 fprintf(f, " local context state line\n");
1581 fprintf(f, " typeset -A opt_args\n");
1582 fprintf(f, " local ret=1\n\n");
1583
1584 // Main Arguments
1585 fprintf(f, " _arguments -C \\\n");
1586
1587 // Global flags
1588 write_zsh_args(f, fp, 2);
1589
1590 // Help flag
1591 fprintf(f, " '--help[Show help information]' \\\n");
1592
1593 // Subcommands
1594 if (fp->cmd_count > 0) {
1595 fprintf(f, " '1:command:((");
1596 for (size_t i = 0; i < fp->cmd_count; i++) {
1597 if (i > 0) fprintf(f, " ");
1598 write_safe_str(f, fp->subcommands[i]->name, 0);
1599 fprintf(f, "\\:");
1600 if (fp->subcommands[i]->description) {
1601 write_safe_str(f, fp->subcommands[i]->description, 0);
1602 }
1603 }
1604 fprintf(f, "))' \\\n");
1605 fprintf(f, " '*::arg:->args' \\\n");
1606 }
1607
1608 fprintf(f, " && ret=0\n\n");
1609
1610 // State Machine for subcommands
1611 if (fp->cmd_count > 0) {
1612 fprintf(f, " case $state in\n");
1613 fprintf(f, " args)\n");
1614 fprintf(f, " case $line[1] in\n");
1615
1616 write_zsh_subcommand_cases(f, fp, 4);
1617
1618 fprintf(f, " esac\n");
1619 fprintf(f, " ;;\n");
1620 fprintf(f, " esac\n\n");
1621 }
1622
1623 fprintf(f, " return ret\n");
1624 fprintf(f, "}\n\n");
1625
1626 // Call the completion function directly without arguments
1627 fprintf(f, "compdef _");
1628 write_shell_identifier(f, bin_name);
1629 fprintf(f, " ");
1630 write_safe_str(f, bin_name, 0);
1631 fprintf(f, "\n");
1632}
1633
1637static void completion_handler(void* user_data) {
1638 (void)user_data; // Unused, we use _comp_ctx
1639
1640 if (!_comp_ctx.shell || !*_comp_ctx.shell) {
1641 fprintf(stderr, "Error: --shell is required (bash or zsh)\n");
1642 exit(1);
1643 }
1644
1645 if (!_comp_ctx.root) {
1646 fprintf(stderr, "Error: Internal error - no root parser available\n");
1647 exit(1);
1648 }
1649
1650 FILE* out = stdout;
1651 if (_comp_ctx.output && *_comp_ctx.output) {
1652 out = fopen(_comp_ctx.output, "w");
1653 if (!out) {
1654 fprintf(stderr, "Error: Cannot open output file '%s': %s\n", _comp_ctx.output, strerror(errno));
1655 exit(1);
1656 }
1657 }
1658
1659 // Normalize shell name (case-insensitive)
1660 char shell_lower[32];
1661 size_t i;
1662 for (i = 0; i < sizeof(shell_lower) - 1 && _comp_ctx.shell[i]; i++) {
1663 shell_lower[i] = (char)tolower(_comp_ctx.shell[i]);
1664 }
1665 shell_lower[i] = '\0';
1666
1667 if (strcmp(shell_lower, "bash") == 0) {
1668 gen_bash_completion(_comp_ctx.root, out);
1669 } else if (strcmp(shell_lower, "zsh") == 0) {
1670 gen_zsh_completion(_comp_ctx.root, out);
1671 } else {
1672 fprintf(stderr, "Error: Unsupported shell '%s'. Supported: bash, zsh\n", _comp_ctx.shell);
1673 if (_comp_ctx.output) fclose(out);
1674 exit(1);
1675 }
1676
1677 if (_comp_ctx.output && out != stdout) {
1678 if (fclose(out) != 0) {
1679 fprintf(stderr, "Warning: Error closing output file: %s\n", strerror(errno));
1680 }
1681 printf("Completion script written to: %s\n", _comp_ctx.output);
1682 }
1683
1684 exit(0); // Exit after generating completion
1685}
1686
1694void flag_add_completion_cmd(FlagParser* fp) {
1695 if (!fp) return;
1696
1697 // Store root parser for traversal during generation
1698 _comp_ctx.root = fp;
1699
1700 // Ensure null initialization (defense in depth)
1701 _comp_ctx.shell = NULL;
1702 _comp_ctx.output = NULL;
1703
1704 FlagParser* cmd = flag_add_subcommand(fp, "completion", "Generate shell completion scripts", completion_handler);
1705
1706 if (!cmd) {
1707 fprintf(stderr, "Warning: Failed to add completion subcommand\n");
1708 return;
1709 }
1710
1711 // Add flags - use static context addresses
1712 flag_add(cmd, TYPE_STRING, "shell", 's', "Target shell (bash or zsh)", &_comp_ctx.shell, true);
1713 flag_add(cmd, TYPE_STRING, "output", 'o', "Output file path (default: stdout)", &_comp_ctx.output, false);
1714}
bool flag_invoke_subcommand(FlagParser *parser, void(*pre_invoke)(void *user_data), void *user_data)
FlagStatus flag_parse(FlagParser *parser, int argc, char **argv)
void flag_parser_set_footer(FlagParser *parser, const char *footer)
void flag_parser_free(FlagParser *parser)
Flag * flag_add(FlagParser *parser, FlagDataType type, const char *name, char short_name, const char *desc, void *value_ptr, bool required)
bool flag_is_present(FlagParser *parser, const char *flag_name)
FlagDataType
Definition flags.h:45
FlagParser * flag_add_subcommand(FlagParser *parser, const char *name, const char *desc, void(*handler)(void *data))
const char * flag_positional_at(FlagParser *parser, int index)
FlagStatus flag_parse_and_invoke(FlagParser *parser, int argc, char **argv, void *user_data)
const char * flag_status_str(FlagStatus status)
FlagStatus
Definition flags.h:32
const char * flag_get_error(FlagParser *parser)
FlagParser * flag_active_subcommand(FlagParser *parser)
bool(* FlagValidator)(const void *value, const char **error_out)
Definition flags.h:71
FlagParser * flag_parser_new(const char *name, const char *description)
void flag_set_validator(Flag *flag, FlagValidator validator)
int flag_positional_count(FlagParser *parser)
void flag_print_usage(FlagParser *parser)
void flag_set_pre_invoke(FlagParser *parser, void(*pre_invoke)(void *user_data))