diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-10-18 19:02:07 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-10-18 19:02:07 -0400 |
| commit | 5bdf96234a388cbd3854747b0667620ebb60ccdf (patch) | |
| tree | e4ab4055e0190f4a080b69fcd933cd8ca6146260 /src/stdlib/cli.c | |
| parent | 82e3c05d547b2372ca4033c68469479260092b5a (diff) | |
Improved CLI parsing and add CString.join()
Diffstat (limited to 'src/stdlib/cli.c')
| -rw-r--r-- | src/stdlib/cli.c | 112 |
1 files changed, 56 insertions, 56 deletions
diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c index bf461105..c3def8c0 100644 --- a/src/stdlib/cli.c +++ b/src/stdlib/cli.c @@ -122,8 +122,6 @@ void tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const ch before_double_dash = List$slice(args, I(1), I(i)); after_double_dash = List$slice(args, I(i + 2), I(-1)); break; - } else if (arg[0] == '-') { - print_err("Unrecognized argument: ", arg); } } @@ -133,6 +131,7 @@ void tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const ch pop_cli_positional(&before_double_dash, spec[i].name, spec[i].dest, spec[i].type, false); } } + for (int i = 0; i < spec_len && after_double_dash.length > 0; i++) { if (!spec[i].populated) { spec[i].populated = pop_cli_positional(&after_double_dash, spec[i].name, spec[i].dest, spec[i].type, true); @@ -145,63 +144,68 @@ void tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const ch List_t remaining_args = List$concat(before_double_dash, after_double_dash, sizeof(const char *)); if (remaining_args.length > 0) { - print_err("Unknown flag values: ", generic_as_text(&remaining_args, true, List$info(&CString$info))); + print_err("Unknown flag values: ", CString$join(" ", remaining_args)); } } -static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const TypeInfo_t *type, bool allow_dashes) { +static List_t parse_arg_list(List_t args, const char *flag, void *dest, const TypeInfo_t *type, bool allow_dashes) { if (type->tag == ListInfo) { - void *item = GC_MALLOC((size_t)type->ListInfo.item->size); - int64_t n = 0; - while (n < (int64_t)args.length) { - const char *arg = *(const char **)(args.data + n * args.stride); + void *item = type->ListInfo.item->size ? GC_MALLOC((size_t)type->ListInfo.item->size) : NULL; + while (args.length > 0) { + const char *arg = *(const char **)args.data; if (arg[0] == '-' && !allow_dashes) break; - n += parse_arg_list(List$slice(args, I(n + 1), I(-1)), flag, item, type->ListInfo.item, allow_dashes); + args = parse_arg_list(args, flag, item, type->ListInfo.item, allow_dashes); List$insert(dest, item, I(0), type->ListInfo.item->size); } - return n; + return args; } else if (type->tag == TableInfo) { - // Arguments take the form key=value, with a guarantee that there is an '=' - void *key = GC_MALLOC((size_t)type->TableInfo.key->size); - void *value = GC_MALLOC((size_t)type->TableInfo.value->size); - int64_t n = 0; - while (n < (int64_t)args.length) { - const char *arg = *(const char **)(args.data + n * args.stride); + // Arguments take the form key:value + void *key = type->TableInfo.key->size ? GC_MALLOC((size_t)type->TableInfo.key->size) : NULL; + void *value = type->TableInfo.value->size ? GC_MALLOC((size_t)type->TableInfo.value->size) : NULL; + while (args.length > 0) { + const char *arg = *(const char **)args.data; if (arg[0] == '-' && !allow_dashes) break; - const char *colon = strchr(arg, ':'); - if (!colon) break; - const char *key_arg = String(string_slice(arg, (size_t)(colon - arg))); - n += parse_arg_list(List(key_arg), flag, key, type->TableInfo.key, allow_dashes); - const char *value_arg = colon + 1; - n += parse_arg_list(List(value_arg), flag, value, type->TableInfo.value, allow_dashes); - Table$set(dest, key, value, type); + if (type->TableInfo.value->size == 0) { + List_t key_arg = List(arg); + (void)parse_arg_list(key_arg, flag, key, type->TableInfo.key, allow_dashes); + Table$set(dest, key, NULL, type); + args = List$from(args, I(2)); + } else { + const char *colon = strchr(arg, ':'); + if (!colon) break; + List_t key_arg = List(String(string_slice(arg, (size_t)(colon - arg)))); + (void)parse_arg_list(key_arg, flag, key, type->TableInfo.key, allow_dashes); + List_t value_arg = List(colon + 1); + (void)parse_arg_list(value_arg, flag, value, type->TableInfo.value, allow_dashes); + Table$set(dest, key, value, type); + args = List$from(args, I(2)); + } } - return n; + return args; } else if (type->tag == StructInfo) { - int64_t n = 0; for (int i = 0; i < type->StructInfo.num_fields; i++) { const TypeInfo_t *field_type = type->StructInfo.fields[i].type; if (field_type->align > 0 && (size_t)dest % (size_t)field_type->align > 0) dest += (size_t)field_type->align - ((size_t)dest % (size_t)field_type->align); - n += parse_arg_list(List$slice(args, I(n + 1), I(-1)), String(flag, ".", type->StructInfo.fields[i].name), - dest, field_type, allow_dashes); + args = parse_arg_list(args, String(flag, ".", type->StructInfo.fields[i].name), dest, field_type, + allow_dashes); dest += field_type->size; } - return n; + return args; } if (args.length == 0) print_err("No value provided for flag: ", flag); const char *arg = *(const char **)args.data; - int64_t n = 1; if (type->tag == OptionalInfo) { const TypeInfo_t *nonnull = type->OptionalInfo.type; if (streq(arg, "none")) { if (nonnull == &Num$info) *(double *)dest = (double)NAN; else if (nonnull == &Num32$info) *(float *)dest = (float)NAN; else memset(dest, 0, (size_t)type->size); + return List$from(args, I(2)); } else { - n = parse_arg_list(args, flag, dest, nonnull, allow_dashes); + args = parse_arg_list(args, flag, dest, nonnull, allow_dashes); if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true; else if (nonnull == &Int32$info) ((OptionalInt32_t *)dest)->has_value = true; else if (nonnull == &Int16$info) ((OptionalInt16_t *)dest)->has_value = true; @@ -209,8 +213,13 @@ static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const T else if (nonnull == &Byte$info) ((OptionalByte_t *)dest)->has_value = true; else if (nonnull->tag == StructInfo && nonnull != &Path$info) *(bool *)(dest + nonnull->size) = true; else print_err("Unsupported type: ", generic_as_text(NULL, true, nonnull)); + return args; } - } else if (type == &CString$info) { + } + + List_t rest_of_args = List$from(args, I(2)); + + if (type == &CString$info) { *(const char **)dest = arg; } else if (type == &Int$info) { OptionalInt_t parsed = Int$from_str(arg); @@ -251,9 +260,9 @@ static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const T } else if (type->tag == PointerInfo) { // For pointers, we can just allocate memory for the value and then parse the value void *value = GC_MALLOC((size_t)type->PointerInfo.pointed->size); - n = parse_arg_list(args, flag, value, type->PointerInfo.pointed, allow_dashes); + args = parse_arg_list(args, flag, value, type->PointerInfo.pointed, allow_dashes); *(void **)dest = value; - return n; + return args; } else if (type == &Path$info) { *(Path_t *)dest = Path$from_str(arg); } else if (type->tag == TextInfo) { @@ -269,16 +278,15 @@ static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const T *(int32_t *)dest = (t + 1); // Simple tag (no associated data): - if (!named.type || (named.type->tag == StructInfo && named.type->StructInfo.num_fields == 0)) return n; + if (!named.type || (named.type->tag == StructInfo && named.type->StructInfo.num_fields == 0)) + return rest_of_args; dest += sizeof(int32_t); if (named.type->align > 0 && (size_t)dest % (size_t)named.type->align > 0) dest += (size_t)named.type->align - ((size_t)dest % (size_t)named.type->align); - n += parse_arg_list(List$slice(args, I(n + 1), I(-1)), String(flag, ".", named.name), dest, named.type, - allow_dashes); - return n; + return parse_arg_list(rest_of_args, String(flag, ".", named.name), dest, named.type, allow_dashes); } } print_err("Invalid enum name for ", type->EnumInfo.name, ": ", arg, @@ -287,7 +295,7 @@ static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const T Text_t t = generic_as_text(NULL, false, type); print_err("Unsupported type for argument parsing: ", t); } - return n; + return rest_of_args; } bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, const TypeInfo_t *type) { @@ -305,9 +313,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c // Case: --flag values... if (i + 1 >= (int64_t)args->length) print_err("No value provided for flag: ", flag); List_t values = List$slice(*args, I(i + 2), I(-1)); - int64_t n = parse_arg_list(values, flag, dest, type, false); - if (n == 0) print_err("No value provided for flag: ", flag); - List$remove_at(args, I(i + 1), I(n + 1), sizeof(const char *)); + *args = parse_arg_list(values, flag, dest, type, false); return true; } else if (starts_with(arg + 2, flag) && arg[2 + strlen(flag)] == '=') { // Case: --flag=... @@ -323,9 +329,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c } else { values = List(arg_value); } - if (parse_arg_list(values, flag, dest, type, false) == 0) - print_err("No value provided for flag: ", flag); - List$remove_at(args, I(i + 1), I(1), sizeof(const char *)); + *args = parse_arg_list(values, flag, dest, type, false); return true; } } else if (short_flag && arg[0] == '-' && arg[1] != '-' && strchr(arg + 1, short_flag)) { @@ -346,8 +350,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c // Case: -f=value values = List(arg_value); } - if (parse_arg_list(values, flag, dest, type, false) == 0) - print_err("No value provided for flag: -", short_str); + values = parse_arg_list(values, flag, dest, type, false); if (loc > arg + 1) { // Case: -abcdef=... -> -abcde @@ -363,17 +366,17 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c // Case: -...f value... if (i + 1 >= (int64_t)args->length) print_err("No value provided for flag: -", short_str); List_t values = List$slice(*args, I(i + 2), I(-1)); - int64_t n = parse_arg_list(values, flag, dest, type, false); - if (n == 0) print_err("No value provided for flag: -", short_str); + List_t remaining_values = parse_arg_list(values, flag, dest, type, false); if (loc == arg + 1) { // Case: -f values... - List$remove_at(args, I(i + 1), I(n + 1), sizeof(const char *)); + *args = List$concat(List$to(*args, I(i)), remaining_values, sizeof(const char *)); } else { // Case: -abcdef values... -> -abcde char *remainder = String(string_slice(arg, (size_t)(loc - arg))); if unlikely (args->data_refcount > 0) List$compact(args, sizeof(const char *)); - *(const char **)(args->data + i * args->stride) = remainder; - List$remove_at(args, I(i + 2), I(n), sizeof(const char *)); + *args = List$concat(List$to(*args, I(i)), + List$concat(List(remainder), remaining_values, sizeof(const char *)), + sizeof(const char *)); } return true; } else { @@ -391,8 +394,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c // Case: -fVALUE values = List(arg_value); } - if (parse_arg_list(values, flag, dest, type, false) == 0) - print_err("No value provided for flag: -", short_str); + (void)parse_arg_list(values, flag, dest, type, false); if (loc > arg + 1) { // Case: -abcdefVALUE -> -abcde; // NOTE: adding a semicolon means that `-ab1 2` won't parse as b=1, then a=2 @@ -415,8 +417,6 @@ bool pop_cli_positional(List_t *args, const char *flag, void *dest, const TypeIn print_err("No value provided for flag: ", flag); return false; } - int64_t n = parse_arg_list(*args, flag, dest, type, allow_dashes); - if (n == 0) print_err("No value provided for flag: ", flag); - List$remove_at(args, I(1), I(n), sizeof(const char *)); + *args = parse_arg_list(*args, flag, dest, type, allow_dashes); return true; } |
