Improved support for CLI arg parsing
This commit is contained in:
parent
b138893c40
commit
0622f758f7
257
compile.c
257
compile.c
@ -3466,53 +3466,42 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type)
|
||||
{
|
||||
auto fn_info = Match(fn_type, FunctionType);
|
||||
|
||||
env_t *main_env = fresh_scope(env);
|
||||
|
||||
CORD code = CORD_EMPTY;
|
||||
binding_t *usage_binding = get_binding(env, "_USAGE");
|
||||
CORD usage_code = usage_binding ? usage_binding->code : "usage";
|
||||
binding_t *help_binding = get_binding(env, "_HELP");
|
||||
CORD help_code = help_binding ? help_binding->code : usage_code;
|
||||
|
||||
if (!fn_info->args) {
|
||||
CORD code = "Text_t usage = Texts(Text(\"Usage: \"), Text$from_str(argv[0]), Text(\" [--help]\"));\n";
|
||||
code = CORD_all(code, "if (argc > 1 && streq(argv[1], \"--help\")) {\n",
|
||||
"Text$print(stdout, ", help_code, ");\n"
|
||||
"puts(\"\");\n"
|
||||
"return 0;\n}\n");
|
||||
|
||||
return CORD_all(
|
||||
code,
|
||||
"if (argc > 1)\n"
|
||||
"errx(1, \"This program doesn't take any arguments.\\n%k\", &", usage_code, ");\n",
|
||||
fn_name, "();\n");
|
||||
}
|
||||
|
||||
CORD code = CORD_all(
|
||||
"#define USAGE_ERR(fmt, ...) errx(1, fmt \"\\n%s\" __VA_OPT__(,) __VA_ARGS__, Text$as_c_string(", usage_code, "))\n"
|
||||
"#define IS_FLAG(str, flag) (strncmp(str, flag, strlen(flag) == 0 && (str[strlen(flag)] == 0 || str[strlen(flag)] == '=')) == 0)\n");
|
||||
|
||||
env_t *main_env = fresh_scope(env);
|
||||
|
||||
bool explicit_help_flag = false;
|
||||
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
|
||||
if (streq(arg->name, "help")) {
|
||||
explicit_help_flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!usage_binding) {
|
||||
bool explicit_help_flag = false;
|
||||
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
|
||||
if (streq(arg->name, "help")) {
|
||||
explicit_help_flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CORD usage = explicit_help_flag ? CORD_EMPTY : " [--help]";
|
||||
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
|
||||
usage = CORD_cat(usage, " ");
|
||||
type_t *t = get_arg_type(main_env, arg);
|
||||
CORD flag = CORD_replace(arg->name, "_", "-");
|
||||
if (arg->default_val) {
|
||||
if (t->tag == BoolType)
|
||||
usage = CORD_all(usage, "[--", flag, "|--no-", flag, "]");
|
||||
else
|
||||
usage = CORD_all(usage, "[--", flag, "=", get_flag_options(t, "|"), "]");
|
||||
if (arg->default_val || arg->type->tag == OptionalType) {
|
||||
if (strlen(arg->name) == 1) {
|
||||
if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
|
||||
usage = CORD_all(usage, "[-", flag, "]");
|
||||
else
|
||||
usage = CORD_all(usage, "[-", flag, " ", get_flag_options(t, "|"), "]");
|
||||
} else {
|
||||
if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
|
||||
usage = CORD_all(usage, "[--", flag, "]");
|
||||
else
|
||||
usage = CORD_all(usage, "[--", flag, "=", get_flag_options(t, "|"), "]");
|
||||
}
|
||||
} else {
|
||||
if (t->tag == BoolType)
|
||||
usage = CORD_all(usage, "[--", flag, "|--no-", flag, "]");
|
||||
usage = CORD_all(usage, "<--", flag, "|--no-", flag, ">");
|
||||
else if (t->tag == EnumType)
|
||||
usage = CORD_all(usage, get_flag_options(t, "|"));
|
||||
else if (t->tag == ArrayType)
|
||||
@ -3525,188 +3514,32 @@ CORD compile_cli_arg_call(env_t *env, CORD fn_name, type_t *fn_type)
|
||||
usage == CORD_EMPTY ? CORD_EMPTY : CORD_all(", Text(", CORD_quoted(usage), ")"), ");\n");
|
||||
}
|
||||
|
||||
// Declare args:
|
||||
|
||||
int num_args = 0;
|
||||
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
|
||||
type_t *t = get_arg_type(main_env, arg);
|
||||
assert(arg->name);
|
||||
type_t *optional = t->tag == OptionalType ? t : Type(OptionalType, .type=t);
|
||||
type_t *non_optional = t->tag == OptionalType ? Match(t, OptionalType)->type : t;
|
||||
code = CORD_all(
|
||||
code, compile_declaration(optional, CORD_cat("$", arg->name)), " = ", compile_null(non_optional), ";\n");
|
||||
set_binding(main_env, arg->name, new(binding_t, .type=optional, .code=CORD_cat("$", arg->name)));
|
||||
}
|
||||
// Provide --flags:
|
||||
code = CORD_all(code, "Text_t flag;\n"
|
||||
"for (int i = 1; i < argc; ) {\n"
|
||||
"if (streq(argv[i], \"--\")) {\n"
|
||||
"argv[i] = NULL;\n"
|
||||
"break;\n"
|
||||
"}\n"
|
||||
"if (strncmp(argv[i], \"--\", 2) != 0) {\n++i;\ncontinue;\n}\n");
|
||||
|
||||
if (!explicit_help_flag) {
|
||||
code = CORD_all(code, "else if (pop_flag(argv, &i, \"help\", &flag)) {\n"
|
||||
"Text$print(stdout, ", help_code, ");\n"
|
||||
"puts(\"\");\n"
|
||||
"return 0;\n"
|
||||
"}\n");
|
||||
}
|
||||
|
||||
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
|
||||
type_t *t = get_arg_type(main_env, arg);
|
||||
type_t *non_optional = t->tag == OptionalType ? Match(t, OptionalType)->type : t;
|
||||
CORD flag = CORD_replace(arg->name, "_", "-");
|
||||
switch (non_optional->tag) {
|
||||
case BoolType: {
|
||||
CORD is_null = check_null(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name));
|
||||
code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n"
|
||||
"if (flag.length != 0) {\n",
|
||||
"$", arg->name, " = Bool$from_text(flag);\n"
|
||||
"if (", is_null, ") \n"
|
||||
"USAGE_ERR(\"Invalid argument for '--", flag, "'\");\n",
|
||||
"} else {\n",
|
||||
"$", arg->name, " = yes;\n",
|
||||
"}\n"
|
||||
"}\n");
|
||||
break;
|
||||
}
|
||||
case TextType: {
|
||||
code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n",
|
||||
"$", arg->name, " = ", streq(Match(non_optional, TextType)->lang, "Path") ? "Path$cleanup(flag)" : "flag",";\n",
|
||||
"}\n");
|
||||
break;
|
||||
}
|
||||
case ArrayType: {
|
||||
if (Match(non_optional, ArrayType)->item_type->tag != TextType)
|
||||
compiler_err(NULL, NULL, NULL, "Main function has unsupported argument type: %T (only arrays of Text are supported)", non_optional);
|
||||
code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n",
|
||||
"$", arg->name, " = Text$split(flag, Pattern(\",\"));\n");
|
||||
if (streq(Match(Match(non_optional, ArrayType)->item_type, TextType)->lang, "Path")) {
|
||||
code = CORD_all(code, "for (int64_t j = 0; j < $", arg->name, ".length; j++)\n"
|
||||
"*(Path_t*)($", arg->name, ".data + j*$", arg->name, ".stride) "
|
||||
"= Path$cleanup(*(Path_t*)($", arg->name, ".data + j*$", arg->name, ".stride));\n");
|
||||
}
|
||||
code = CORD_all(code, "}\n");
|
||||
break;
|
||||
}
|
||||
case BigIntType: case IntType: case NumType: {
|
||||
CORD is_null = check_null(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name));
|
||||
CORD type_name = type_to_cord(non_optional);
|
||||
code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n",
|
||||
"if (flag.length == 0)\n"
|
||||
"USAGE_ERR(\"No value provided for '--", flag, "'\");\n"
|
||||
"$", arg->name, " = ", type_name, "$from_text(flag);\n"
|
||||
"if (", is_null, ")\n"
|
||||
"USAGE_ERR(\"Invalid value provided for '--", flag, "'\");\n",
|
||||
"}\n");
|
||||
break;
|
||||
}
|
||||
case EnumType: {
|
||||
env_t *enum_env = Match(non_optional, EnumType)->env;
|
||||
code = CORD_all(code, "else if (pop_flag(argv, &i, \"", flag, "\", &flag)) {\n",
|
||||
"if (flag.length == 0)\n"
|
||||
"USAGE_ERR(\"No value provided for '--", flag, "'\");\n");
|
||||
for (tag_t *tag = Match(non_optional, EnumType)->tags; tag; tag = tag->next) {
|
||||
if (tag->type && Match(tag->type, StructType)->fields)
|
||||
compiler_err(NULL, NULL, NULL,
|
||||
"The type %T has enum fields with member values, which is not yet supported for command line arguments.");
|
||||
binding_t *b = get_binding(enum_env, tag->name);
|
||||
code = CORD_all(code,
|
||||
"if (Text$equal_ignoring_case(flag, Text(\"", tag->name, "\"))) {\n"
|
||||
"$", arg->name, " = ", b->code, ";\n",
|
||||
"} else ");
|
||||
}
|
||||
code = CORD_all(code, "USAGE_ERR(\"Invalid value provided for '--", flag, "', valid values are: ",
|
||||
get_flag_options(non_optional, ", "), "\");\n",
|
||||
"}\n");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
compiler_err(NULL, NULL, NULL, "Main function has unsupported argument type: %T", t);
|
||||
}
|
||||
}
|
||||
|
||||
code = CORD_all(
|
||||
code, "else {\n"
|
||||
"USAGE_ERR(\"Unrecognized argument: %s\", argv[i]);\n"
|
||||
"}\n"
|
||||
"}\n"
|
||||
"int i = 1;\n"
|
||||
"while (i < argc && argv[i] == NULL)\n"
|
||||
"++i;\n");
|
||||
|
||||
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
|
||||
type_t *t = get_arg_type(main_env, arg);
|
||||
type_t *non_optional = t->tag == OptionalType ? Match(t, OptionalType)->type : t;
|
||||
CORD is_null = check_null(Type(OptionalType, .type=non_optional), CORD_all("$", arg->name));
|
||||
code = CORD_all(code, "if (", is_null, ") {\n");
|
||||
if (non_optional->tag == ArrayType) {
|
||||
if (Match(non_optional, ArrayType)->item_type->tag != TextType)
|
||||
compiler_err(NULL, NULL, NULL, "Main function has unsupported argument type: %T (only arrays of Text are supported)", non_optional);
|
||||
|
||||
code = CORD_all(
|
||||
code, "$", arg->name, " = (Array_t){};\n"
|
||||
"for (; i < argc; i++) {\n"
|
||||
"if (argv[i]) {\n");
|
||||
if (streq(Match(Match(non_optional, ArrayType)->item_type, TextType)->lang, "Path")) {
|
||||
code = CORD_all(code, "Path_t arg = Path$cleanup(Text$from_str(argv[i]));\n");
|
||||
} else {
|
||||
code = CORD_all(code, "Text_t arg = Text$from_str(argv[i]);\n");
|
||||
}
|
||||
code = CORD_all(code, "Array$insert(&$", arg->name, ", &arg, I(0), sizeof(Text_t));\n"
|
||||
"argv[i] = NULL;\n"
|
||||
"}\n"
|
||||
"}\n");
|
||||
} else if (arg->default_val) {
|
||||
code = CORD_all(code, "$", arg->name, " = ", compile(main_env, arg->default_val), ";\n");
|
||||
type_t *opt_type = arg->type->tag == OptionalType ? arg->type : Type(OptionalType, .type=arg->type);
|
||||
code = CORD_all(code, compile_declaration(opt_type, CORD_all("$", arg->name)));
|
||||
if (arg->default_val) {
|
||||
CORD default_val = compile(env, arg->default_val);
|
||||
if (arg->type->tag != OptionalType)
|
||||
default_val = promote_to_optional(arg->type, default_val);
|
||||
code = CORD_all(code, " = ", default_val);
|
||||
} else {
|
||||
code = CORD_all(
|
||||
code,
|
||||
"if (i < argc) {");
|
||||
if (non_optional->tag == TextType) {
|
||||
code = CORD_all(code, "$", arg->name, " = Text$from_str(argv[i]);\n");
|
||||
if (streq(Match(non_optional, TextType)->lang, "Path"))
|
||||
code = CORD_all(code, "$", arg->name, " = Path$cleanup($", arg->name, ");\n");
|
||||
|
||||
} else if (non_optional->tag == EnumType) {
|
||||
env_t *enum_env = Match(non_optional, EnumType)->env;
|
||||
for (tag_t *tag = Match(non_optional, EnumType)->tags; tag; tag = tag->next) {
|
||||
if (tag->type && Match(tag->type, StructType)->fields)
|
||||
compiler_err(NULL, NULL, NULL,
|
||||
"The type %T has enum fields with member values, which is not yet supported for command line arguments.",
|
||||
non_optional);
|
||||
binding_t *b = get_binding(enum_env, tag->name);
|
||||
code = CORD_all(code,
|
||||
"if (strcasecmp(argv[i], \"", tag->name, "\") == 0) {\n"
|
||||
"$", arg->name, " = ", b->code, ";\n",
|
||||
"} else ");
|
||||
}
|
||||
code = CORD_all(code, "USAGE_ERR(\"Invalid value provided for '--", arg->name, "', valid values are: ",
|
||||
get_flag_options(non_optional, ", "), "\");\n");
|
||||
} else {
|
||||
code = CORD_all(
|
||||
code,
|
||||
"$", arg->name, " = ", type_to_cord(non_optional), "$from_text(Text$from_str(argv[i]))", ";\n"
|
||||
"if (", is_null, ")\n"
|
||||
"USAGE_ERR(\"Unable to parse this argument as a ", type_to_cord(non_optional), ": %s\", argv[i]);\n");
|
||||
}
|
||||
code = CORD_all(
|
||||
code,
|
||||
"argv[i++] = NULL;\n"
|
||||
"while (i < argc && argv[i] == NULL)\n"
|
||||
"++i;\n");
|
||||
if (t->tag != OptionalType) {
|
||||
code = CORD_all(code, "} else {\n"
|
||||
"USAGE_ERR(\"Required argument '", arg->name, "' was not provided!\");\n");
|
||||
}
|
||||
code = CORD_all(code, "}\n");
|
||||
code = CORD_all(code, " = ", compile_null(arg->type));
|
||||
}
|
||||
code = CORD_all(code, "}\n");
|
||||
code = CORD_all(code, ";\n");
|
||||
num_args += 1;
|
||||
}
|
||||
|
||||
|
||||
code = CORD_all(code, "for (; i < argc; i++) {\n"
|
||||
"if (argv[i])\nUSAGE_ERR(\"Unexpected argument: %s\", argv[i]);\n}\n");
|
||||
code = CORD_all(code, "tomo_parse_args(", usage_code, ", ", help_code, ", ",
|
||||
heap_strf("%d", num_args), ", (cli_arg_t[]){");
|
||||
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
|
||||
code = CORD_all(code, "{", CORD_quoted(CORD_replace(arg->name, "_", "-")), ", ",
|
||||
(arg->default_val || arg->type->tag == OptionalType) ? "false" : "true", ", ",
|
||||
compile_type_info(env, arg->type),
|
||||
", &", CORD_all("$", arg->name), "}, ");
|
||||
}
|
||||
code = CORD_all(code, "}, argc, argv);\n");
|
||||
|
||||
code = CORD_all(code, fn_name, "(");
|
||||
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
|
||||
|
287
stdlib/stdlib.c
287
stdlib/stdlib.c
@ -10,11 +10,15 @@
|
||||
#include <sys/random.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "bools.h"
|
||||
#include "files.h"
|
||||
#include "integers.h"
|
||||
#include "optionals.h"
|
||||
#include "metamethods.h"
|
||||
#include "patterns.h"
|
||||
#include "paths.h"
|
||||
#include "siphash.h"
|
||||
#include "stdlib.h"
|
||||
#include "tables.h"
|
||||
#include "text.h"
|
||||
#include "util.h"
|
||||
@ -36,6 +40,289 @@ public void tomo_init(void)
|
||||
errx(1, "Couldn't set printf specifier");
|
||||
}
|
||||
|
||||
static bool parse_single_arg(const TypeInfo *info, char *arg, void *dest)
|
||||
{
|
||||
while (info->tag == OptionalInfo)
|
||||
info = info->OptionalInfo.type;
|
||||
|
||||
if (info == &Int$info) {
|
||||
OptionalInt_t parsed = Int$from_str(arg);
|
||||
if (parsed.small != 0)
|
||||
*(OptionalInt_t*)dest = parsed;
|
||||
return parsed.small != 0;
|
||||
} else if (info == &Int64$info) {
|
||||
OptionalInt64_t parsed = Int64$from_text(Text$from_str(arg));
|
||||
if (!parsed.is_null)
|
||||
*(OptionalInt64_t*)dest = parsed;
|
||||
return !parsed.is_null;
|
||||
} else if (info == &Int32$info) {
|
||||
OptionalInt32_t parsed = Int32$from_text(Text$from_str(arg));
|
||||
if (!parsed.is_null)
|
||||
*(OptionalInt32_t*)dest = parsed;
|
||||
return !parsed.is_null;
|
||||
} else if (info == &Int16$info) {
|
||||
OptionalInt16_t parsed = Int16$from_text(Text$from_str(arg));
|
||||
if (!parsed.is_null)
|
||||
*(OptionalInt16_t*)dest = parsed;
|
||||
return !parsed.is_null;
|
||||
} else if (info == &Int8$info) {
|
||||
OptionalInt8_t parsed = Int8$from_text(Text$from_str(arg));
|
||||
if (!parsed.is_null)
|
||||
*(OptionalInt8_t*)dest = parsed;
|
||||
return !parsed.is_null;
|
||||
} else if (info == &Bool$info) {
|
||||
OptionalBool_t parsed = Bool$from_text(Text$from_str(arg));
|
||||
if (parsed != NULL_BOOL)
|
||||
*(OptionalBool_t*)dest = parsed;
|
||||
return parsed != NULL_BOOL;
|
||||
} else if (info == &Num$info) {
|
||||
OptionalNum_t parsed = Num$from_text(Text$from_str(arg));
|
||||
if (!isnan(parsed))
|
||||
*(OptionalNum_t*)dest = parsed;
|
||||
return !isnan(parsed);
|
||||
} else if (info == &Num32$info) {
|
||||
OptionalNum32_t parsed = Num32$from_text(Text$from_str(arg));
|
||||
if (!isnan(parsed))
|
||||
*(OptionalNum32_t*)dest = parsed;
|
||||
return !isnan(parsed);
|
||||
} else if (info == &Path$info) {
|
||||
Path_t path = Text$from_str(arg);
|
||||
if (Text$equal_values(path, Path("~"))) {
|
||||
path = Path("~/");
|
||||
} else if (Text$equal_values(path, Path("."))) {
|
||||
path = Path("./");
|
||||
} else if (Text$equal_values(path, Path(".."))) {
|
||||
path = Path("../");
|
||||
} else if (!Text$starts_with(path, Text("./"))
|
||||
&& !Text$starts_with(path, Text("../"))
|
||||
&& !Text$starts_with(path, Text("/"))
|
||||
&& !Text$starts_with(path, Text("~/"))) {
|
||||
path = Text$concat(Text("./"), path);
|
||||
}
|
||||
*(OptionalText_t*)dest = path;
|
||||
return true;
|
||||
} else if (info->tag == TextInfo) {
|
||||
*(OptionalText_t*)dest = Text$from_str(arg);
|
||||
return true;
|
||||
} else {
|
||||
Text_t t = generic_as_text(NULL, false, info);
|
||||
errx(1, "Unsupported type for argument parsing: %k", &t);
|
||||
}
|
||||
}
|
||||
|
||||
static Array_t parse_array(const TypeInfo *item_info, int n, char *args[])
|
||||
{
|
||||
int64_t padded_size = item_info->size;
|
||||
if ((padded_size % item_info->align) > 0)
|
||||
padded_size = padded_size + item_info->align - (padded_size % item_info->align);
|
||||
|
||||
Array_t items = {
|
||||
.stride=padded_size,
|
||||
.length=n,
|
||||
.data=GC_MALLOC((size_t)(padded_size*n)),
|
||||
};
|
||||
for (int i = 0; i < n; i++) {
|
||||
bool success = parse_single_arg(item_info, args[i], items.data + items.stride*i);
|
||||
if (!success)
|
||||
errx(1, "Couldn't parse argument: %s", args[i]);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wstack-protector"
|
||||
public void tomo_parse_args(Text_t usage, Text_t help, int spec_len, cli_arg_t spec[spec_len], int argc, char *argv[])
|
||||
{
|
||||
bool populated_args[spec_len] = {};
|
||||
bool used_args[argc] = {};
|
||||
for (int i = 1; i < argc; ) {
|
||||
if (argv[i][0] == '-' && argv[i][1] == '-') {
|
||||
if (argv[i][2] == '\0') { // "--" signals the rest of the arguments are literal
|
||||
used_args[i] = true;
|
||||
i += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int s = 0; s < spec_len; s++) {
|
||||
const TypeInfo *non_opt_type = spec[s].type;
|
||||
while (non_opt_type->tag == OptionalInfo)
|
||||
non_opt_type = non_opt_type->OptionalInfo.type;
|
||||
|
||||
if (non_opt_type == &Bool$info
|
||||
&& strncmp(argv[i], "--no-", strlen("--no-")) == 0
|
||||
&& strcmp(argv[i] + strlen("--no-"), spec[s].name) == 0) {
|
||||
*(OptionalBool_t*)spec[s].dest = false;
|
||||
populated_args[s] = true;
|
||||
used_args[i] = true;
|
||||
goto next_arg;
|
||||
}
|
||||
|
||||
if (strncmp(spec[s].name, argv[i] + 2, strlen(spec[s].name)) != 0)
|
||||
continue;
|
||||
|
||||
char after_name = argv[i][2+strlen(spec[s].name)];
|
||||
if (after_name == '\0') { // --foo val
|
||||
used_args[i] = true;
|
||||
if (non_opt_type->tag == ArrayInfo || non_opt_type->tag == TableInfo) {
|
||||
int num_args = 0;
|
||||
while (i + 1 + num_args < argc) {
|
||||
if (argv[i+1+num_args][0] == '-')
|
||||
break;
|
||||
used_args[i+1+num_args] = true;
|
||||
num_args += 1;
|
||||
}
|
||||
populated_args[s] = true;
|
||||
const TypeInfo *item_type = non_opt_type->tag == ArrayInfo ? non_opt_type->ArrayInfo.item : non_opt_type->TableInfo.key;
|
||||
Array_t items = parse_array(item_type, num_args, &argv[i+1]);
|
||||
if (non_opt_type->tag == ArrayInfo) {
|
||||
*(OptionalArray_t*)spec[s].dest = items;
|
||||
} else {
|
||||
*(OptionalTable_t*)spec[s].dest = Table$from_entries(items, non_opt_type);
|
||||
}
|
||||
} else if (non_opt_type == &Bool$info) { // --flag
|
||||
populated_args[s] = true;
|
||||
*(OptionalBool_t*)spec[s].dest = true;
|
||||
} else {
|
||||
used_args[i+1] = true;
|
||||
populated_args[s] = parse_single_arg(spec[s].type, argv[i+1], spec[s].dest);
|
||||
if (!populated_args[s])
|
||||
errx(1, "Couldn't parse argument: %s %s\n%k", argv[i], argv[i+1], &usage);
|
||||
}
|
||||
goto next_arg;
|
||||
} else if (after_name == '=') { // --foo=val
|
||||
used_args[i] = true;
|
||||
populated_args[s] = parse_single_arg(spec[s].type, 2 + argv[i] + strlen(spec[s].name) + 1, spec[s].dest);
|
||||
if (!populated_args[s])
|
||||
errx(1, "Couldn't parse argument: %s\n%k", argv[i], &usage);
|
||||
goto next_arg;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (streq(argv[i], "--help")) {
|
||||
say(help, true);
|
||||
exit(0);
|
||||
}
|
||||
errx(1, "Unrecognized argument: %s\n%k", argv[i], &usage);
|
||||
} else if (argv[i][0] == '-' && argv[i][1] && argv[i][1] != '-') { // Single flag args
|
||||
used_args[i] = true;
|
||||
for (char *f = argv[i] + 1; *f; f++) {
|
||||
for (int s = 0; s < spec_len; s++) {
|
||||
if (spec[s].name[0] != *f || strlen(spec[s].name) > 1)
|
||||
continue;
|
||||
|
||||
const TypeInfo *non_opt_type = spec[s].type;
|
||||
while (non_opt_type->tag == OptionalInfo)
|
||||
non_opt_type = non_opt_type->OptionalInfo.type;
|
||||
|
||||
if (non_opt_type->tag == ArrayInfo || non_opt_type->tag == TableInfo) {
|
||||
if (f[1]) errx(1, "No value provided for -%c\n%k", *f, &usage);
|
||||
int num_args = 0;
|
||||
while (i + 1 + num_args < argc) {
|
||||
if (argv[i+1+num_args][0] == '-')
|
||||
break;
|
||||
used_args[i+1+num_args] = true;
|
||||
num_args += 1;
|
||||
}
|
||||
populated_args[s] = true;
|
||||
const TypeInfo *item_type = non_opt_type->tag == ArrayInfo ? non_opt_type->ArrayInfo.item : non_opt_type->TableInfo.key;
|
||||
Array_t items = parse_array(item_type, num_args, &argv[i+1]);
|
||||
if (non_opt_type->tag == ArrayInfo) {
|
||||
*(OptionalArray_t*)spec[s].dest = items;
|
||||
} else {
|
||||
*(OptionalTable_t*)spec[s].dest = Table$from_entries(items, non_opt_type);
|
||||
}
|
||||
} else if (non_opt_type == &Bool$info) { // -f
|
||||
populated_args[s] = true;
|
||||
*(OptionalBool_t*)spec[s].dest = true;
|
||||
} else {
|
||||
if (f[1] || i+1 >= argc) errx(1, "No value provided for -%c\n%k", *f, &usage);
|
||||
used_args[i+1] = true;
|
||||
populated_args[s] = parse_single_arg(spec[s].type, argv[i+1], spec[s].dest);
|
||||
if (!populated_args[s])
|
||||
errx(1, "Couldn't parse argument: %s %s\n%k", argv[i], argv[i+1], &usage);
|
||||
}
|
||||
goto next_flag;
|
||||
}
|
||||
|
||||
if (*f == 'h') {
|
||||
say(help, true);
|
||||
exit(0);
|
||||
}
|
||||
errx(1, "Unrecognized flag: -%c\n%k", *f, &usage);
|
||||
next_flag:;
|
||||
}
|
||||
} else {
|
||||
// Handle positional args later
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
next_arg:
|
||||
while (used_args[i] && i < argc)
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Get remaining positional arguments
|
||||
bool ignore_dashes = false;
|
||||
for (int i = 1, s = 0; i < argc; i++) {
|
||||
if (!ignore_dashes && streq(argv[i], "--")) {
|
||||
ignore_dashes = true;
|
||||
continue;
|
||||
}
|
||||
if (used_args[i]) continue;
|
||||
|
||||
while (populated_args[s]) {
|
||||
next_non_bool_flag:
|
||||
++s;
|
||||
if (s >= spec_len)
|
||||
errx(1, "Extra argument: %s\n%k", argv[i], &usage);
|
||||
}
|
||||
|
||||
const TypeInfo *non_opt_type = spec[s].type;
|
||||
while (non_opt_type->tag == OptionalInfo)
|
||||
non_opt_type = non_opt_type->OptionalInfo.type;
|
||||
|
||||
// You can't specify boolean flags positionally
|
||||
if (non_opt_type == &Bool$info)
|
||||
goto next_non_bool_flag;
|
||||
|
||||
if (non_opt_type->tag == ArrayInfo || non_opt_type->tag == TableInfo) {
|
||||
int num_args = 0;
|
||||
while (i + num_args < argc) {
|
||||
if (!ignore_dashes && argv[i+num_args][0] == '-')
|
||||
break;
|
||||
used_args[i+num_args] = true;
|
||||
num_args += 1;
|
||||
}
|
||||
populated_args[s] = true;
|
||||
const TypeInfo *item_type = non_opt_type->tag == ArrayInfo ? non_opt_type->ArrayInfo.item : non_opt_type->TableInfo.key;
|
||||
Array_t items = parse_array(item_type, num_args, &argv[i]);
|
||||
if (non_opt_type->tag == ArrayInfo) {
|
||||
*(OptionalArray_t*)spec[s].dest = items;
|
||||
} else {
|
||||
*(OptionalTable_t*)spec[s].dest = Table$from_entries(items, non_opt_type);
|
||||
}
|
||||
} else {
|
||||
populated_args[s] = parse_single_arg(spec[s].type, argv[i], spec[s].dest);
|
||||
}
|
||||
|
||||
if (!populated_args[s])
|
||||
errx(1, "Invalid value for %s: %s\n%k", spec[s].name, argv[i], &usage);
|
||||
}
|
||||
|
||||
for (int s = 0; s < spec_len; s++) {
|
||||
if (!populated_args[s] && spec[s].required) {
|
||||
if (spec[s].type->tag == ArrayInfo)
|
||||
*(OptionalArray_t*)spec[s].dest = (Array_t){};
|
||||
else if (spec[s].type->tag == TableInfo)
|
||||
*(OptionalTable_t*)spec[s].dest = (Table_t){};
|
||||
else
|
||||
errx(1, "The required argument '%s' was not provided\n%k", spec[s].name, &usage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print_stack_trace(FILE *out, int start, int stop)
|
||||
{
|
||||
// Print stack trace:
|
||||
|
@ -12,7 +12,15 @@
|
||||
|
||||
extern bool USE_COLOR;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
bool required;
|
||||
const TypeInfo *type;
|
||||
void *dest;
|
||||
} cli_arg_t;
|
||||
|
||||
void tomo_init(void);
|
||||
void tomo_parse_args(Text_t usage, Text_t help, int spec_len, cli_arg_t spec[spec_len], int argc, char *argv[]);
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
_Noreturn void fail(const char *fmt, ...);
|
||||
__attribute__((format(printf, 4, 5)))
|
||||
|
Loading…
Reference in New Issue
Block a user