17#include "../include/flags.h"
18#include "../include/arena.h"
19#include "../include/str_utils.h"
21#define INITIAL_CAPACITY 64
22#define ERR_BUF_SIZE 128
23#define MAX_FLAG_NAME_LEN 128
24#define MAX_DEFAULT_STR 64
62 FlagParser** subcommands;
66 void (*handler)(
void* user_data);
67 void (*pre_invoke)(
void* user_data);
69 char** positional_args;
73 char last_error[ERR_BUF_SIZE];
74 FlagParser* active_subcommand;
86static void* copy_default_value(Arena* arena,
FlagDataType type,
void* value_ptr) {
87 if (!value_ptr)
return NULL;
91 bool* copy = ARENA_ALLOC(arena,
bool);
92 if (copy) *copy = *(
bool*)value_ptr;
96 char* copy = ARENA_ALLOC(arena,
char);
97 if (copy) *copy = *(
char*)value_ptr;
101 char* str = *(
char**)value_ptr;
102 return str ? arena_strdup(arena, str) : NULL;
105 int8_t* copy = ARENA_ALLOC(arena, int8_t);
106 if (copy) *copy = *(int8_t*)value_ptr;
110 uint8_t* copy = ARENA_ALLOC(arena, uint8_t);
111 if (copy) *copy = *(uint8_t*)value_ptr;
115 int16_t* copy = ARENA_ALLOC(arena, int16_t);
116 if (copy) *copy = *(int16_t*)value_ptr;
120 uint16_t* copy = ARENA_ALLOC(arena, uint16_t);
121 if (copy) *copy = *(uint16_t*)value_ptr;
125 int32_t* copy = ARENA_ALLOC(arena, int32_t);
126 if (copy) *copy = *(int32_t*)value_ptr;
130 uint32_t* copy = ARENA_ALLOC(arena, uint32_t);
131 if (copy) *copy = *(uint32_t*)value_ptr;
135 int64_t* copy = ARENA_ALLOC(arena, int64_t);
136 if (copy) *copy = *(int64_t*)value_ptr;
140 uint64_t* copy = ARENA_ALLOC(arena, uint64_t);
141 if (copy) *copy = *(uint64_t*)value_ptr;
145 size_t* copy = ARENA_ALLOC(arena,
size_t);
146 if (copy) *copy = *(
size_t*)value_ptr;
150 float* copy = ARENA_ALLOC(arena,
float);
151 if (copy) *copy = *(
float*)value_ptr;
155 double* copy = ARENA_ALLOC(arena,
double);
156 if (copy) *copy = *(
double*)value_ptr;
171static void format_default_value(
FlagDataType type,
void* default_ptr,
char* buf,
size_t buf_size) {
172 if (!default_ptr || !buf || buf_size == 0) {
179 snprintf(buf, buf_size,
"%s", *(
bool*)default_ptr ?
"true" :
"false");
182 snprintf(buf, buf_size,
"'%c'", *(
char*)default_ptr);
185 char* str = (
char*)default_ptr;
186 snprintf(buf, buf_size,
"\"%s\"", str ? str :
"");
190 snprintf(buf, buf_size,
"%d", *(int8_t*)default_ptr);
193 snprintf(buf, buf_size,
"%u", *(uint8_t*)default_ptr);
196 snprintf(buf, buf_size,
"%d", *(int16_t*)default_ptr);
199 snprintf(buf, buf_size,
"%u", *(uint16_t*)default_ptr);
202 snprintf(buf, buf_size,
"%d", *(int32_t*)default_ptr);
205 snprintf(buf, buf_size,
"%u", *(uint32_t*)default_ptr);
208 snprintf(buf, buf_size,
"%lld", (
long long)*(int64_t*)default_ptr);
211 snprintf(buf, buf_size,
"%llu", (
unsigned long long)*(uint64_t*)default_ptr);
214 snprintf(buf, buf_size,
"%zu", *(
size_t*)default_ptr);
217 snprintf(buf, buf_size,
"%.6g", *(
float*)default_ptr);
220 snprintf(buf, buf_size,
"%.6g", *(
double*)default_ptr);
238FlagParser*
flag_parser_new(
const char* name,
const char* description) {
240 Arena* arena = arena_create(0);
242 perror(
"arena_create");
246 FlagParser* fp = ARENA_ALLOC_ZERO(arena, FlagParser);
248 perror(
"arena_alloc");
253 fp->name = arena_strdup(arena, name);
254 fp->description = arena_strdup(arena, description);
266 arena_destroy(fp->arena);
276 parser->footer = arena_strdup(parser->arena, footer);
289 fp->pre_invoke = pre_invoke;
316Flag*
flag_add(FlagParser* fp,
FlagDataType type,
const char* name,
char short_name,
const char* desc,
void* value_ptr,
318 if (!fp || !name || !value_ptr)
return NULL;
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);
326 memcpy(new_flags, fp->flags, fp->flag_count *
sizeof(Flag));
328 fp->flags = new_flags;
329 fp->flag_capacity = new_cap;
332 Flag* f = &fp->flags[fp->flag_count++];
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;
343 f->default_ptr = copy_default_value(fp->arena, type, value_ptr);
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);
372 if (fp->subcommands) {
373 memcpy(new_cmds, fp->subcommands, fp->cmd_count *
sizeof(FlagParser*));
375 fp->subcommands = new_cmds;
376 fp->cmd_capacity = new_cap;
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;
403 if (!fp || !fp->active_subcommand) {
407 FlagParser* sub = fp->active_subcommand;
411 pre_invoke(user_data);
416 sub->handler(user_data);
444 if (flag) flag->validator = validator;
455static void set_error(FlagParser* fp,
const char* fmt, ...) {
459 vsnprintf(fp->last_error, ERR_BUF_SIZE, fmt, args);
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];
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];
498static bool check_range_int(
long long val,
long long min,
long long max) {
return (val >= min && val <= max); }
506static bool check_range_uint(
unsigned long long val,
unsigned long long max) {
return (val <= max); }
516static FlagStatus parse_value(FlagParser* fp, Flag* flag,
const char* str) {
517 if (!str)
return FLAG_ERROR_MISSING_VALUE;
521 switch (flag->type) {
524 if (strcasecmp(str,
"false") == 0 || strcmp(str,
"0") == 0) val =
false;
525 *(
bool*)flag->value_ptr = val;
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];
537 *(
char**)flag->value_ptr = arena_strdup(fp->arena, str);
545 long long val = strtoll(str, &endptr, 10);
546 if (endptr == str || *endptr !=
'\0' || errno == ERANGE)
return FLAG_ERROR_INVALID_NUMBER;
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;
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;
562 *(int64_t*)flag->value_ptr = (int64_t)val;
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;
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;
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;
590 *(uint64_t*)flag->value_ptr = (uint64_t)val;
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;
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;
607 return FLAG_ERROR_INVALID_ARGUMENT;
634 if (!fp || argc < 1)
return FLAG_ERROR_INVALID_ARGUMENT;
637 bool end_of_opts =
false;
643 if (!end_of_opts && strcmp(arg,
"--") == 0) {
650 if (end_of_opts || arg[0] !=
'-') {
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];
657 return flag_parse(fp->active_subcommand, argc - i, argv + i);
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);
668 if (fp->positional_args) {
669 memcpy(new_pos, fp->positional_args, fp->pos_count *
sizeof(
char*));
671 fp->positional_args = new_pos;
672 fp->pos_capacity = new_cap;
674 fp->positional_args[fp->pos_count++] = arg;
680 if (strncmp(arg,
"--", 2) == 0) {
681 char* name_start = arg + 2;
682 if (strcmp(name_start,
"help") == 0) {
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;
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;
701 Flag* f = find_flag_long(fp, lookup_name);
703 set_error(fp,
"Unknown flag: --%s", lookup_name);
704 return FLAG_ERROR_UNKNOWN_FLAG;
707 f->is_present =
true;
709 if (f->type == TYPE_BOOL) {
711 if (val_str && (strcasecmp(val_str,
"false") == 0 || strcmp(val_str,
"0") == 0)) b =
false;
712 *(
bool*)f->value_ptr = b;
715 if (i + 1 < argc && argv[i + 1][0] !=
'-')
718 set_error(fp,
"Flag --%s requires a value", f->name);
719 return FLAG_ERROR_MISSING_VALUE;
724 set_error(fp,
"Invalid value for --%s: '%s' (Type mismatch or overflow)", f->name, val_str);
734 size_t len = strlen(arg);
735 for (
size_t k = 1; k < len; k++) {
737 Flag* f = find_flag_short(fp, c);
739 set_error(fp,
"Unknown short flag: -%c", c);
740 return FLAG_ERROR_UNKNOWN_FLAG;
742 f->is_present =
true;
744 if (f->type == TYPE_BOOL) {
745 *(
bool*)f->value_ptr =
true;
747 const char* val_str = NULL;
749 val_str = &arg[k + 1];
750 if (*val_str ==
'=') val_str++;
753 set_error(fp,
"Invalid value for -%c (Type mismatch or overflow)", c);
758 if (i + 1 < argc && argv[i + 1][0] !=
'-') {
762 set_error(fp,
"Invalid value for -%c (Type mismatch or overflow)", c);
766 set_error(fp,
"Flag -%c requires a value", c);
767 return FLAG_ERROR_MISSING_VALUE;
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;
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;
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;
835 if (!fp || argc < 1) {
836 return FLAG_ERROR_INVALID_ARGUMENT;
841 if (status != FLAG_OK) {
846 if (fp->pre_invoke) {
847 fp->pre_invoke(user_data);
851 FlagParser* target = fp;
852 while (target->active_subcommand) {
853 target = target->active_subcommand;
857 if (target->handler) {
858 target->handler(user_data);
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++) {
914 w += strlen(fp->flags[i].name) + 2;
916 if (fp->flags[i].type != TYPE_BOOL) {
917 w += 1 + strlen(type_to_str(fp->flags[i].type));
920 if (w > max_width) max_width = w;
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;
932 if (w > max_width) max_width = w;
940static void print_flag_row(Flag* f,
size_t max_width) {
946 pos += snprintf(left + pos,
sizeof(left) - (
size_t)pos,
" -%c, ", f->short_name);
948 pos += snprintf(left + pos,
sizeof(left) - (
size_t)pos,
" ");
952 pos += snprintf(left + pos,
sizeof(left) - (
size_t)pos,
"--%s", f->name);
955 if (f->type != TYPE_BOOL) {
956 pos += snprintf(left + pos,
sizeof(left) - (
size_t)pos,
"=%s", type_to_str(f->type));
960 printf(
"%-*s %s", (
int)max_width, left, f->description ? f->description :
"");
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);
978static void print_help_internal(FlagParser* fp) {
982 printf(
"\n%s\n", fp->description ? fp->description : fp->name);
984 printf(
"\nUsage:\n %s", fp->name);
985 if (fp->flag_count > 0) printf(
" [flags]");
986 if (fp->cmd_count > 0) printf(
" [command]");
990 if (fp->flag_count > 0) {
991 printf(
"\nFlags:\n");
992 size_t width = calculate_flag_width(fp);
993 if (width < 20) width = 20;
995 for (
size_t i = 0; i < fp->flag_count; i++) {
996 print_flag_row(&fp->flags[i], width);
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;
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 :
"");
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);
1031 FlagParser* leaf = fp;
1032 while (leaf->active_subcommand) {
1033 leaf = leaf->active_subcommand;
1036 print_help_internal(leaf);
1052 FlagParser* root = fp;
1054 if (root->active_subcommand) {
1055 root = fp->active_subcommand;
1056 while (root->active_subcommand) root = root->active_subcommand;
1058 return root->last_error;
1063 FlagParser* target = parser;
1064 while (target && target->active_subcommand) {
1065 target = target->active_subcommand;
1091 return (fp && i >= 0 && (
size_t)i < fp->pos_count) ? fp->positional_args[i] : NULL;
1110 Flag* f = find_flag_long(fp, name);
1111 return f ? f->is_present :
false;
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";
1162static void write_safe_str(FILE* f,
const char* str,
int quote_style) {
1163 if (!str || !f)
return;
1165 if (quote_style == 1) fputc(
'\'', f);
1166 if (quote_style == 2) fputc(
'"', f);
1168 for (
const char* p = str; *p; p++) {
1169 if (quote_style == 1 && *p ==
'\'') {
1171 fprintf(f,
"'\\''");
1172 }
else if (quote_style == 2 && (*p ==
'"' || *p ==
'\\' || *p ==
'$' || *p ==
'`')) {
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 ==
'?')) {
1187 if (quote_style == 1) fputc(
'\'', f);
1188 if (quote_style == 2) fputc(
'"', f);
1194static void write_shell_identifier(FILE* f,
const char* str) {
1195 if (!str || !f)
return;
1198 if (!isalpha(*str) && *str !=
'_') {
1202 for (
const char* p = str; *p; p++) {
1203 if (isalnum(*p) || *p ==
'_') {
1217static void write_bash_subcommand_cases(FILE* f, FlagParser* p,
const char* prefix) {
1221 char full_name[512];
1222 if (prefix && *prefix) {
1223 snprintf(full_name,
sizeof(full_name),
"%s_%s", prefix, p->name);
1225 snprintf(full_name,
sizeof(full_name),
"%s", p->name);
1229 if (p != _comp_ctx.root && (p->flag_count > 0 || p->cmd_count > 0)) {
1231 write_safe_str(f, p->name, 0);
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) {
1241 write_safe_str(f, flag->name, 0);
1242 if (flag->short_name && isprint(flag->short_name)) {
1244 fputc(flag->short_name, f);
1249 switch (flag->type) {
1251 fprintf(f,
" # File/directory completion\n");
1252 fprintf(f,
" COMPREPLY=( $(compgen -f -- \"$cur\") )\n");
1255 fprintf(f,
" # Value expected\n");
1256 fprintf(f,
" return 0\n");
1260 fprintf(f,
" return 0\n");
1261 fprintf(f,
" ;;\n");
1264 fprintf(f,
" esac\n\n");
1268 fprintf(f,
" local flags=\"");
1269 for (
size_t i = 0; i < p->flag_count; i++) {
1271 write_safe_str(f, p->flags[i].name, 0);
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);
1283 fprintf(f,
" COMPREPLY=( $(compgen -W \"$flags $subcommands\" -- \"$cur\") )\n");
1285 fprintf(f,
" COMPREPLY=( $(compgen -W \"$flags\" -- \"$cur\") )\n");
1288 fprintf(f,
" return 0\n");
1289 fprintf(f,
" ;;\n");
1293 for (
size_t i = 0; i < p->cmd_count; i++) {
1294 write_bash_subcommand_cases(f, p->subcommands[i], full_name);
1301static void write_bash_all_subcommands_list(FILE* f, FlagParser* p) {
1303 for (
size_t i = 0; i < p->cmd_count; i++) {
1304 write_safe_str(f, p->subcommands[i]->name, 0);
1306 write_bash_all_subcommands_list(f, p->subcommands[i]);
1313static void gen_bash_completion(FlagParser* fp, FILE* f) {
1314 if (!fp || !f)
return;
1316 char* bin_name = fp->name;
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");
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");
1328 fprintf(f,
" local global_flags=\"");
1329 if (fp->flag_count > 0) {
1330 for (
size_t i = 0; i < fp->flag_count; i++) {
1332 write_safe_str(f, fp->flags[i].name, 0);
1336 fprintf(f,
"--help\"\n");
1339 fprintf(f,
" local subcommands=\"");
1340 write_bash_all_subcommands_list(f, fp);
1341 fprintf(f,
"\"\n\n");
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) {
1350 write_safe_str(f, flag->name, 0);
1351 if (flag->short_name && isprint(flag->short_name)) {
1353 fputc(flag->short_name, f);
1358 if (flag->type == TYPE_STRING) {
1359 fprintf(f,
" COMPREPLY=( $(compgen -f -- \"$cur\") )\n");
1362 fprintf(f,
" return 0\n");
1363 fprintf(f,
" ;;\n");
1366 fprintf(f,
" esac\n\n");
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");
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);
1395 fprintf(f,
" COMPREPLY=( $(compgen -W \"$top_level_subs $global_flags\" -- \"$cur\") )\n");
1397 fprintf(f,
" COMPREPLY=( $(compgen -W \"$global_flags\" -- \"$cur\") )\n");
1399 fprintf(f,
" return 0\n");
1400 fprintf(f,
" fi\n\n");
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");
1413 fprintf(f,
" return 0\n");
1414 fprintf(f,
"}\n\n");
1416 fprintf(f,
"complete -F _");
1417 write_shell_identifier(f, bin_name);
1418 fprintf(f,
"_completion ");
1419 write_safe_str(f, bin_name, 0);
1428static void write_zsh_args(FILE* f, FlagParser* p,
int indent) {
1429 if (!p || !f)
return;
1431 for (
size_t i = 0; i < p->flag_count; i++) {
1432 Flag* flag = &p->flags[i];
1435 char desc[512] = {0};
1436 if (flag->description) {
1438 for (
const char* c = flag->description; *c && k < 500; c++) {
1440 if (*c ==
'[' || *c ==
']' || *c ==
'\'' || *c ==
'\\' || *c ==
':') {
1449 for (
int j = 0; j < indent; j++) fprintf(f,
" ");
1452 if (flag->type == TYPE_BOOL) {
1454 write_safe_str(f, flag->name, 0);
1455 fprintf(f,
"[%s]'", desc);
1458 const char* arg_type =
"value";
1459 switch (flag->type) {
1461 arg_type =
"file:_files";
1467 arg_type =
"integer";
1474 arg_type =
"unsigned integer";
1478 arg_type =
"number";
1486 write_safe_str(f, flag->name, 0);
1487 fprintf(f,
"[%s]:", desc);
1489 if (flag->type == TYPE_STRING) {
1490 fprintf(f,
":_files'");
1492 fprintf(f,
":%s:'", arg_type);
1496 fprintf(f,
" \\\n");
1503static void write_zsh_subcommand_cases(FILE* f, FlagParser* p,
int depth) {
1504 if (!p || p->cmd_count == 0)
return;
1506 for (
size_t i = 0; i < p->cmd_count; i++) {
1507 FlagParser* sub = p->subcommands[i];
1509 for (
int j = 0; j < depth; j++) fprintf(f,
" ");
1510 write_safe_str(f, sub->name, 0);
1513 for (
int j = 0; j < depth; j++) fprintf(f,
" ");
1514 fprintf(f,
" _arguments -C \\\n");
1517 write_zsh_args(f, sub, depth + 2);
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);
1527 if (sub->subcommands[k]->description) {
1528 write_safe_str(f, sub->subcommands[k]->description, 0);
1531 fprintf(f,
"))' \\\n");
1533 for (
int j = 0; j < depth + 2; j++) fprintf(f,
" ");
1534 fprintf(f,
"'*::arg:->args' \\\n");
1537 for (
int j = 0; j < depth; j++) fprintf(f,
" ");
1538 fprintf(f,
" && ret=0\n");
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");
1549 write_zsh_subcommand_cases(f, sub, depth + 4);
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");
1559 for (
int j = 0; j < depth; j++) fprintf(f,
" ");
1560 fprintf(f,
" ;;\n");
1567static void gen_zsh_completion(FlagParser* fp, FILE* f) {
1568 if (!fp || !f)
return;
1570 char* bin_name = fp->name;
1572 fprintf(f,
"#compdef ");
1573 write_safe_str(f, bin_name, 0);
1575 fprintf(f,
"# Generated by flags.c completion generator\n\n");
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");
1585 fprintf(f,
" _arguments -C \\\n");
1588 write_zsh_args(f, fp, 2);
1591 fprintf(f,
" '--help[Show help information]' \\\n");
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);
1600 if (fp->subcommands[i]->description) {
1601 write_safe_str(f, fp->subcommands[i]->description, 0);
1604 fprintf(f,
"))' \\\n");
1605 fprintf(f,
" '*::arg:->args' \\\n");
1608 fprintf(f,
" && ret=0\n\n");
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");
1616 write_zsh_subcommand_cases(f, fp, 4);
1618 fprintf(f,
" esac\n");
1619 fprintf(f,
" ;;\n");
1620 fprintf(f,
" esac\n\n");
1623 fprintf(f,
" return ret\n");
1624 fprintf(f,
"}\n\n");
1627 fprintf(f,
"compdef _");
1628 write_shell_identifier(f, bin_name);
1630 write_safe_str(f, bin_name, 0);
1637static void completion_handler(
void* user_data) {
1640 if (!_comp_ctx.shell || !*_comp_ctx.shell) {
1641 fprintf(stderr,
"Error: --shell is required (bash or zsh)\n");
1645 if (!_comp_ctx.root) {
1646 fprintf(stderr,
"Error: Internal error - no root parser available\n");
1651 if (_comp_ctx.output && *_comp_ctx.output) {
1652 out = fopen(_comp_ctx.output,
"w");
1654 fprintf(stderr,
"Error: Cannot open output file '%s': %s\n", _comp_ctx.output, strerror(errno));
1660 char shell_lower[32];
1662 for (i = 0; i <
sizeof(shell_lower) - 1 && _comp_ctx.shell[i]; i++) {
1663 shell_lower[i] = (char)tolower(_comp_ctx.shell[i]);
1665 shell_lower[i] =
'\0';
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);
1672 fprintf(stderr,
"Error: Unsupported shell '%s'. Supported: bash, zsh\n", _comp_ctx.shell);
1673 if (_comp_ctx.output) fclose(out);
1677 if (_comp_ctx.output && out != stdout) {
1678 if (fclose(out) != 0) {
1679 fprintf(stderr,
"Warning: Error closing output file: %s\n", strerror(errno));
1681 printf(
"Completion script written to: %s\n", _comp_ctx.output);
1694void flag_add_completion_cmd(FlagParser* fp) {
1698 _comp_ctx.root = fp;
1701 _comp_ctx.shell = NULL;
1702 _comp_ctx.output = NULL;
1704 FlagParser* cmd =
flag_add_subcommand(fp,
"completion",
"Generate shell completion scripts", completion_handler);
1707 fprintf(stderr,
"Warning: Failed to add completion subcommand\n");
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);
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)
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)
const char * flag_get_error(FlagParser *parser)
FlagParser * flag_active_subcommand(FlagParser *parser)
bool(* FlagValidator)(const void *value, const char **error_out)
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))