From ca8d76c6e81aef3651bd2dcae2f71a14e0fcca6c Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Tue, 20 Jan 2026 23:50:01 +0100 Subject: feat: add string argument parsing --- ls_args.h | 65 +++++++++++++++++++++++++++++++++++++++++------------------ tests/tests.c | 19 +++++++++++++++++ 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/ls_args.h b/ls_args.h index fc23134..780b3cf 100644 --- a/ls_args.h +++ b/ls_args.h @@ -18,7 +18,10 @@ typedef enum ls_args_mode { LS_ARG_REQUIRED = 1 } ls_args_mode; -typedef enum ls_args_type { LS_ARGS_TYPE_BOOL = 0 } ls_args_type; +typedef enum ls_args_type { + LS_ARGS_TYPE_BOOL = 0, + LS_ARGS_TYPE_STRING = 1 +} ls_args_type; typedef struct ls_args_arg { const char* short_opt; @@ -39,6 +42,8 @@ void ls_args_init(ls_args*); void ls_arg_bool(ls_args*, int* val, const char* short_opt, const char* long_opt, const char* help, ls_args_mode mode); +void ls_arg_string(ls_args*, const char** val, const char* short_opt, + const char* long_opt, const char* help, ls_args_mode mode); int ls_args_parse(ls_args*, int argc, char** argv); @@ -105,6 +110,16 @@ void _lsa_register(ls_args* a, void* val, ls_args_type type, arg->val_ptr = val; } +void ls_arg_bool(ls_args* a, int* val, const char* short_opt, + const char* long_opt, const char* help, ls_args_mode mode) { + _lsa_register(a, val, LS_ARGS_TYPE_BOOL, short_opt, long_opt, help, mode); +} + +void ls_arg_string(ls_args* a, const char** val, const char* short_opt, + const char* long_opt, const char* help, ls_args_mode mode) { + _lsa_register(a, val, LS_ARGS_TYPE_STRING, short_opt, long_opt, help, mode); +} + typedef enum _lsa_parsed_type { LS_ARGS_PARSED_ERROR = 0, LS_ARGS_PARSED_LONG = 1, @@ -148,7 +163,7 @@ _lsa_parsed _lsa_parse(const char* s) { goto end; } res.type = LS_ARGS_PARSED_LONG; - res.as.long_arg = &s[remaining]; + res.as.long_arg = &s[2]; } else { /* short opt */ size_t remaining = s_len - 1; @@ -159,7 +174,7 @@ _lsa_parsed _lsa_parse(const char* s) { goto end; } res.type = LS_ARGS_PARSED_SHORT; - res.as.short_args = &s[remaining]; + res.as.short_args = &s[1]; } } else { res.type = LS_ARGS_PARSED_POSITIONAL; @@ -170,20 +185,16 @@ end: return res; } -void ls_arg_bool(ls_args* a, int* val, const char* short_opt, - const char* long_opt, const char* help, ls_args_mode mode) { - _lsa_register(a, val, LS_ARGS_TYPE_BOOL, short_opt, long_opt, help, mode); -} - -void _lsa_apply(ls_args_arg* arg, _lsa_parsed* parsed, ls_args_arg** prev_arg) { - (void)parsed; - (void)prev_arg; +void _lsa_apply(ls_args_arg* arg, ls_args_arg** prev_arg) { switch (arg->type) { case LS_ARGS_TYPE_BOOL: *(int*)arg->val_ptr = 1; + *prev_arg = NULL; + break; + case LS_ARGS_TYPE_STRING: + *prev_arg = arg; break; } - *prev_arg = arg; } #include @@ -196,7 +207,7 @@ int ls_args_parse(ls_args* a, int argc, char** argv) { assert(argv != NULL); for (i = 1; i < argc; ++i) { _lsa_parsed parsed = _lsa_parse(argv[i]); - if (prev_arg && prev_arg->type != LS_ARGS_TYPE_BOOL) { + if (prev_arg) { if (parsed.type != LS_ARGS_PARSED_POSITIONAL) { /* argument for the previous param expected, but none given */ /* TODO: Error properly */ @@ -204,20 +215,29 @@ int ls_args_parse(ls_args* a, int argc, char** argv) { prev_arg->long_opt); return 0; } - _lsa_apply(prev_arg, &parsed, NULL); + switch (prev_arg->type) { + case LS_ARGS_TYPE_BOOL: + assert(!"UNREACHABLE"); + abort(); + break; + case LS_ARGS_TYPE_STRING: + *(const char**)prev_arg->val_ptr = parsed.as.positional; + break; + } + prev_arg = NULL; continue; } switch (parsed.type) { case LS_ARGS_PARSED_ERROR: /* TODO: Return/print/save error */ + /* TODO: Error properly */ fprintf(stderr, "Failed to parse '%s'\n", parsed.as.erroneous); return 0; case LS_ARGS_PARSED_LONG: for (k = 0; k < a->args_len; ++k) { if (a->args[k].long_opt != NULL - && strcmp(argv[i], a->args[k].long_opt) == 0) { - /* match! */ - _lsa_apply(&a->args[k], &parsed, &prev_arg); + && strcmp(parsed.as.long_arg, a->args[k].long_opt) == 0) { + _lsa_apply(&a->args[k], &prev_arg); break; } } @@ -226,11 +246,16 @@ int ls_args_parse(ls_args* a, int argc, char** argv) { const char* args = parsed.as.short_args; while (*args) { char arg = *args++; + if (prev_arg) { + /* TODO: Error properly */ + fprintf(stderr, "Expected argument for '-%s'\n", + prev_arg->short_opt); + return 0; + } for (k = 0; k < a->args_len; ++k) { const char* opt = a->args[k].short_opt; if (opt != NULL && opt[0] == arg) { - /* match! */ - _lsa_apply(&a->args[k], &parsed, &prev_arg); + _lsa_apply(&a->args[k], &prev_arg); break; } } @@ -239,7 +264,7 @@ int ls_args_parse(ls_args* a, int argc, char** argv) { } case LS_ARGS_PARSED_STOP: case LS_ARGS_PARSED_POSITIONAL: - assert(!"NOT IMPLEMENTED"); + assert(!"UNREACHABLE"); break; } } diff --git a/tests/tests.c b/tests/tests.c index 0e644e8..450c6bd 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -23,5 +23,24 @@ TEST_CASE(basic_args) { return 0; } +TEST_CASE(string_args) { + const char* input = NULL; + const char* output = NULL; + int verbose = 0; + ls_args args; + char* argv[] = { "./program", "--input", "file.txt", "-o", "output.txt", "-v", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_arg_string(&args, &input, "i", "input", "Input file path", 0); + ls_arg_string(&args, &output, "o", "output", "Output file path", 0); + ls_arg_bool(&args, &verbose, "v", "verbose", "Verbose output", 0); + ASSERT(ls_args_parse(&args, argc, argv)); + ASSERT(strcmp(input, "file.txt") == 0); + ASSERT(strcmp(output, "output.txt") == 0); + ASSERT_EQ(verbose, 1, "%d"); + ls_args_free(&args); + return 0; +} TEST_MAIN -- cgit