aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLion Kortlepel <[email protected]>2026-01-22 23:18:16 +0100
committerLion Kortlepel <[email protected]>2026-01-22 23:18:16 +0100
commit2d2be7e2b38031f7cd826f78543b18a287423ad7 (patch)
tree2bf0eba1995000e8d95cffae4101486541ce6eeb
parent36fdeca43eab4439a117d4c912f87e704eaa9cd5 (diff)
downloadargs-2d2be7e2b38031f7cd826f78543b18a287423ad7.tar.zst
args-2d2be7e2b38031f7cd826f78543b18a287423ad7.zip
fix!: reach 96% line coverage, add tests, fix bugs
-rw-r--r--.gitignore1
-rw-r--r--Makefile18
-rw-r--r--examples/basic_example.c17
-rw-r--r--ls_args.h269
-rw-r--r--tests/ls_test.h16
-rw-r--r--tests/tests.c286
6 files changed, 513 insertions, 94 deletions
diff --git a/.gitignore b/.gitignore
index 45b6c28..e406c09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ tests/tests
*.o
compile_commands.json
coverage/
+examples/basic_example
diff --git a/Makefile b/Makefile
index 9381484..28834f2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,18 +1,28 @@
# CAUTION: This Makefile builds ONLY the tests.
# To use this library, see ls_test.h or README.md.
+CFLAGS ?= -fsanitize=address,undefined
+CFLAGS += -I.
+
+all: tests/tests examples/basic_example
+
tests/tests: ls_args.o tests/tests.c tests/ls_test.h
- $(CC) -o $@ ls_args.o tests/tests.c -Itests -I. -ggdb
+ $(CC) -o $@ ls_args.o tests/tests.c -Itests -ggdb $(CFLAGS)
# Usually you wouldn't do this, but for tests we want this compiled with the
# most pedantic settings.
# Dont use this.
ls_args.o: ls_args.h
- $(CC) -c -x c -o $@ $^ -Wall -Wextra -Wpedantic -Werror -std=c89 -ggdb \
+ echo -e "#include <stddef.h>\nvoid* test_realloc(void*, size_t);" >.test.h
+ cat .test.h ls_args.h >.ls_args_test.c
+ rm .test.h
+ $(CC) -c -x c -o $@ .ls_args_test.c -Wall -Wextra -Wpedantic -Werror -std=c89 -ggdb \
-Wno-error=pragma-once-outside-header \
- -I. \
-DLS_ARGS_IMPLEMENTATION \
- -Wno-pragma-once-outside-header
+ -DLS_REALLOC=test_realloc \
+ -Wno-pragma-once-outside-header \
+ $(CFLAGS)
+ rm .ls_args_test.c
.PHONY: clean
diff --git a/examples/basic_example.c b/examples/basic_example.c
new file mode 100644
index 0000000..293d5c5
--- /dev/null
+++ b/examples/basic_example.c
@@ -0,0 +1,17 @@
+#define LS_ARGS_IMPLEMENTATION
+#include "ls_args.h"
+
+int main(int argc, char** argv) {
+ ls_args args;
+ int help = 0;
+ const char* outfile = "out.txt";
+
+ ls_args_init(&args);
+ ls_args_bool(&args, &help, "h", "help", "Prints help", 0);
+ ls_args_string(&args, &outfile, "o", "out", "Specify the outfile, default 'out.txt'", 0);
+ if (!ls_args_parse(&args, argc, argv)) {
+ printf("Error: %s\n%s\n", args.last_error, ls_args_help(&args));
+ }
+ ls_args_free(&args);
+ return 0;
+}
diff --git a/ls_args.h b/ls_args.h
index 780b3cf..50f8f7d 100644
--- a/ls_args.h
+++ b/ls_args.h
@@ -11,8 +11,6 @@
#define LS_FREE free
#endif
-/* Yes the naming is a little inconsistent, but "arg optional" reads better than
- * "args mode optional" */
typedef enum ls_args_mode {
LS_ARG_OPTIONAL = 0,
LS_ARG_REQUIRED = 1
@@ -33,28 +31,85 @@ typedef struct ls_args_arg {
} ls_args_arg;
typedef struct ls_args {
+ /* The last error, if any. Might be dynamically allocated; if so, it's
+ * free'd with `ls_args_free` automatically. Always a valid, printable
+ * string. */
+ char* last_error;
+
+ /* don't use the following fields outside the library */
ls_args_arg* args;
- int args_len;
- int args_cap;
+ size_t args_len;
+ size_t args_cap;
+
+ /* some bookkeeping -- these are used to free dynamically allocated memory
+ * for help or errors cleanly on `ls_args_free`. */
+ void* allocated_error;
+ void* allocated_help;
} ls_args;
+/* Zero-initializes the arguments, does not allocate */
void ls_args_init(ls_args*);
-void ls_arg_bool(ls_args*, int* val, const char* short_opt,
+/* The following functions register arguments. Upon a call to `ls_args_parse`,
+ * the given `val` parameter is filled. The `val` pointer must never be NULL.
+ *
+ * ONE of `short_opt` and `long_opt` may be NULL, if only a short- or long
+ * version should exist. The `help` string may be null, but is used to construct
+ * help with `ls_args_help`.
+ *
+ * In your short and long opts, you *can* have `-` or `--`, but you don't need
+ * them and, really, you should not use them. For example "h" or "help" are
+ * valid short- and long-opts respectively.
+ *
+ * You can call ls_args_* functions with the same `val` pointer, if multiple
+ * arguments should affect the same memory, however the order of evaluation (and
+ * thus the order of which they may overwrite each other) is the same order as
+ * the registration.
+ *
+ * BE AWARE that, if an argument is not present, the corresponding `val` is NOT
+ * touched. This means that, if you initialize a bool with `true` and then parse
+ * the args, and the corresponding flag is not present, the flag will not be set
+ * to `false` (it will instead stay untouched). This allows you to set defaults
+ * for the case in which the argument isn't present.
+ */
+
+/* A "flag", aka a boolean argument. If the argument is present, `*val` is set
+ * to 1, otherwise it's left untouched.
+ * Can fail if the allocator fails. `args.last_error` is set on failure. */
+int ls_args_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,
+/* An argument which requires a string parameter, for example `--file
+ * hello.txt`. Can fail if the allocator fails. `args.last_error` is set on
+ * failure. */
+int ls_args_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);
+/* Does all the heavy lifting. Assumes that `argv` has `argc` elements. NULL
+ * termination of the `argv` array doesn't matter, but null-termination of each
+ * individual string is required of course.
+ *
+ * Returns 1 on success, 0 on failure (boolean behavior).
+ * On failure, the `args.last_error` is set to a human-readable string. */
+int ls_args_parse(ls_args* args, int argc, char** argv);
+/* Same as args.last_error. */
+char* ls_args_get_error(ls_args*);
+/* Constructs a help message from the arguments registered on the args struct
+ * via `ls_args_{bool, string, ...} functions.
+ * The string is statically allocated and only valid until `ls_args_help` is
+ * called again. */
char* ls_args_help(ls_args*);
+/* Frees all memory allocated in the args. */
void ls_args_free(ls_args*);
+/* Define this in exactly ONE source file, or in an object file compiled
+ * separately with -DLS_ARGS_IMPLEMENTATION. */
#ifdef LS_ARGS_IMPLEMENTATION
#include <assert.h>
-#include <limits.h>
+#include <stdint.h>
+#include <stdio.h> /* for sprintf */
#include <string.h>
/* 0 on failure, 1 on success */
@@ -63,9 +118,11 @@ static int _lsa_add(ls_args* a, ls_args_arg** arg) {
assert(arg != NULL);
if (a->args_len + 1 > a->args_cap) {
ls_args_arg* new_args;
- int new_cap = a->args_cap + a->args_cap / 2 + 8;
- if (new_cap > INT_MAX / (int)sizeof(*a->args)) {
- /* int overflow */
+ size_t new_cap = a->args_cap + a->args_cap / 2 + 8;
+
+ size_t max_items = SIZE_MAX / sizeof(*a->args);
+ if (new_cap > max_items) {
+ /* would overflow size_t */
return 0;
}
new_args = LS_REALLOC(a->args, new_cap * sizeof(*new_args));
@@ -80,27 +137,37 @@ static int _lsa_add(ls_args* a, ls_args_arg** arg) {
return 1;
}
-void ls_args_init(ls_args* a) { memset(a, 0, sizeof(*a)); }
+void ls_args_init(ls_args* a) {
+ memset(a, 0, sizeof(*a));
+ a->last_error = "Success";
+}
-void _lsa_register(ls_args* a, void* val, ls_args_type type,
+int _lsa_register(ls_args* a, void* val, ls_args_type type,
const char* short_opt, const char* long_opt, const char* help,
ls_args_mode mode) {
ls_args_arg* arg;
+ int ret;
assert(a != NULL);
assert(val != NULL);
/* only one can be NULL, not both, but neither have to be NULL */
assert(short_opt != NULL || long_opt != NULL);
+ /* remove preceding dashes for later matching */
+ if (long_opt)
+ while (*long_opt == '-')
+ long_opt++;
+ /* remove preceding dashes for later matching */
+ if (short_opt)
+ while (*short_opt == '-')
+ short_opt++;
/* if short_opt isn't null, it must be 1 char */
assert(short_opt == NULL || strlen(short_opt) == 1);
- assert(_lsa_add(a, &arg));
+ ret = _lsa_add(a, &arg);
+ if (ret == 0) {
+ a->last_error = "Allocation failure";
+ return 0;
+ }
/* TODO: sanity check that there are no dashes in there, because that would
* be a misuse of the API. */
- /* remove preceding dashes for later matching */
- while (*long_opt == '-')
- long_opt++;
- /* remove preceding dashes for later matching */
- while (*short_opt == '-')
- short_opt++;
/* the rest may be NULL */
arg->type = type;
arg->short_opt = short_opt;
@@ -108,16 +175,19 @@ void _lsa_register(ls_args* a, void* val, ls_args_type type,
arg->help = help;
arg->mode = mode;
arg->val_ptr = val;
+ return 1;
}
-void ls_arg_bool(ls_args* a, int* val, const char* short_opt,
+int ls_args_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);
+ return _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,
+int ls_args_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);
+ return _lsa_register(
+ a, val, LS_ARGS_TYPE_STRING, short_opt, long_opt, help, mode);
}
typedef enum _lsa_parsed_type {
@@ -142,7 +212,7 @@ typedef struct _lsa_parsed {
} as;
} _lsa_parsed;
-_lsa_parsed _lsa_parse(const char* s) {
+static _lsa_parsed _lsa_parse(const char* s) {
size_t s_len = strlen(s);
_lsa_parsed res;
assert(s != NULL);
@@ -166,13 +236,7 @@ _lsa_parsed _lsa_parse(const char* s) {
res.as.long_arg = &s[2];
} else {
/* short opt */
- size_t remaining = s_len - 1;
- if (remaining == 0) {
- /* shouldn't be possible due to earlier checks */
- res.type = LS_ARGS_PARSED_ERROR;
- res.as.erroneous = s;
- goto end;
- }
+ /* guaranteed to be the right size due to earlier checks */
res.type = LS_ARGS_PARSED_SHORT;
res.as.short_args = &s[1];
}
@@ -197,11 +261,70 @@ void _lsa_apply(ls_args_arg* arg, ls_args_arg** prev_arg) {
}
}
-#include <stdio.h>
+static int _lsa_parse_long(
+ ls_args* a, _lsa_parsed* parsed, ls_args_arg** prev_arg) {
+ int found = 0;
+ size_t k;
+ for (k = 0; k < a->args_len; ++k) {
+ if (a->args[k].long_opt != NULL
+ && strcmp(parsed->as.long_arg, a->args[k].long_opt) == 0) {
+ _lsa_apply(&a->args[k], prev_arg);
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ const size_t len = 32 + strlen(parsed->as.erroneous);
+ a->allocated_error = LS_REALLOC(a->allocated_error, len);
+ memset(a->allocated_error, 0, len);
+ sprintf(a->allocated_error, "Invalid argument '--%s'",
+ parsed->as.erroneous);
+ a->last_error = a->allocated_error;
+ return 0;
+ }
+ return 1;
+}
+
+static int _lsa_parse_short(
+ ls_args* a, _lsa_parsed* parsed, ls_args_arg** prev_arg) {
+ const char* args = parsed->as.short_args;
+ while (*args) {
+ char arg = *args++;
+ int found = 0;
+ size_t k;
+ if (*prev_arg) {
+ const size_t len = 128 + strlen((*prev_arg)->short_opt);
+ a->allocated_error = LS_REALLOC(a->allocated_error, len);
+ memset(a->allocated_error, 0, len);
+ sprintf(a->allocated_error,
+ "Expected argument following '-%s', instead got another "
+ "argument '-%c'",
+ (*prev_arg)->short_opt, arg);
+ a->last_error = a->allocated_error;
+ return 0;
+ }
+ for (k = 0; k < a->args_len; ++k) {
+ const char* opt = a->args[k].short_opt;
+ if (opt != NULL && opt[0] == arg) {
+ _lsa_apply(&a->args[k], prev_arg);
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ const size_t len = 32;
+ a->allocated_error = LS_REALLOC(a->allocated_error, len);
+ memset(a->allocated_error, 0, len);
+ sprintf(a->allocated_error, "Invalid argument '-%c'", arg);
+ a->last_error = a->allocated_error;
+ return 0;
+ }
+ }
+ return 1;
+}
int ls_args_parse(ls_args* a, int argc, char** argv) {
int i;
- int k;
ls_args_arg* prev_arg = NULL;
assert(a != NULL);
assert(argv != NULL);
@@ -210,64 +333,60 @@ int ls_args_parse(ls_args* a, int argc, char** argv) {
if (prev_arg) {
if (parsed.type != LS_ARGS_PARSED_POSITIONAL) {
/* argument for the previous param expected, but none given */
- /* TODO: Error properly */
- fprintf(stderr, "Expected argument for '--%s'\n",
- prev_arg->long_opt);
+ const size_t len = 64 + strlen(prev_arg->long_opt);
+ a->allocated_error = LS_REALLOC(a->allocated_error, len);
+ memset(a->allocated_error, 0, len);
+ sprintf(a->allocated_error,
+ "Expected argument following '--%s'", prev_arg->long_opt);
+ a->last_error = a->allocated_error;
return 0;
}
- switch (prev_arg->type) {
- case LS_ARGS_TYPE_BOOL:
- assert(!"UNREACHABLE");
- abort();
- break;
- case LS_ARGS_TYPE_STRING:
+ if (prev_arg->type == 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);
+ case LS_ARGS_PARSED_ERROR: {
+ const size_t len = 32 + strlen(parsed.as.erroneous);
+ a->allocated_error = LS_REALLOC(a->allocated_error, len);
+ memset(a->allocated_error, 0, len);
+ sprintf(a->allocated_error, "Invalid argument '%s'",
+ parsed.as.erroneous);
+ a->last_error = a->allocated_error;
return 0;
- case LS_ARGS_PARSED_LONG:
- for (k = 0; k < a->args_len; ++k) {
- if (a->args[k].long_opt != NULL
- && strcmp(parsed.as.long_arg, a->args[k].long_opt) == 0) {
- _lsa_apply(&a->args[k], &prev_arg);
- break;
- }
+ }
+ case LS_ARGS_PARSED_LONG: {
+ if (!_lsa_parse_long(a, &parsed, &prev_arg)) {
+ return 0;
}
break;
+ }
case LS_ARGS_PARSED_SHORT: {
- 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) {
- _lsa_apply(&a->args[k], &prev_arg);
- break;
- }
- }
+ if (!_lsa_parse_short(a, &parsed, &prev_arg)) {
+ return 0;
}
break;
}
case LS_ARGS_PARSED_STOP:
+ /* TODO */
+ break;
case LS_ARGS_PARSED_POSITIONAL:
assert(!"UNREACHABLE");
break;
}
}
+ if (prev_arg) {
+ /* argument for the previous param expected, but none given */
+ const size_t len = 64 + strlen(prev_arg->long_opt);
+ a->allocated_error = LS_REALLOC(a->allocated_error, len);
+ memset(a->allocated_error, 0, len);
+ sprintf(a->allocated_error, "Expected argument following '--%s'",
+ prev_arg->long_opt);
+ a->last_error = a->allocated_error;
+ return 0;
+ }
return 1;
}
@@ -276,12 +395,20 @@ char* ls_args_help(ls_args* a) {
return "help!";
}
+char* ls_args_get_error(ls_args* a) { return a->last_error; }
+
void ls_args_free(ls_args* a) {
if (a) {
LS_FREE(a->args);
a->args = NULL;
a->args_cap = 0;
a->args_len = 0;
+
+ LS_FREE(a->allocated_error);
+ a->allocated_error = NULL;
+ a->last_error = "";
+ LS_FREE(a->allocated_help);
+ a->allocated_help = NULL;
}
}
#endif
diff --git a/tests/ls_test.h b/tests/ls_test.h
index 5dda4aa..31d87f8 100644
--- a/tests/ls_test.h
+++ b/tests/ls_test.h
@@ -1,6 +1,6 @@
/* Lion's Standard (LS) test harness.
*
- * Version: 1.1
+ * Version: 1.2
* Website: https://libls.org
* Repo: https://github.com/libls/test
* SPDX-License-Identifier: MIT
@@ -96,6 +96,8 @@
_func += 6; \
fprintf(stderr, "%s: FAILED: %s (%s:%d)\n", _func, #cond, \
__FILE__, __LINE__); \
+ ++lst_fail; \
+ return 1; \
} \
} while (0)
@@ -115,7 +117,6 @@
++lst_fail; \
return 1; \
} \
- ++lst_ok; \
} while (0)
#define ASSERT_NEQ(a, b, fmt) \
@@ -132,7 +133,6 @@
++lst_fail; \
return 1; \
} \
- ++lst_ok; \
} while (0)
#define ASSERT_LT(a, b, fmt) \
@@ -149,7 +149,6 @@
++lst_fail; \
return 1; \
} \
- ++lst_ok; \
} while (0)
#define ASSERT_LE(a, b, fmt) \
@@ -166,7 +165,6 @@
++lst_fail; \
return 1; \
} \
- ++lst_ok; \
} while (0)
#define ASSERT_GT(a, b, fmt) \
@@ -183,7 +181,6 @@
++lst_fail; \
return 1; \
} \
- ++lst_ok; \
} while (0)
#define ASSERT_GE(a, b, fmt) \
@@ -200,7 +197,6 @@
++lst_fail; \
return 1; \
} \
- ++lst_ok; \
} while (0)
#define LS_CAT2(a, b) a##b
#define LS_CAT(a, b) LS_CAT2(a, b)
@@ -217,7 +213,6 @@ extern lst_func* lst_funcs;
extern int lst_n;
extern int lst_cap;
extern int lst_fail;
-extern int lst_ok;
void lst_reg(lst_func f);
@@ -229,7 +224,6 @@ lst_func* lst_funcs;
int lst_n;
int lst_cap;
int lst_fail = 0;
-int lst_ok = 0;
void lst_reg(lst_func f) {
if (lst_n == lst_cap) {
@@ -276,8 +270,8 @@ static int ls_test_main(int argc, char** argv) {
}
end:
- fprintf(stderr, "%d succeeded, %d failed, %d total\n", lst_ok, lst_fail,
- lst_ok + lst_fail);
+ fprintf(stderr, "%d succeeded, %d failed, %d total\n", lst_n - lst_fail,
+ lst_fail, lst_n);
free(lst_funcs);
if (lst_fail > 0) {
diff --git a/tests/tests.c b/tests/tests.c
index 450c6bd..8b22e06 100644
--- a/tests/tests.c
+++ b/tests/tests.c
@@ -1,6 +1,22 @@
+#include <stdint.h>
+#include <limits.h>
#define LS_TEST_IMPLEMENTATION
#include "ls_test.h"
+int fail_alloc = 0;
+
+void* test_realloc(void* p, size_t size) {
+ if (fail_alloc) {
+ return NULL;
+ }
+ return realloc(p, size);
+}
+
+/* this is also set in the Makefile because of the way this is built for tests.
+ * It requires a bit of magic because of the way we do things.
+ */
+#define LS_REALLOC test_realloc
+
#include "ls_args.h"
TEST_CASE(basic_args) {
@@ -12,10 +28,201 @@ TEST_CASE(basic_args) {
int argc = sizeof(argv) / sizeof(*argv) - 1;
ls_args_init(&args);
- ls_arg_bool(&args, &help, "h", "help", "Provides help", 0);
- ls_arg_bool(&args, &test, "t", "test", "A test argument", 0);
- ls_arg_bool(&args, &no, "n", "nope", "An argument that isn't present", 0);
- ASSERT(ls_args_parse(&args, argc, argv));
+ ls_args_bool(&args, &help, "h", "help", "Provides help", 0);
+ ls_args_bool(&args, &test, "t", "test", "A test argument", 0);
+ ls_args_bool(&args, &no, "n", "nope", "An argument that isn't present", 0);
+ if (!ls_args_parse(&args, argc, argv)) {
+ printf("Error: %s\n", args.last_error);
+ ASSERT(!"ls_args_parse failed");
+ }
+ ASSERT_EQ(help, 1, "%d");
+ ASSERT_EQ(test, 1, "%d");
+ ASSERT_EQ(no, 0, "%d");
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(basic_args_only_short) {
+ int help = 0;
+ int test = 0;
+ int no = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "-h", "-t", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ls_args_bool(&args, &help, "h", NULL, "Provides help", 0);
+ ls_args_bool(&args, &test, "t", NULL, "A test argument", 0);
+ ls_args_bool(&args, &no, "n", NULL, "An argument that isn't present", 0);
+ if (!ls_args_parse(&args, argc, argv)) {
+ printf("Error: %s\n", args.last_error);
+ ASSERT(!"ls_args_parse failed");
+ }
+ ASSERT_EQ(help, 1, "%d");
+ ASSERT_EQ(test, 1, "%d");
+ ASSERT_EQ(no, 0, "%d");
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(basic_args_only_long) {
+ int help = 0;
+ int test = 0;
+ int no = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "--help", "--test", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ls_args_bool(&args, &help, NULL, "help", "Provides help", 0);
+ ls_args_bool(&args, &test, NULL, "test", "A test argument", 0);
+ ls_args_bool(&args, &no, NULL, "nope", "An argument that isn't present", 0);
+ if (!ls_args_parse(&args, argc, argv)) {
+ printf("Error: %s\n", args.last_error);
+ ASSERT(!"ls_args_parse failed");
+ }
+ ASSERT_EQ(help, 1, "%d");
+ ASSERT_EQ(test, 1, "%d");
+ ASSERT_EQ(no, 0, "%d");
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(basic_args_short_combined) {
+ int help = 0;
+ int test = 0;
+ int no = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "-ht", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ls_args_bool(&args, &help, "h", "help", "Provides help", 0);
+ ls_args_bool(&args, &test, "t", "test", "A test argument", 0);
+ ls_args_bool(&args, &no, "n", "nope", "An argument that isn't present", 0);
+ if (!ls_args_parse(&args, argc, argv)) {
+ printf("Error: %s\n", args.last_error);
+ ASSERT(!"ls_args_parse failed");
+ }
+ ASSERT_EQ(help, 1, "%d");
+ ASSERT_EQ(test, 1, "%d");
+ ASSERT_EQ(no, 0, "%d");
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(error_invalid_argument) {
+ int help = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "-h", "--test", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ls_args_bool(&args, &help, "h", "help", "Provides help", 0);
+ ASSERT(!ls_args_parse(&args, argc, argv));
+ ASSERT(strcmp(args.last_error, "Invalid argument '--test'") == 0);
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(error_expected_argument) {
+ const char* file = 0;
+ ls_args args;
+ int help;
+ char* argv[] = { "./hello", "--file", "--help", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ls_args_bool(&args, &help, "h", "help", "Provides help", 0);
+ ls_args_string(&args, &file, "f", "file", "File to work on", 0);
+ ASSERT(!ls_args_parse(&args, argc, argv));
+ ASSERT(strcmp(args.last_error, "Expected argument following '--file'") == 0);
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(error_expected_argument_last_arg) {
+ const char* file = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "--file", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ls_args_string(&args, &file, "f", "file", "File to work on", 0);
+ ASSERT(!ls_args_parse(&args, argc, argv));
+ ASSERT(strcmp(args.last_error, "Expected argument following '--file'") == 0);
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(error_expected_argument_short_combined) {
+ const char* file = 0;
+ ls_args args;
+ int help;
+ char* argv[] = { "./hello", "-fh", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ls_args_bool(&args, &help, "h", "help", "Provides help", 0);
+ ls_args_string(&args, &file, "f", "file", "File to work on", 0);
+ ASSERT(!ls_args_parse(&args, argc, argv));
+ ASSERT(strcmp(args.last_error, "Expected argument following '-f', instead got another argument '-h'") == 0);
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(error_parse_fail) {
+ int help = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "-", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ASSERT(!ls_args_parse(&args, argc, argv));
+ ASSERT(strcmp(args.last_error, "Invalid argument '-'") == 0);
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(overflow_args_cap) {
+ int help = 0;
+ int dummy = 0;
+ ls_args args;
+ int ret;
+
+ ls_args_init(&args);
+ /* First allocate one arg so the structure is properly initialized */
+ ret = ls_args_bool(&args, &dummy, "d", "dummy", "Dummy argument", 0);
+ ASSERT(ret);
+ /* can't have more elements, not even one, at this count */
+ args.args_len = SIZE_MAX / sizeof(ls_args_arg);
+ args.args_cap = SIZE_MAX / sizeof(ls_args_arg);
+ /* Try to add an argument, which should fail due to overflow */
+ ret = ls_args_bool(&args, &help, "h", "help", "Provides help", 0);
+ ASSERT(!ret);
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(strip_dashes) {
+ int help = 0;
+ int test = 0;
+ int no = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "-h", "--test", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ /* the dashes are optional */
+ ls_args_bool(&args, &help, "-h", "--help", "Provides help", 0);
+ /* you can mix them */
+ ls_args_bool(&args, &test, "t", "--test", "A test argument", 0);
+ /* have as many as you want */
+ ls_args_bool(&args, &no, "-n", "----nope", "An argument that isn't present", 0);
+ if (!ls_args_parse(&args, argc, argv)) {
+ printf("Error: %s\n", args.last_error);
+ ASSERT(!"ls_args_parse failed");
+ }
ASSERT_EQ(help, 1, "%d");
ASSERT_EQ(test, 1, "%d");
ASSERT_EQ(no, 0, "%d");
@@ -23,18 +230,81 @@ TEST_CASE(basic_args) {
return 0;
}
+TEST_CASE(alloc_fail) {
+ int help = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "-", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+ int ret;
+
+ ls_args_init(&args);
+ ASSERT_EQ(args.args_len, 0, "%uz");
+ /* deliberately fail the allocation here */
+ fail_alloc = 1;
+ /* if the allocation fails, this fails */
+ ret = ls_args_bool(&args, &help, "h", "help", "Provides help", 0);
+ fail_alloc = 0;
+ ASSERT(!ret);
+ ASSERT(strcmp(args.last_error, "Allocation failure") == 0);
+ /* there is no documented error state for this; we simply fail to add the
+ * argument? */
+ ASSERT_EQ(args.args_len, 0, "%uz");
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(error_parse_fail_empty) {
+ int help = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ASSERT(!ls_args_parse(&args, argc, argv));
+ ASSERT(strcmp(args.last_error, "Invalid argument ''") == 0);
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(error_parse_ignore_double_dash) {
+ int help = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "--", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ASSERT(ls_args_parse(&args, argc, argv));
+ ls_args_free(&args);
+ return 0;
+}
+
+TEST_CASE(error_invalid_argument_short) {
+ int help = 0;
+ ls_args args;
+ char* argv[] = { "./hello", "-h", "-t", "-h", NULL };
+ int argc = sizeof(argv) / sizeof(*argv) - 1;
+
+ ls_args_init(&args);
+ ls_args_bool(&args, &help, "h", "help", "Provides help", 0);
+ ASSERT(!ls_args_parse(&args, argc, argv));
+ ASSERT(strcmp(args.last_error, "Invalid argument '-t'") == 0);
+ ls_args_free(&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 };
+ 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);
+ ls_args_string(&args, &input, "i", "input", "Input file path", 0);
+ ls_args_string(&args, &output, "o", "output", "Output file path", 0);
+ ls_args_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);