From: Michael D. Lowis Date: Mon, 7 Dec 2015 01:59:54 +0000 (-0500) Subject: Added suckless/plan9 inspired opt.h for lightweight traditional option parsing X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=38717ebaa5f1fc4be2c5e19a5c0b9a7d5ca7605e;p=projs%2Fopts.git Added suckless/plan9 inspired opt.h for lightweight traditional option parsing --- diff --git a/Makefile b/Makefile index 511fb89..466e757 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ AR = ar # flags INCS = -Isource/ -Itests/ CPPFLAGS = -D_XOPEN_SOURCE=700 -CFLAGS += ${INCS} ${CPPFLAGS} +CFLAGS += -g ${INCS} ${CPPFLAGS} LDFLAGS += ${LIBS} ARFLAGS = rcs @@ -20,7 +20,7 @@ ARFLAGS = rcs #------------------------------------------------------------------------------ SRCS = source/opts.c OBJS = ${SRCS:.c=.o} -TEST_SRCS = tests/atf.c tests/main.c tests/test_opts.c +TEST_SRCS = tests/atf.c tests/main.c tests/test_opts.c tests/test_opt.c TEST_OBJS = ${TEST_SRCS:.c=.o} all: options libopts.a testopts diff --git a/source/opt.h b/source/opt.h new file mode 100644 index 0000000..5e64c3f --- /dev/null +++ b/source/opt.h @@ -0,0 +1,61 @@ +#ifndef OPT_H +#define OPT_H + +extern char* ARGV0; + +static inline char* getarg(int* p_argc, char*** p_argv) { + if (!(*p_argv)[0][1] && !(*p_argv)[1]) { + return (char*)0; + } else if ((*p_argv)[0][1]) { + return &(*p_argv)[0][1]; + } else { + *p_argv = *p_argv + 1; + *p_argc = *p_argc - 1; + return (*p_argv)[0]; + } +} + +/* use main(int argc, char *argv[]) */ +#define OPTBEGIN \ + for ( \ + ARGV0 = *argv, argc--, argv++; \ + argv[0] && argv[0][1] && argv[0][0] == '-'; \ + argc--, argv++ \ + ) { \ + char argc_ , **argv_, *optarg_; \ + int brk_; \ + if (argv[0][1] == '-' && !argv[0][2]) { \ + argv++, argc--; break; \ + } \ + for (brk_=0, argv[0]++, argv_=argv; argv[0][0] && !brk_; argv[0]++) { \ + if (argv_ != argv) break; \ + argc_ = argv[0][0]; \ + switch (argc_) + +#define OPTEND }} + +#define OPTC() (argc_) + +#define OPTARG() \ + (optarg_ = getarg(&argc,&argv), brk_ = (optarg_!=0), optarg_) + +#define EOPTARG(code) \ + (optarg_ = getarg(&argc,&argv), \ + (!optarg_ ? ((code), abort(), (char*)0) : (brk_ = 1, optarg_))) + +#define OPTNUM \ + case '0': \ + case '1': \ + case '2': \ + case '3': \ + case '4': \ + case '5': \ + case '6': \ + case '7': \ + case '8': \ + case '9' + +#define OPTLONG \ + case '-' + +#endif diff --git a/tests/main.c b/tests/main.c index c47450f..85d6c81 100644 --- a/tests/main.c +++ b/tests/main.c @@ -5,5 +5,6 @@ int main(int argc, char** argv) (void)argc; (void)argv; RUN_EXTERN_TEST_SUITE(Opts); + RUN_EXTERN_TEST_SUITE(Arg); return PRINT_TEST_RESULTS(); } diff --git a/tests/test_opt.c b/tests/test_opt.c new file mode 100644 index 0000000..b81a10d --- /dev/null +++ b/tests/test_opt.c @@ -0,0 +1,184 @@ +// Unit Test Framework Includes +#include "atf.h" +#include +#include + +// File To Test +#include "opt.h" + +char* ARGV0; +int argc; +char** argv; + +bool Aborted = false; +void abort(void) { + Aborted = true; +} + +void dummy(){} + +//----------------------------------------------------------------------------- +// Begin Unit Tests +//----------------------------------------------------------------------------- +TEST_SUITE(Arg) { + //------------------------------------------------------------------------- + // Short Option Parsing + //------------------------------------------------------------------------- + TEST(should parse an option with no argument) + { + char* args[] = { "prog", "-a", NULL }; + argc = 2, argv = args; + OPTBEGIN { + case 'a': CHECK(true); break; + default: CHECK(false); + } OPTEND; + } + + TEST(should parse two short options grouped together) + { + char* args[] = { "prog", "-ab", NULL }; + bool a = false, b = false; + argc = 2, argv = args; + OPTBEGIN { + case 'a': a = true; break; + case 'b': b = true; break; + default: CHECK(false); + } OPTEND; + CHECK(a && b); + } + + TEST(should parse three short options grouped together) + { + char* args[] = { "prog", "-abc", NULL }; + bool a = false, b = false, c = false; + argc = 2, argv = args; + OPTBEGIN { + case 'a': a = true; break; + case 'b': b = true; break; + case 'c': c = true; break; + default: CHECK(false); + } OPTEND; + CHECK(a && b && c); + } + + TEST(OPTARG should parse an option with an adjacent argument) + { + char* args[] = { "prog", "-afoo", NULL }; + argc = 2, argv = args; + OPTBEGIN { + case 'a': + CHECK('a' == OPTC()); + CHECK(0 == strcmp("foo", OPTARG())); + break; + default: CHECK(false); + } OPTEND; + } + + TEST(OPTARG should parse an option with an argument) + { + char* args[] = { "prog", "-a", "foo", NULL }; + argc = 3, argv = args; + OPTBEGIN { + case 'a': + CHECK('a' == OPTC()); + CHECK(0 == strcmp("foo", OPTARG())); + break; + default: CHECK(false); + } OPTEND; + } + +#if 1 + TEST(OPTARG should return null if no argument found for option) + { + char* args[] = { "prog", "-a", NULL }; + argc = 2, argv = args; + OPTBEGIN { + case 'a': + CHECK('a' == OPTC()); + CHECK(0 == OPTARG()); + break; + default: CHECK(false); + } OPTEND; + } + + + TEST(EOPTARG should parse an option with an adjacent argument) + { + char* args[] = { "prog", "-afoo", NULL }; + argc = 2, argv = args; + OPTBEGIN { + case 'a': + CHECK('a' == OPTC()); + CHECK(0 == strcmp("foo", EOPTARG(dummy()))); + break; + default: CHECK(false); + } OPTEND; + } + + TEST(EOPTARG should parse an option with an argument) + { + char* args[] = { "prog", "-a", "foo", NULL }; + argc = 3, argv = args; + char* arg; + OPTBEGIN { + case 'a': + CHECK('a' == OPTC()); + CHECK(0 == strcmp("foo", EOPTARG(dummy()))); + break; + default: CHECK(false); + } OPTEND; + } + + TEST(EOPTARG should return abort if no argument found for option) + { + char* args[] = { "prog", "-a", NULL }; + argc = 2, argv = args; + Aborted = false; + OPTBEGIN { + case 'a': + CHECK('a' == OPTC()); + (void)EOPTARG(dummy()); + goto done; + break; + default: CHECK(false); + } OPTEND; + done: CHECK(Aborted); + } +#endif + + //------------------------------------------------------------------------- + // Miscellaneous Syntax + //------------------------------------------------------------------------- +#if 1 + TEST(Option processing should terminate at a --) + { + char* args[] = { "prog", "--", "-a", NULL }; + argc = 3, argv = args; + OPTBEGIN { + default: CHECK(false); + } OPTEND; + CHECK(0 == strcmp("-a", argv[0])); + } + + TEST(Option processing should recognize long options) + { + char* args[] = { "prog", "--foo", NULL }; + argc = 2, argv = args; + OPTBEGIN { + case '-': CHECK(0 == strcmp("foo", OPTARG())); break; + default: CHECK(false); + } OPTEND; + } + + + TEST(Option processing should recognize - as a positional argument) + { + char* args[] = { "prog", "-", NULL }; + argc = 2, argv = args; + OPTBEGIN { + default: CHECK(false); + } OPTEND; + CHECK(0 == strcmp("-",argv[0])); + } +#endif +}