diff --git a/.gitignore b/.gitignore index 64c24d3..30953ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,4 @@ *.o -libreadopt.a -libreadopt.so -test/test -test/*.o +test .ninja_deps .ninja_log -test/.ninja_deps -test/.ninja_log diff --git a/Makefile b/Makefile deleted file mode 100644 index 78f6cd7..0000000 --- a/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -include config.mk - -SOURCE = readopt.c -OBJECT = readopt.o - -all: $(STATIC) $(SHARED) - -%.o: %.c - $(CC) -c $(CFLAGS) $< - -$(STATIC): $(OBJECT) - $(AR) $@ $^ - -$(SHARED): $(OBJECT) - $(CC) --shared $^ -o $@ - -install: staticinstall sharedinstall - -staticinstall: $(STATIC) - $(MKDIR) $(DESTDIR)$(LIB) - $(CP) $^ $(DESTDIR)$(LIB) - $(MKDIR) $(DESTDIR)$(INCL) - $(CP) $(HEADER) $(DESTDIR)$(INCL) - -sharedinstall: $(SHARED) - $(MKDIR) $(DESTDIR)$(LIB) - $(CP) $^ $(DESTDIR)$(LIB) - $(MKDIR) $(DESTDIR)$(INCL) - $(CP) $(HEADER) $(DESTDIR)$(INCL) - -clean: - $(RM) $(OBJECT) $(STATIC) $(SHARED) - -format: - clang-format -i -- $(SOURCE) $(HEADER) - -.PHONY: clean install staticinstall sharedinstall format diff --git a/build.ninja b/build.ninja deleted file mode 100644 index e27c4e9..0000000 --- a/build.ninja +++ /dev/null @@ -1,28 +0,0 @@ -# vim: set tabstop=2 shiftwidth=2 expandtab : - -include config.ninja - -rule compile - command = $cc $cflags -c -o $out $in - -rule static - command = ar $arflags -- $out $in - -rule shared - command = $cc --shared -o $out $in - -rule install - command = cp -- $in $out - -build ./readopt.o: compile ./readopt.c -build $static: static ./readopt.o -build $shared: shared ./readopt.o - -build $destdir/$lib/$static: install $static -build $destdir/$lib/$shared: install $shared -build $destdir/$incl/$header: install $header - -build install: phony $destdir/$lib/$static $destdir/$lib/$shared $destdir/$incl/$header -build all: phony $static $shared - -default all diff --git a/config.mk b/config.mk deleted file mode 100644 index 2a4aa82..0000000 --- a/config.mk +++ /dev/null @@ -1,17 +0,0 @@ -DESTDIR = -PREFIX = /usr/local/ -LIB = $(PREFIX)/lib/ -INCL = $(PREFIX)/include/ - -CC = cc -MACROS = -D _POSIX_C_SOURCE=200809L -D NDEBUG -CFLAGS = --std=c99 -Wall -Wextra -Wpedantic -g $(MACROS) -O2 - -AR = ar -rcs -- -RM = rm -f -- -CP = cp -- -MKDIR = mkdir -p -- - -HEADER = ./readopt.h -STATIC = ./libreadopt.a -SHARED = ./libreadopt.so diff --git a/config.ninja b/config.ninja deleted file mode 100644 index 0b36692..0000000 --- a/config.ninja +++ /dev/null @@ -1,15 +0,0 @@ -# vim: set tabstop=2 shiftwidth=2 expandtab : - -destdir = -prefix = /usr/local/ -lib = $prefix/lib/ -incl = $prefix/include/ - -cc = cc -macros = -D _POSIX_C_SOURCE=200809L -D NDEBUG -cflags = --std=c99 -Wall -Wextra -Wpedantic -g $macros -O2 -arflags = -rcs - -header = ./readopt.h -static = ./libreadopt.a -shared = ./libreadopt.so diff --git a/readopt.c b/readopt.c deleted file mode 100644 index 8e6e7b7..0000000 --- a/readopt.c +++ /dev/null @@ -1,439 +0,0 @@ -#include "readopt.h" - -#include -#include - -static void parse_arg(struct readopt_parser *rp, const char *arg); - -static void parse_opt(struct readopt_parser *rp, enum readopt_form form, const char **pos); - -static struct readopt_opt *match_opt(struct readopt_parser *rp, enum readopt_form form, const char **needle); -static struct readopt_opt *match_finish(struct readopt_parser *rp, const char **needle, const char *adv, struct readopt_opt *opt); - -static void update_opt(struct readopt_parser *rp, const char *attach, struct readopt_opt *opt); -static void update_oper(struct readopt_parser *rp, struct readopt_view_strings val); - -static void assign_opers(struct readopt_parser *rp); - -static void add_val(struct readopt_parser *rp, struct readopt_oper *oper, const char *string, int end); - -static const char *skip_incl(const char *outer, const char *inner); - -static void occ_opt(struct readopt_parser *rp, struct readopt_opt *opt); - -static void permute_val(struct readopt_parser *rp, struct readopt_view_strings *target, const char *val, int end); -static void incr_between(const char **start, const char **stop, struct readopt_view_strings *curr, struct readopt_view_strings *exclude); -static void permute_rest(const char **target, struct readopt_view_strings start); - -int readopt_parse(struct readopt_parser *rp) -{ - /* Check whether the current offset is at the end of argv. */ - size_t off = rp->state.curr.arg - rp->args.strings; - if (off >= rp->args.len) - { - if (rp->state.pending) - { - /* The last specified option required an argument, but no argument has been provided. */ - rp->error = READOPT_ERROR_NOVAL; - return 0; - } - - for (size_t i = 0; readopt_validate_opt(rp->opts + i); i++) - { - if (!readopt_validate_within(&rp->opts[i].cont.oper)) - { - rp->error = READOPT_ERROR_RANGEOPT; - return 0; - } - } - - assign_opers(rp); - return 0; - } - - if (rp->state.pending) - { - add_val(rp, &rp->state.curr.opt->cont.oper, *rp->state.curr.arg, 0); - ++rp->state.curr.arg; - return !rp->error; - } - - parse_arg(rp, *rp->state.curr.arg); - - /* If grouped options are still being parsed, they should not be discarded. */ - if (!rp->state.grppos) - ++rp->state.curr.arg; - - return !rp->error; -} - -static void parse_arg(struct readopt_parser *rp, const char *arg) -{ - /* Parse the next option in the grouped option string, which automatically advances it. */ - if (rp->state.grppos) - { - parse_opt(rp, READOPT_FORM_SHORT, &rp->state.grppos); - if (!*rp->state.grppos) - { - rp->state.grppos = NULL; - } - return; - } - - const char *pos = arg; - - switch (*pos) - { - case '-': - ++pos; - switch (*pos) - { - case '-': - ++pos; - switch (*pos) - { - size_t off; - case '\0': - /* "--" denotes the end of options. */ - off = rp->args.len - (rp->state.curr.arg - rp->args.strings); - assert(off); - if (off == 1) - { - /* No operands after the "--". */ - return; - } - - update_oper(rp, (struct readopt_view_strings){ - .len = off - 1, - .strings = rp->state.curr.arg + 1}); - rp->state.curr.arg = rp->args.strings + rp->args.len - 1; - - return; - default: - parse_opt(rp, READOPT_FORM_LONG, &pos); - return; - } - case '\0': - update_oper(rp, (struct readopt_view_strings){.len = 1, .strings = (const char *[]){arg}}); - return; - default: - parse_opt(rp, READOPT_FORM_SHORT, &pos); - return; - } - default: - update_oper(rp, (struct readopt_view_strings){.len = 1, .strings = (const char *[]){arg}}); - return; - } -} - -static void parse_opt(struct readopt_parser *rp, enum readopt_form form, const char **pos) -{ - struct readopt_opt *match; - assert(form == READOPT_FORM_SHORT || form == READOPT_FORM_LONG); - - if (form == READOPT_FORM_SHORT) - { - match = match_opt(rp, form, pos); - if (match) - { - const char *strpos = *pos; - - if (!match->cont.req && *strpos) - { - rp->state.grppos = strpos; - update_opt(rp, NULL, match); - } - else - { - update_opt(rp, *strpos ? strpos : NULL, match); - } - } - else - { - rp->error = READOPT_ERROR_NOTOPT; - } - } - else - { - /* Match and advance pos to the end of the match. */ - match = match_opt(rp, form, pos); - - if (match) - { - switch (**pos) - { - case '\0': - update_opt(rp, NULL, match); - break; - case '=': - ++(*pos); - update_opt(rp, *pos, match); - break; - default: - rp->error = READOPT_ERROR_NOTOPT; - break; - } - } - else - { - rp->error = READOPT_ERROR_NOTOPT; - } - } -} - -static struct readopt_opt *match_opt(struct readopt_parser *rp, enum readopt_form form, const char **needle) -{ - /* This represents the last inexact match. */ - struct - { - /* The current advanced string. */ - const char *adv; - /* The current option. */ - struct readopt_opt *opt; - } loose = {0}; - - struct readopt_opt *haystack = rp->opts; - for (size_t i = 0; readopt_validate_opt(haystack + i); i++) - { - /* Iterate through all short or long names of the current option. */ - char **names = haystack[i].names[form]; - - if (!names) - /* Ignore the option as it does not have names in the required form. */ - continue; - - const char *cmp = loose.adv; - - for (size_t j = 0; names[j]; j++) - { - char *name = names[j]; - cmp = skip_incl(*needle, name); - - if (!cmp) - continue; - - if (!*cmp) - /* A guaranteed match. */ - return match_finish(rp, needle, cmp, haystack + i); - else if ((cmp - *needle) > (loose.adv - *needle)) - /* Maybe a match, maybe not. */ - loose.adv = cmp, loose.opt = haystack + i; - } - } - - return match_finish(rp, needle, loose.adv, loose.opt); -} - -static struct readopt_opt *match_finish(struct readopt_parser *rp, const char **needle, const char *adv, struct readopt_opt *opt) -{ - if (adv) - *needle = adv; - - if (opt) - rp->state.curr.opt = opt; - - return opt; -} - -static void update_opt(struct readopt_parser *rp, const char *attach, struct readopt_opt *opt) -{ - if (opt->cont.req) - { - if (attach) - { - /* --opt=value, --opt=, -ovalue */ - struct readopt_oper *curr = &rp->state.curr.opt->cont.oper; - occ_opt(rp, opt); - add_val(rp, curr, attach, 0); - } - else - { - /* --opt value, -o value */ - rp->state.pending = 1; - occ_opt(rp, opt); - } - } - else - { - occ_opt(rp, opt); - if (attach) - rp->error = READOPT_ERROR_NOTREQ; - } -} - -static void update_oper(struct readopt_parser *rp, struct readopt_view_strings val) -{ - assert(val.len && val.strings); - - if (val.len == 1) - { - ++rp->state.curr.ioper.len; - permute_val(rp, &rp->state.curr.ioper, val.strings[0], 1); - } - else - { - permute_rest(rp->state.curr.eoval, val); - rp->state.curr.ioper.len += val.len; - } -} - -static void assign_opers(struct readopt_parser *rp) -{ - size_t count = rp->state.curr.ioper.len; - - size_t nlower = 0; - size_t nupper = 0; - for (size_t i = 0; readopt_validate_oper(rp->opers + i); i++) - { - nlower += readopt_select_lower(rp->opers[i].bounds); - nupper += readopt_select_upper(rp->opers[i].bounds); - } - - if (count < nlower) - { - rp->error = READOPT_ERROR_RANGEOPER; - return; - } - - struct - { - size_t extra; - size_t req; - } rest = { - count - nlower, - nlower}; - - for (size_t i = 0; readopt_validate_oper(rp->opers + i); i++) - { - if (count == 0 || !rp->opers[i].val.strings) - { - size_t off = count - (rest.extra + rest.req); - rp->opers[i].val.strings = rp->state.curr.ioper.strings + off; - } - - size_t lower = readopt_select_lower(rp->opers[i].bounds); - size_t upper = readopt_select_upper(rp->opers[i].bounds); - int inf = rp->opers[i].bounds.inf; - - size_t add; - - /* Add required elements. */ - add = rest.req > lower ? lower : rest.req; - rp->opers[i].val.len += add, rest.req -= add; - - /* Add optional elements. */ - add = inf ? rest.extra : rest.extra > upper ? upper - : rest.extra; - rp->opers[i].val.len += add, rest.extra -= add; - } - - if (rest.extra || rest.req) - rp->error = READOPT_ERROR_RANGEOPER; -} - -static void add_val(struct readopt_parser *rp, struct readopt_oper *oper, const char *string, int end) -{ - rp->state.pending = 0; - - if (!readopt_validate_within(oper)) - rp->error = READOPT_ERROR_RANGEOPT; - else - permute_val(rp, &oper->val, string, end); -} - -static const char *skip_incl(const char *outer, const char *inner) -{ - while (*inner == *outer) - { - if (!*inner) - return outer; - ++inner, ++outer; - } - return !*inner ? outer : NULL; -} - -static void occ_opt(struct readopt_parser *rp, struct readopt_opt *opt) -{ - assert(opt); - rp->state.curr.opt = opt; - ++rp->state.curr.opt->cont.oper.val.len; -} - -static void permute_val(struct readopt_parser *rp, struct readopt_view_strings *target, const char *val, int end) -{ - if (!target->strings) - /* Fallback position when no value has yet been set. */ - target->strings = rp->state.curr.eoval - (end ? 0 : rp->state.curr.ioper.len); - - const char **pos = target->strings + (target->len - 1); - - assert(rp->state.curr.arg >= rp->state.curr.eoval); - - memmove(pos + 1, pos, (rp->state.curr.eoval - pos) * sizeof *pos); - - *pos = val; - ++rp->state.curr.eoval; - - const char **start = pos, **stop = rp->state.curr.eoval; - - /* Increment all value pointers in the options which are between start and stop (inclusive). */ - for (size_t i = 0; readopt_validate_opt(rp->opts + i); i++) - incr_between(start, stop, &rp->opts[i].cont.oper.val, target); - - incr_between(start, stop, &rp->state.curr.ioper, target); -} - -static void incr_between(const char **start, const char **stop, struct readopt_view_strings *curr, struct readopt_view_strings *exclude) -{ - if (curr->strings >= start && curr->strings <= stop && curr != exclude) - ++curr->strings; -} - -static void permute_rest(const char **target, struct readopt_view_strings start) -{ - memmove(target, start.strings, start.len * sizeof *start.strings); -} - -void readopt_parser_init(struct readopt_parser *rp, struct readopt_opt *opts, struct readopt_oper *opers, struct readopt_view_strings args) -{ - *rp = (struct readopt_parser){ - .args = args, - .opts = opts, - .opers = opers, - .state.curr = { - .arg = args.strings, - .eoval = args.strings}}; -} - -int readopt_validate_opt(struct readopt_opt *opt) -{ - assert(opt); - return opt->names[0] || opt->names[1]; -} - -int readopt_validate_oper(struct readopt_oper *oper) -{ - assert(oper); - return !!oper->name; -} - -int readopt_validate_within(struct readopt_oper *oper) -{ - size_t occ = oper->val.len; - size_t upper = readopt_select_upper(oper->bounds); - size_t lower = readopt_select_lower(oper->bounds); - return occ >= lower && (occ <= upper || oper->bounds.inf); -} - -size_t readopt_select_upper(struct readopt_bounds bounds) -{ - return bounds.val[0] > bounds.val[1] ? bounds.val[0] : bounds.val[1]; -} - -size_t readopt_select_lower(struct readopt_bounds bounds) -{ - return bounds.inf ? readopt_select_upper(bounds) : bounds.val[0] < bounds.val[1] ? bounds.val[0] - : bounds.val[1]; -} - -// vim: set sw=4 ts=4 et : diff --git a/readopt.h b/readopt.h index 763434b..78e9d07 100644 --- a/readopt.h +++ b/readopt.h @@ -1,3 +1,5 @@ +/* vim:set sw=4 ts=4 et: */ + #pragma once #include @@ -97,3 +99,443 @@ int readopt_validate_within(struct readopt_oper *oper); size_t readopt_select_upper(struct readopt_bounds bounds); /* Get the lower limit. This does not always return the minimum. */ size_t readopt_select_lower(struct readopt_bounds bounds); + +#ifdef READOPT_IMPLEMENTATION + +#include +#include + +static void parse_arg(struct readopt_parser *rp, const char *arg); + +static void parse_opt(struct readopt_parser *rp, enum readopt_form form, const char **pos); + +static struct readopt_opt *match_opt(struct readopt_parser *rp, enum readopt_form form, const char **needle); +static struct readopt_opt *match_finish(struct readopt_parser *rp, const char **needle, const char *adv, struct readopt_opt *opt); + +static void update_opt(struct readopt_parser *rp, const char *attach, struct readopt_opt *opt); +static void update_oper(struct readopt_parser *rp, struct readopt_view_strings val); + +static void assign_opers(struct readopt_parser *rp); + +static void add_val(struct readopt_parser *rp, struct readopt_oper *oper, const char *string, int end); + +static const char *skip_incl(const char *outer, const char *inner); + +static void occ_opt(struct readopt_parser *rp, struct readopt_opt *opt); + +static void permute_val(struct readopt_parser *rp, struct readopt_view_strings *target, const char *val, int end); +static void incr_between(const char **start, const char **stop, struct readopt_view_strings *curr, struct readopt_view_strings *exclude); +static void permute_rest(const char **target, struct readopt_view_strings start); + +int readopt_parse(struct readopt_parser *rp) +{ + /* Check whether the current offset is at the end of argv. */ + size_t off = rp->state.curr.arg - rp->args.strings; + if (off >= rp->args.len) + { + if (rp->state.pending) + { + /* The last specified option required an argument, but no argument has been provided. */ + rp->error = READOPT_ERROR_NOVAL; + return 0; + } + + for (size_t i = 0; readopt_validate_opt(rp->opts + i); i++) + { + if (!readopt_validate_within(&rp->opts[i].cont.oper)) + { + rp->error = READOPT_ERROR_RANGEOPT; + return 0; + } + } + + assign_opers(rp); + return 0; + } + + if (rp->state.pending) + { + add_val(rp, &rp->state.curr.opt->cont.oper, *rp->state.curr.arg, 0); + ++rp->state.curr.arg; + return !rp->error; + } + + parse_arg(rp, *rp->state.curr.arg); + + /* If grouped options are still being parsed, they should not be discarded. */ + if (!rp->state.grppos) + ++rp->state.curr.arg; + + return !rp->error; +} + +static void parse_arg(struct readopt_parser *rp, const char *arg) +{ + /* Parse the next option in the grouped option string, which automatically advances it. */ + if (rp->state.grppos) + { + parse_opt(rp, READOPT_FORM_SHORT, &rp->state.grppos); + if (!*rp->state.grppos) + { + rp->state.grppos = NULL; + } + return; + } + + const char *pos = arg; + + switch (*pos) + { + case '-': + ++pos; + switch (*pos) + { + case '-': + ++pos; + switch (*pos) + { + size_t off; + case '\0': + /* "--" denotes the end of options. */ + off = rp->args.len - (rp->state.curr.arg - rp->args.strings); + assert(off); + if (off == 1) + { + /* No operands after the "--". */ + return; + } + + update_oper(rp, (struct readopt_view_strings){ + .len = off - 1, + .strings = rp->state.curr.arg + 1}); + rp->state.curr.arg = rp->args.strings + rp->args.len - 1; + + return; + default: + parse_opt(rp, READOPT_FORM_LONG, &pos); + return; + } + case '\0': + update_oper(rp, (struct readopt_view_strings){.len = 1, .strings = (const char *[]){arg}}); + return; + default: + parse_opt(rp, READOPT_FORM_SHORT, &pos); + return; + } + default: + update_oper(rp, (struct readopt_view_strings){.len = 1, .strings = (const char *[]){arg}}); + return; + } +} + +static void parse_opt(struct readopt_parser *rp, enum readopt_form form, const char **pos) +{ + struct readopt_opt *match; + assert(form == READOPT_FORM_SHORT || form == READOPT_FORM_LONG); + + if (form == READOPT_FORM_SHORT) + { + match = match_opt(rp, form, pos); + if (match) + { + const char *strpos = *pos; + + if (!match->cont.req && *strpos) + { + rp->state.grppos = strpos; + update_opt(rp, NULL, match); + } + else + { + update_opt(rp, *strpos ? strpos : NULL, match); + } + } + else + { + rp->error = READOPT_ERROR_NOTOPT; + } + } + else + { + /* Match and advance pos to the end of the match. */ + match = match_opt(rp, form, pos); + + if (match) + { + switch (**pos) + { + case '\0': + update_opt(rp, NULL, match); + break; + case '=': + ++(*pos); + update_opt(rp, *pos, match); + break; + default: + rp->error = READOPT_ERROR_NOTOPT; + break; + } + } + else + { + rp->error = READOPT_ERROR_NOTOPT; + } + } +} + +static struct readopt_opt *match_opt(struct readopt_parser *rp, enum readopt_form form, const char **needle) +{ + /* This represents the last inexact match. */ + struct + { + /* The current advanced string. */ + const char *adv; + /* The current option. */ + struct readopt_opt *opt; + } loose = {0}; + + struct readopt_opt *haystack = rp->opts; + for (size_t i = 0; readopt_validate_opt(haystack + i); i++) + { + /* Iterate through all short or long names of the current option. */ + char **names = haystack[i].names[form]; + + if (!names) + /* Ignore the option as it does not have names in the required form. */ + continue; + + const char *cmp = loose.adv; + + for (size_t j = 0; names[j]; j++) + { + char *name = names[j]; + cmp = skip_incl(*needle, name); + + if (!cmp) + continue; + + if (!*cmp) + /* A guaranteed match. */ + return match_finish(rp, needle, cmp, haystack + i); + else if ((cmp - *needle) > (loose.adv - *needle)) + /* Maybe a match, maybe not. */ + loose.adv = cmp, loose.opt = haystack + i; + } + } + + return match_finish(rp, needle, loose.adv, loose.opt); +} + +static struct readopt_opt *match_finish(struct readopt_parser *rp, const char **needle, const char *adv, struct readopt_opt *opt) +{ + if (adv) + *needle = adv; + + if (opt) + rp->state.curr.opt = opt; + + return opt; +} + +static void update_opt(struct readopt_parser *rp, const char *attach, struct readopt_opt *opt) +{ + if (opt->cont.req) + { + if (attach) + { + /* --opt=value, --opt=, -ovalue */ + struct readopt_oper *curr = &rp->state.curr.opt->cont.oper; + occ_opt(rp, opt); + add_val(rp, curr, attach, 0); + } + else + { + /* --opt value, -o value */ + rp->state.pending = 1; + occ_opt(rp, opt); + } + } + else + { + occ_opt(rp, opt); + if (attach) + rp->error = READOPT_ERROR_NOTREQ; + } +} + +static void update_oper(struct readopt_parser *rp, struct readopt_view_strings val) +{ + assert(val.len && val.strings); + + if (val.len == 1) + { + ++rp->state.curr.ioper.len; + permute_val(rp, &rp->state.curr.ioper, val.strings[0], 1); + } + else + { + permute_rest(rp->state.curr.eoval, val); + rp->state.curr.ioper.len += val.len; + } +} + +static void assign_opers(struct readopt_parser *rp) +{ + size_t count = rp->state.curr.ioper.len; + + size_t nlower = 0; + size_t nupper = 0; + for (size_t i = 0; readopt_validate_oper(rp->opers + i); i++) + { + nlower += readopt_select_lower(rp->opers[i].bounds); + nupper += readopt_select_upper(rp->opers[i].bounds); + } + + if (count < nlower) + { + rp->error = READOPT_ERROR_RANGEOPER; + return; + } + + struct + { + size_t extra; + size_t req; + } rest = { + count - nlower, + nlower}; + + for (size_t i = 0; readopt_validate_oper(rp->opers + i); i++) + { + if (count == 0 || !rp->opers[i].val.strings) + { + size_t off = count - (rest.extra + rest.req); + rp->opers[i].val.strings = rp->state.curr.ioper.strings + off; + } + + size_t lower = readopt_select_lower(rp->opers[i].bounds); + size_t upper = readopt_select_upper(rp->opers[i].bounds); + int inf = rp->opers[i].bounds.inf; + + size_t add; + + /* Add required elements. */ + add = rest.req > lower ? lower : rest.req; + rp->opers[i].val.len += add, rest.req -= add; + + /* Add optional elements. */ + add = inf ? rest.extra : rest.extra > upper ? upper + : rest.extra; + rp->opers[i].val.len += add, rest.extra -= add; + } + + if (rest.extra || rest.req) + rp->error = READOPT_ERROR_RANGEOPER; +} + +static void add_val(struct readopt_parser *rp, struct readopt_oper *oper, const char *string, int end) +{ + rp->state.pending = 0; + + if (!readopt_validate_within(oper)) + rp->error = READOPT_ERROR_RANGEOPT; + else + permute_val(rp, &oper->val, string, end); +} + +static const char *skip_incl(const char *outer, const char *inner) +{ + while (*inner == *outer) + { + if (!*inner) + return outer; + ++inner, ++outer; + } + return !*inner ? outer : NULL; +} + +static void occ_opt(struct readopt_parser *rp, struct readopt_opt *opt) +{ + assert(opt); + rp->state.curr.opt = opt; + ++rp->state.curr.opt->cont.oper.val.len; +} + +static void permute_val(struct readopt_parser *rp, struct readopt_view_strings *target, const char *val, int end) +{ + if (!target->strings) + /* Fallback position when no value has yet been set. */ + target->strings = rp->state.curr.eoval - (end ? 0 : rp->state.curr.ioper.len); + + const char **pos = target->strings + (target->len - 1); + + assert(rp->state.curr.arg >= rp->state.curr.eoval); + + memmove(pos + 1, pos, (rp->state.curr.eoval - pos) * sizeof *pos); + + *pos = val; + ++rp->state.curr.eoval; + + const char **start = pos, **stop = rp->state.curr.eoval; + + /* Increment all value pointers in the options which are between start and stop (inclusive). */ + for (size_t i = 0; readopt_validate_opt(rp->opts + i); i++) + incr_between(start, stop, &rp->opts[i].cont.oper.val, target); + + incr_between(start, stop, &rp->state.curr.ioper, target); +} + +static void incr_between(const char **start, const char **stop, struct readopt_view_strings *curr, struct readopt_view_strings *exclude) +{ + if (curr->strings >= start && curr->strings <= stop && curr != exclude) + ++curr->strings; +} + +static void permute_rest(const char **target, struct readopt_view_strings start) +{ + memmove(target, start.strings, start.len * sizeof *start.strings); +} + +void readopt_parser_init(struct readopt_parser *rp, struct readopt_opt *opts, struct readopt_oper *opers, struct readopt_view_strings args) +{ + *rp = (struct readopt_parser){ + .args = args, + .opts = opts, + .opers = opers, + .state.curr = { + .arg = args.strings, + .eoval = args.strings}}; +} + +int readopt_validate_opt(struct readopt_opt *opt) +{ + assert(opt); + return opt->names[0] || opt->names[1]; +} + +int readopt_validate_oper(struct readopt_oper *oper) +{ + assert(oper); + return !!oper->name; +} + +int readopt_validate_within(struct readopt_oper *oper) +{ + size_t occ = oper->val.len; + size_t upper = readopt_select_upper(oper->bounds); + size_t lower = readopt_select_lower(oper->bounds); + return occ >= lower && (occ <= upper || oper->bounds.inf); +} + +size_t readopt_select_upper(struct readopt_bounds bounds) +{ + return bounds.val[0] > bounds.val[1] ? bounds.val[0] : bounds.val[1]; +} + +size_t readopt_select_lower(struct readopt_bounds bounds) +{ + return bounds.inf ? readopt_select_upper(bounds) : bounds.val[0] < bounds.val[1] ? bounds.val[0] + : bounds.val[1]; +} + +#endif diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index d75afe0..0000000 --- a/test/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -CC = cc -LDFLAGS = -lreadopt -MACROS = -D_POSIX_C_SOURCE=200809L -CFLAGS = $(MACROS) --std=c99 -O2 -g -Wall -Wextra -Wpedantic - -SOURCES = test.c -OBJECTS = $(SOURCES:.c=.o) - -RM = rm -f -- -CP = cp -- - -all: test - -%.o: %.c - $(CC) $(CFLAGS) -c $< - -test: $(OBJECTS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - -clean: - $(RM) $(OBJECTS) - -.PHONY: clean diff --git a/test/build.ninja b/test/build.ninja index 55728cb..694a062 100644 --- a/test/build.ninja +++ b/test/build.ninja @@ -1,4 +1,4 @@ -# vim: set tabstop=2 shiftwidth=2 expandtab : +# vim:set tabstop=2 shiftwidth=2 expandtab: include config.ninja diff --git a/test/config.ninja b/test/config.ninja index 0c81395..5897456 100644 --- a/test/config.ninja +++ b/test/config.ninja @@ -1,9 +1,9 @@ -# vim: set tabstop=2 shiftwidth=2 expandtab : +# vim:set tabstop=2 shiftwidth=2 expandtab: cc = cc -macros = -D _POSIX_C_SOURCE=200809L -D NDEBUG -cflags = --std=c99 -Wall -Wextra -Wpedantic -g $macros -O2 +macros = -D NDEBUG +cflags = -std=c99 -Wall -Wextra -Wpedantic -g $macros -O2 ldflags = -ldlibs = -lreadopt +ldlibs = target = ./test diff --git a/test/test.c b/test/test.c index f963299..f6a5523 100644 --- a/test/test.c +++ b/test/test.c @@ -1,200 +1,163 @@ -#include -#include -#include -#include +#define READOPT_IMPLEMENTATION -#include +#include "../readopt.h" -int -main(int argc, char **argv) +int main(int argc, char **argv) { - if (!*argv) - return EXIT_FAILURE; + struct readopt_opt opts[] = { + { + .names = { + [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("e", "x"), + [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("expr", "expression"), + }, + .cont = { + .req = 1, + .oper.bounds.val = { + 1, + 4, + }, + }, + }, + { + .names = { + [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("c"), + [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("config"), + }, + .cont = { + .req = 1, + .oper = { + .name = "file", + .bounds.val = { + 2, + }, + }, + }, + }, + { + .names = { + [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("i"), + [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("uri"), + }, + .cont = { + .req = 1, + .oper.bounds.inf = 1, + }, + }, + { + .names = { + [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("b"), + [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("backup", "backup-file"), + }, + .cont = { + .req = 1, + .oper.bounds.inf = 1, + }, + }, + { + .names = { + [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("v"), + [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("verbose"), + }, + .cont.oper.bounds.val = { + 3, + }, + }, + { + .names = { + [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("s"), + [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("sort"), + }, + .cont.oper.bounds.inf = 1, + }, + { + .names = { + [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("help"), + }, + .cont.oper.bounds.val = { + 1, + }, + }, + { + .names = { + [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("V"), + [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("version"), + }, + .cont.oper.bounds.val = { + 1, + }, + }, + {0}, + }; - struct readopt_opt opts[] = { - { - .names = { - [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("e", "x"), - [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("expr", "expression") - }, - .cont = { - .req = 1, - .oper.bounds.val = {1, 4} - } - }, - { - .names = { - [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("c"), - [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("config") - }, - .cont = { - .req = 1, - .oper = { - .name = "file", - .bounds.val = {2}, - } - } - }, - { - .names = { - [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("i"), - [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("uri") - }, - .cont = { - .req = 1, - .oper.bounds.inf = 1 - } - }, - { - .names = { - [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("b"), - [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("backup", "backup-file") - }, - .cont = { - .req = 1, - .oper.bounds.inf = 1 - } - }, - { - .names = { - [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("v"), - [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("verbose") - }, - .cont.oper.bounds.val = {3} - }, - { - .names = { - [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("s"), - [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("sort") - }, - .cont.oper.bounds.inf = 1 - }, - { - .names = { - [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("help") - }, - .cont.oper.bounds.val = {1} - }, - { - .names = { - [READOPT_FORM_SHORT] = READOPT_ALLOC_STRINGS("V"), - [READOPT_FORM_LONG] = READOPT_ALLOC_STRINGS("version") - }, - .cont.oper.bounds.val = {1} - }, - {0} - }; + struct readopt_oper opers[] = { + {.name = "pattern", + .bounds.inf = 1}, + {.name = "file", + .bounds = { + .val = {1}, + .inf = 1}}, + {.name = "name", .bounds = {.val = {1}, .inf = 1}}, + {0}}; - struct readopt_oper opers[] = { - { - .name = "pattern", - .bounds.inf = 1 - }, - { - .name = "file", - .bounds = { - .val = {1}, - .inf = 1 - } - }, - { - .name = "name", - .bounds = { - .val = {1}, - .inf = 1 - } - }, - {0} - }; + struct readopt_parser rp; + readopt_parser_init( + &rp, + opts, + opers, + (struct readopt_view_strings){ + .strings = (const char **)argv + 1, + .len = argc - 1}); - struct readopt_parser rp; - readopt_parser_init( - &rp, - opts, - opers, - (struct readopt_view_strings){ - .strings = (const char **)argv + 1, - .len = argc - 1 - } - ); + while (readopt_parse(&rp)) + ; - while (readopt_parse(&rp)); + fprintf(stderr, "error: %d\n", rp.error); + if (rp.error != READOPT_ERROR_SUCCESS) + { + return 1; + } - fputs("error: ", stderr); - readopt_put_error(rp.error, &(struct readopt_write_context){ - .dest.stream = stderr - }); - fputc('\n', stderr); - if (rp.error != READOPT_ERROR_SUCCESS) { - return EXIT_FAILURE; - } + printf("opt:\n"); + { + struct readopt_opt *curr = rp.opts; + for (size_t i = 0; readopt_validate_opt(curr + i); i++) + { + for (size_t j = 0; j < sizeof curr[i].names / sizeof *curr[i].names; j++) + { + if (curr[i].names[j]) + { + for (size_t k = 0; curr[i].names[j][k]; k++) + { + printf("%s ", curr[i].names[j][k]); + } + } + } + printf("{ [%zu] ", curr[i].cont.oper.val.len); + if (curr[i].cont.req) + { + struct readopt_view_strings val = curr[i].cont.oper.val; + for (size_t j = 0; j < val.len; j++) + { + printf("%s ", val.strings[j]); + } + } + printf("}\n"); + } + } - printf("opt:\n"); - { - struct readopt_opt *curr = rp.opts; - for (size_t i = 0; readopt_validate_opt(curr + i); i++) { - for (size_t j = 0; j < sizeof curr[i].names / sizeof *curr[i].names; j++) { - if (curr[i].names[j]) { - for (size_t k = 0; curr[i].names[j][k]; k++) { - printf("%s ", curr[i].names[j][k]); - } - } - } - printf("{ [%zu] ", curr[i].cont.oper.val.len); - if (curr[i].cont.req) { - struct readopt_view_strings val = curr[i].cont.oper.val; - for (size_t j = 0; j < val.len; j++) { - printf("%s ", val.strings[j]); - } - } - printf("}\n"); - } - } + printf("oper:\n"); + { + struct readopt_oper *curr = rp.opers; + for (size_t i = 0; readopt_validate_oper(curr + i); i++) + { + printf("%s { [%zu] ", curr[i].name, curr[i].val.len); + for (size_t j = 0; j < curr[i].val.len; j++) + { + printf("%s ", curr[i].val.strings[j]); + } + printf("}\n"); + } + } - printf("oper:\n"); - { - struct readopt_oper *curr = rp.opers; - for (size_t i = 0; readopt_validate_oper(curr + i); i++) { - printf("%s { [%zu] ", curr[i].name, curr[i].val.len); - for (size_t j = 0; j < curr[i].val.len; j++) { - printf("%s ", curr[i].val.strings[j]); - } - printf("}\n"); - } - } - - printf("usage (plain) (stream):\n"); - readopt_put_usage(&rp, &(struct readopt_format_context){ - .fmt = READOPT_FORMAT_PLAIN, - .wr = &(struct readopt_write_context){ - .dest.stream = stdout, - } - }); - printf("\nusage (mdoc) (stream):\n"); - readopt_put_usage(&rp, &(struct readopt_format_context){ - .fmt = READOPT_FORMAT_MDOC, - .wr = &(struct readopt_write_context){ - .dest.stream = stdout, - } - }); - - size_t sz = 0; - char *buf = NULL; - /* alternatively, use fmemopen and increase the buffer size via the callback */ - FILE *stream = open_memstream(&buf, &sz); - readopt_put_usage(&rp, &(struct readopt_format_context){ - .fmt = READOPT_FORMAT_MDOC, - .wr = &(struct readopt_write_context){ - .dest = { - .stream = stream, - .size = SIZE_MAX - } - } - }); - fflush(stream); - printf("usage (mdoc) (buffer):\n%s", buf); - fclose(stream); - free(buf); - - return 0; + return 0; }