#include void scc_optparse_init(scc_optparse_t *parser, int argc, const char **argv) { parser->argc = argc; parser->argv = argv; parser->opts = 0; scc_optparse_reset(parser); } void scc_optparse_drop(scc_optparse_t *parser) { (void)parser; } void scc_optparse_set(scc_optparse_t *parser, const scc_optparse_opt_t *opts) { parser->opts = opts; scc_optparse_reset(parser); } void scc_optparse_reset(scc_optparse_t *parser) { parser->handle_positional = 0; parser->current.opt = 0; parser->current.count = 0; parser->current.arg_pos = 1; parser->current.opt_pos = 0; } static inline const char *str_chr(const char *s, char c) { while (*s && *s != c) s++; return *s ? s : 0; } const scc_optparse_opt_t * scc_optparse_get_long_name(const scc_optparse_opt_t *opts, const char *name, const char end) { if (*name == '\0') return 0; for (const scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) { if (!opt->long_name) { continue; } int i; for (i = 0; opt->long_name[i]; ++i) { if (name[i] != opt->long_name[i]) { break; } } if (opt->long_name[i] == '\0' && (name[i] == '\0' || name[i] == end)) return opt; } return 0; } const scc_optparse_opt_t * scc_optparse_get_short_name(const scc_optparse_opt_t *opts, const char short_name) { for (const scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) { if (opt->short_name == short_name) { return opt; } } return 0; } static inline void scc_optparse_parse_long_name(const scc_optparse_opt_t *opts, scc_optparse_result_t *res, const char *arg) { // caller MUST use `--` start arg const scc_optparse_opt_t *ret = 0; const char *chr = str_chr(arg, '='); if (chr) { // --file=a.txt ret = scc_optparse_get_long_name(opts, arg + 2, '='); if (ret == 0) { res->error = SCC_OPT_ERROR_NOT_FOUND_LONG_ARG; } else { res->opt = ret; if (chr[1] == '\0') { // TODO maybe can empty like -file= res->error = SCC_OPT_ERROR_NOT_ENOUGH_ARGS; } res->value = chr + 1; } } else { // --file a.txt ret = scc_optparse_get_long_name(opts, arg + 2, '\0'); if (ret == 0) { res->error = SCC_OPT_ERROR_NOT_FOUND_LONG_ARG; } else { res->opt = ret; } } } static inline void scc_optparse_parse_short_name(const scc_optparse_opt_t *opts, scc_optparse_result_t *res, const char *arg, int arg_start) { // const char *chr = // str_chr(arg + arg_start, '='); // TODO maybe short can have -I=/usr const scc_optparse_opt_t *ret = scc_optparse_get_short_name(opts, arg[arg_start]); if (ret == 0) { res->error = SCC_OPT_ERROR_NOT_FOUND_SHORT_ARG; } else { res->opt = ret; } } static inline void scc_optparse_parse_position_arg(scc_optparse_t *parser, const char *arg, scc_optparse_result_t *res) { parser->current.arg_pos++; res->value = arg; } static inline int scc_optparse_check(scc_optparse_t *parser, scc_optparse_result_t *res) { if (!res->opt || res->error) return 0; if (res->value) parser->current.count += 1; if (parser->current.count < res->opt->max_args && res->opt->max_args > 0) parser->current.opt = res->opt; if (parser->current.count < res->opt->min_args && !res->value) return 1; return 0; } int scc_optparse_parse(scc_optparse_t *parser, scc_optparse_result_t *res) { const scc_optparse_opt_t *opts = parser->opts; const char *arg = 0; res->opt = 0; res->value = 0; res->error = SCC_OPT_ERROR_NONE; if (parser->current.opt_pos != 0) { arg = parser->argv[parser->current.arg_pos]; goto CONTINUE_SHORT_OPTION; } while (parser->current.arg_pos < parser->argc) { arg = parser->argv[parser->current.arg_pos]; if (arg[0] == '\0' || parser->handle_positional) { scc_optparse_parse_position_arg(parser, arg, res); goto RETURN; } for (const scc_optparse_opt_t *opt = opts; opt && opt->prefix; ++opt) { if (arg[0] != opt->prefix) { continue; } parser->current.opt = 0; parser->current.count = 0; if (arg[1] == opt->prefix) { // long option like -- if (arg[2] == '\0') { // the `--` res->value = arg; parser->handle_positional = 1; parser->current.arg_pos++; goto RETURN; } scc_optparse_parse_long_name(opts, res, arg); parser->current.arg_pos += 1; if (scc_optparse_check(parser, res)) { goto NEXT; } goto RETURN; } else { // short option like - if (arg[1] == '\0') { // the `-` res->value = arg; parser->current.arg_pos++; goto RETURN; } parser->current.opt_pos = 1; CONTINUE_SHORT_OPTION: scc_optparse_parse_short_name(opts, res, arg, parser->current.opt_pos++); int have_next = arg[parser->current.opt_pos]; parser->current.opt_pos = have_next ? parser->current.opt_pos : 0; parser->current.arg_pos += have_next ? 0 : 1; if (scc_optparse_check(parser, res)) { if (have_next) { res->value = arg + parser->current.opt_pos; parser->current.opt_pos = 0; parser->current.arg_pos += 1; goto RETURN; } goto NEXT; } goto RETURN; } } // position arg if (parser->current.opt && parser->current.count < parser->current.opt->max_args) { res->opt = parser->current.opt; res->value = arg; parser->current.count++; parser->current.arg_pos++; goto RETURN; } else { scc_optparse_parse_position_arg(parser, arg, res); goto RETURN; } NEXT: continue; } RETURN: if (res->opt && res->opt->max_args > 0 && parser->current.count > res->opt->max_args) { res->error = SCC_OPT_ERROR_TOO_MANY_ARGS; } // res->raw_arg = arg; return res->opt != 0 || res->value != 0 || res->error != SCC_OPT_ERROR_NONE; }