diff options
Diffstat (limited to 'libsimple-arg.c')
-rw-r--r-- | libsimple-arg.c | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/libsimple-arg.c b/libsimple-arg.c new file mode 100644 index 0000000..fcebb42 --- /dev/null +++ b/libsimple-arg.c @@ -0,0 +1,507 @@ +/* See LICENSE file for copyright and license details. */ +#include <setjmp.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "test.h" +#include "libsimple-arg.h" + + +#define PARSER_BEGIN\ + int old_argc = argc;\ + char **old_argv = argv;\ + { + + +#define PARSER_END\ + ;}\ + assert(argv0 == old_argv[0]);\ + assert(argv == &old_argv[old_argc - argc]);\ + return argc + + +#define PARSER_END_NO_ARGV0\ + }\ + assert(argv0 == NULL);\ + assert(argv == &old_argv[old_argc - argc]);\ + return argc + + +#define SHORT_WITHOUT_ARG(SHORT_FLAG)\ + do {\ + assert(nparsed < sizeof(parsed) / sizeof(*parsed));\ + assert(FLAG() == (SHORT_FLAG)[1]);\ + parsed[nparsed].short_used = 1;\ + parsed[nparsed].long_used = 0;\ + parsed[nparsed].have_arg = 0;\ + parsed[nparsed].short_flag[0] = (SHORT_FLAG)[0];\ + parsed[nparsed].short_flag[1] = (SHORT_FLAG)[1];\ + parsed[nparsed].short_flag[2] = '\0';\ + parsed[nparsed].long_flag[0] = '\0';\ + parsed[nparsed].argument[0] = '\0';\ + nparsed += 1;\ + } while (0) + + +#define SHORT_WITH_ARG(SHORT_FLAG)\ + do {\ + char *arg__;\ + assert(nparsed < sizeof(parsed) / sizeof(*parsed));\ + assert(FLAG() == (SHORT_FLAG)[1]);\ + parsed[nparsed].short_used = 1;\ + parsed[nparsed].long_used = 0;\ + parsed[nparsed].have_arg = 1;\ + parsed[nparsed].short_flag[0] = (SHORT_FLAG)[0];\ + parsed[nparsed].short_flag[1] = (SHORT_FLAG)[1];\ + parsed[nparsed].short_flag[2] = '\0';\ + parsed[nparsed].long_flag[0] = '\0';\ + assert(strlen(ARG()) < sizeof(parsed[nparsed].argument));\ + assert((arg__ = ARG()));\ + assert(arg__ == ARGNULL());\ + stpcpy(parsed[nparsed].argument, ARG());\ + nparsed += 1;\ + } while (0) + + +#define SHORT_WITH_MISSING_ARG(SHORT_FLAG)\ + do {\ + assert(!ARGNULL());\ + ARG();\ + } while (0) + + +#define SHORT_WITH_ARGHERE(SHORT_FLAG)\ + do {\ + assert(nparsed < sizeof(parsed) / sizeof(*parsed));\ + assert(FLAG() == ARGHERE()[0]);\ + parsed[nparsed].short_used = 1;\ + parsed[nparsed].long_used = 0;\ + parsed[nparsed].have_arg = 1;\ + parsed[nparsed].short_flag[0] = (SHORT_FLAG)[0];\ + parsed[nparsed].short_flag[1] = (SHORT_FLAG)[1];\ + parsed[nparsed].short_flag[2] = '\0';\ + parsed[nparsed].long_flag[0] = '\0';\ + assert(strlen(ARGHERE()) < sizeof(parsed[nparsed].argument));\ + stpcpy(parsed[nparsed].argument, ARGHERE());\ + nparsed += 1;\ + } while (0) + + +#define ASSERT_ENTRY(SHORT_FLAG, LONG_FLAG, ARGUMENT)\ + do {\ + const char *volatile short_flag__ = (SHORT_FLAG);\ + const char *volatile long_flag__ = (LONG_FLAG);\ + const char *volatile argument__ = (ARGUMENT);\ + assert(parsedi < nparsed);\ + assert(parsed[parsedi].short_used == !!short_flag__);\ + if (short_flag__) assert(!strcmp(parsed[parsedi].short_flag, short_flag__));\ + assert(parsed[parsedi].long_used == !!long_flag__);\ + if (long_flag__) assert(!strcmp(parsed[parsedi].long_flag, long_flag__));\ + assert(parsed[parsedi].have_arg == !!argument__);\ + if (argument__) assert(!strcmp(parsed[parsedi].argument, argument__));\ + parsedi += 1;\ + } while (0) + + +#define ASSERT_END()\ + do {\ + assert(parsedi == nparsed);\ + parsedi = 0;\ + } while (0) + + +struct entry { + char short_used; + char long_used; + char have_arg; + char short_flag[3]; + char long_flag[256]; + char argument[256]; +}; + + +static struct entry parsed[100]; +static size_t nparsed = 0; +static size_t parsedi = 0; +static jmp_buf usage_jmp; +extern char *argv0_1; +extern char *argv0_2; +extern char *argv0_3; +extern char *argv0_0; + + +#define usage usage1 +#define argv0 argv0_1 +USAGE("-1"); +#undef usage +#undef argv0 + +#define usage usage2 +#define argv0 argv0_2 +NUSAGE(2, "-2"); +#undef usage +#undef argv0 + +#define usage usage3 +#define argv0 argv0_3 +NUSAGE(3, ""); +#undef usage +#undef argv0 + +#define usage usage0 +#define argv0 argv0_0 +NUSAGE(0, NULL); +#undef usage +#undef argv0 + + +#define apply(...) apply_(__VA_ARGS__, NULL) +static int +apply_(int (*func)(int argc, char *argv[]), ...) +{ + va_list ap; + char **argv, **old_argv; + const char *arg; + size_t argc = 0, i; + int ret; + + va_start(ap, func); + while (va_arg(ap, const char *)) + argc += 1; + va_end(ap); + assert((argv = calloc(argc + 1, sizeof(*argv)))); + assert((old_argv = calloc(argc + 1, sizeof(*argv)))); + va_start(ap, func); + for (i = 0; i < argc; i++) { + assert((arg = va_arg(ap, const char *))); + assert((argv[i] = strdup(arg))); + } + va_end(ap); + memcpy(old_argv, argv, (argc + 1) * sizeof(*argv)); + + if (setjmp(usage_jmp)) { + ret = -1; + goto out; + } + + nparsed = 0; + argv0 = NULL; + ret = func((int)argc, argv); +out: + va_start(ap, func); + for (i = 0; i < argc - (size_t)ret; i++) { + va_arg(ap, const char *); + free(old_argv[i]); + } + for (; i < argc; i++) { + assert(argv[i] == old_argv[i]); + assert(!strcmp(argv[i], va_arg(ap, const char *))); + free(old_argv[i]); + } + va_end(ap); + free(argv); + free(old_argv); + return ret; +} + + +static void +usage(void) +{ + longjmp(usage_jmp, 1); +} + + +static int +parser1(int argc, char *argv[]) +{ + PARSER_BEGIN; + ARGBEGIN { + case 'a': SHORT_WITHOUT_ARG("-a"); break; + case 'b': SHORT_WITHOUT_ARG("-b"); break; + case 'c': SHORT_WITHOUT_ARG("-c"); break; + case 'd': SHORT_WITHOUT_ARG("-d"); break; + case 'e': SHORT_WITHOUT_ARG("-e"); break; + case 'A': SHORT_WITHOUT_ARG("-A"); break; + case 'B': SHORT_WITHOUT_ARG("-B"); break; + case 'C': SHORT_WITHOUT_ARG("-C"); break; + case 'D': SHORT_WITHOUT_ARG("-D"); break; + case 'E': SHORT_WITHOUT_ARG("-E"); break; + case 'x': SHORT_WITH_ARG("-x"); break; + case 'y': SHORT_WITH_ARG("-y"); break; + case 'z': SHORT_WITH_ARG("-z"); break; + case 'X': SHORT_WITH_ARG("-X"); break; + case 'Y': SHORT_WITH_ARG("-Y"); break; + case 'Z': SHORT_WITH_ARG("-Z"); break; + case '-': SHORT_WITHOUT_ARG("--"); break; + case ARGNUM: SHORT_WITH_ARGHERE("-#"); break; + case '@': SHORT_WITH_MISSING_ARG("-@"); break; + default: + usage(); + } ARGEND; + PARSER_END; +} + + +static int +parser2(int argc, char *argv[]) +{ + PARSER_BEGIN; + SUBARGBEGIN { + case 'a': SHORT_WITHOUT_ARG("-a"); break; + case 'b': SHORT_WITHOUT_ARG("-b"); break; + case 'c': SHORT_WITHOUT_ARG("-c"); break; + case 'd': SHORT_WITHOUT_ARG("-d"); break; + case 'e': SHORT_WITHOUT_ARG("-e"); break; + case 'A': SHORT_WITHOUT_ARG("-A"); break; + case 'B': SHORT_WITHOUT_ARG("-B"); break; + case 'C': SHORT_WITHOUT_ARG("-C"); break; + case 'D': SHORT_WITHOUT_ARG("-D"); break; + case 'E': SHORT_WITHOUT_ARG("-E"); break; + case 'x': SHORT_WITH_ARG("-x"); break; + case 'y': SHORT_WITH_ARG("-y"); break; + case 'z': SHORT_WITH_ARG("-z"); break; + case 'X': SHORT_WITH_ARG("-X"); break; + case 'Y': SHORT_WITH_ARG("-Y"); break; + case 'Z': SHORT_WITH_ARG("-Z"); break; + case '-': SHORT_WITHOUT_ARG("--"); break; + case ARGNUM: SHORT_WITH_ARGHERE("-#"); break; + case '@': SHORT_WITH_MISSING_ARG("-@"); break; + default: + usage(); + } ARGEND; + PARSER_END_NO_ARGV0; +} + + +static int +parser3(int argc, char *argv[]) +{ + PARSER_BEGIN; + NOFLAGS(0); + PARSER_END; +} + + +static int +parser4(int argc, char *argv[]) +{ + PARSER_BEGIN; + NOFLAGS(argc); + PARSER_END; +} + + +static int +parser5(int argc, char *argv[]) +{ + PARSER_BEGIN; + ARGBEGIN { + case 'a': SHORT_WITHOUT_ARG("-a"); break; + case 'x': SHORT_WITHOUT_ARG("-x"); break; + case '-': SHORT_WITHOUT_ARG("--"); break; + default: + usage(); + } ARGALT('+') { + case 'a': SHORT_WITHOUT_ARG("+a"); break; + case 'y': SHORT_WITHOUT_ARG("+y"); break; + case '+': SHORT_WITHOUT_ARG("++"); break; + default: + usage(); + } ARGALT('/') { + case 'a': SHORT_WITHOUT_ARG("/a"); break; + case 'z': SHORT_WITHOUT_ARG("/z"); break; + case '/': SHORT_WITHOUT_ARG("//"); break; + default: + usage(); + } ARGALT('x') { + case 'a': SHORT_WITHOUT_ARG("xa"); break; + case 'b': SHORT_WITHOUT_ARG("xb"); break; + case 'x': SHORT_WITHOUT_ARG("xx"); break; + default: + usage(); + } ARGEND; + PARSER_END; +} + + +static int +parser6(int argc, char *argv[]) +{ + PARSER_BEGIN; + ARGBEGIN2(1, 1) { + default: + argv[0] = LFLAG(); + goto stop; + } ARGALT('+') { + case 'a': SHORT_WITHOUT_ARG("+a"); break; + case 'b': SHORT_WITHOUT_ARG("+b"); break; + case 'c': SHORT_WITHOUT_ARG("+c"); break; + case 'd': SHORT_WITHOUT_ARG("+d"); break; + case 'e': SHORT_WITHOUT_ARG("+e"); break; + case 'A': SHORT_WITHOUT_ARG("+A"); break; + case 'B': SHORT_WITHOUT_ARG("+B"); break; + case 'C': SHORT_WITHOUT_ARG("+C"); break; + case 'D': SHORT_WITHOUT_ARG("+D"); break; + case 'E': SHORT_WITHOUT_ARG("+E"); break; + case 'x': SHORT_WITH_ARG("+x"); break; + case 'y': SHORT_WITH_ARG("+y"); break; + case 'z': SHORT_WITH_ARG("+z"); break; + case 'X': SHORT_WITH_ARG("+X"); break; + case 'Y': SHORT_WITH_ARG("+Y"); break; + case 'Z': SHORT_WITH_ARG("+Z"); break; + case '+': SHORT_WITHOUT_ARG("++"); break; + case ARGNUM: SHORT_WITH_ARGHERE("+#"); break; + case '@': SHORT_WITH_MISSING_ARG("+@"); break; + default: + usage(); + } ARGEND; +stop: + PARSER_END; +} + + +int +main(void) +{ + const char *a1; + size_t i; + + assert(apply(parser1) == 0); + ASSERT_END(); + + assert(apply(parser1, "-a") == 0); + ASSERT_END(); + + assert(apply(parser1, "-a", "-a", "-o") == -1); + ASSERT_ENTRY("-a", NULL, NULL); + ASSERT_END(); + + assert(apply(parser1, "-a", "-a", "-Y") == -1); + ASSERT_ENTRY("-a", NULL, NULL); + ASSERT_END(); + + for (i = 0; i < 5; i++) { + a1 = ((const char *[]){"--", "z", "-", "", "+a"})[i]; + assert(apply(parser1, "argv[0]", "-abdexhello world", "-x", "hi", "-y", "-Y", a1, "-Z", "z") == 2 + !!i); + ASSERT_ENTRY("-a", NULL, NULL); + ASSERT_ENTRY("-b", NULL, NULL); + ASSERT_ENTRY("-d", NULL, NULL); + ASSERT_ENTRY("-e", NULL, NULL); + ASSERT_ENTRY("-x", NULL, "hello world"); + ASSERT_ENTRY("-x", NULL, "hi"); + ASSERT_ENTRY("-y", NULL, "-Y"); + ASSERT_END(); + } + + assert(apply(parser1, "", "-a1000", "-200") == 0); + ASSERT_ENTRY("-a", NULL, NULL); + ASSERT_ENTRY("-#", NULL, "1000"); + ASSERT_ENTRY("-#", NULL, "200"); + ASSERT_END(); + + assert(apply(parser1, "", "-a", "-@") == -1); + ASSERT_ENTRY("-a", NULL, NULL); + ASSERT_END(); + + assert(apply(parser1, "", "-a-") == -1); + ASSERT_ENTRY("-a", NULL, NULL); + ASSERT_END(); + + for (i = 0; i < 5; i++) { + a1 = ((const char *[]){"--", "z", "-", "", "+a"})[i]; + assert(apply(parser2, "-abdexhello world", "-x", "hi", "-y", "-Y", a1, "-Z", "z") == 2 + !!i); + ASSERT_ENTRY("-a", NULL, NULL); + ASSERT_ENTRY("-b", NULL, NULL); + ASSERT_ENTRY("-d", NULL, NULL); + ASSERT_ENTRY("-e", NULL, NULL); + ASSERT_ENTRY("-x", NULL, "hello world"); + ASSERT_ENTRY("-x", NULL, "hi"); + ASSERT_ENTRY("-y", NULL, "-Y"); + ASSERT_END(); + } + + assert(apply(parser3, "x") == 0); ASSERT_END(); + assert(apply(parser3, "y", "z") == 1); ASSERT_END(); + assert(apply(parser3, "x", "z", "-a") == 2); ASSERT_END(); + assert(apply(parser3, "y", "-z", "a") == -1); ASSERT_END(); + assert(apply(parser3, "x", "--", "-a") == 1); ASSERT_END(); + assert(apply(parser3, "", "--") == 0); ASSERT_END(); + assert(apply(parser3, "-", "--x") == -1); ASSERT_END(); + assert(apply(parser3, "", "+") == 1); ASSERT_END(); + assert(apply(parser3, "-", "") == 1); ASSERT_END(); + assert(apply(parser3, "", "-") == 1); ASSERT_END(); + + assert(apply(parser4, "x") == 0); ASSERT_END(); + assert(apply(parser4, "y", "z") == -1); ASSERT_END(); + assert(apply(parser4, "", "z", "-a") == -1); ASSERT_END(); + assert(apply(parser4, "-", "-z", "a") == -1); ASSERT_END(); + assert(apply(parser4, "", "--", "-a") == -1); ASSERT_END(); + assert(apply(parser4, "-", "--") == 0); ASSERT_END(); + assert(apply(parser4, "", "--x") == -1); ASSERT_END(); + assert(apply(parser4, "-", "+") == -1); ASSERT_END(); + assert(apply(parser4, "", "") == -1); ASSERT_END(); + assert(apply(parser4, "-", "-") == -1); ASSERT_END(); + + for (i = 0; i < 4; i++) { + a1 = ((const char *[]){"--", "++", "//", "xx"})[i]; + assert(apply(parser5, "<>", "-a", "+a", "/a", "xa", "-x", "+y", "/z", "xb", a1, "yy") == 1 + !!i); + ASSERT_ENTRY("-a", NULL, NULL); + ASSERT_ENTRY("+a", NULL, NULL); + ASSERT_ENTRY("/a", NULL, NULL); + ASSERT_ENTRY("xa", NULL, NULL); + ASSERT_ENTRY("-x", NULL, NULL); + ASSERT_ENTRY("+y", NULL, NULL); + ASSERT_ENTRY("/z", NULL, NULL); + ASSERT_ENTRY("xb", NULL, NULL); + ASSERT_END(); + } + + assert(apply(parser5, "<>", "-y") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "-z") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "-b") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "+x") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "+z") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "+b") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "/x") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "/y") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "/b") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "xy") == -1); ASSERT_END(); + assert(apply(parser5, "<>", "xz") == -1); ASSERT_END(); + + for (i = 0; i < 6; i++) { + a1 = ((const char *[]){"++", "z", "+", "", "-a", "--"})[i]; + assert(apply(parser6, "0", "+abdexhello world", "+x", "hi", "+y", "+Y", a1, "+Z", "z") == 3); + ASSERT_ENTRY("+a", NULL, NULL); + ASSERT_ENTRY("+b", NULL, NULL); + ASSERT_ENTRY("+d", NULL, NULL); + ASSERT_ENTRY("+e", NULL, NULL); + ASSERT_ENTRY("+x", NULL, "hello world"); + ASSERT_ENTRY("+x", NULL, "hi"); + ASSERT_ENTRY("+y", NULL, "+Y"); + ASSERT_END(); + } + + argv0_1 = "[1]"; + assert_exit(usage1()); + assert(exit_status == 1); + assert_stderr("usage: [1] -1\n"); + + argv0_2 = "[2]"; + assert_exit(usage2()); + assert_stderr("usage: [2] -2\n"); + assert(exit_status == 2); + + argv0_3 = "[3]"; + assert_exit(usage3()); + assert_stderr("usage: [3]\n"); + assert(exit_status == 3); + + argv0_0 = "[0]"; + assert_exit(usage0()); + assert_stderr("usage: [0]\n"); + assert(exit_status == 0); + + return 0; +} |