feat(argparse): 新增命令行参数解析库
新增 argparse 库的基础框架,包含以下组件: - 创建了 argparse 库的 cbuild.toml 配置文件,定义包信息和依赖关系 - 实现了核心的参数解析功能,包括 optparse.h 和 optparse.c - 定义了参数解析相关的数据结构和枚举类型 - 实现了完整的命令行选项解析逻辑,支持长短选项、参数绑定等功能 - 添加了全面的单元测试,覆盖各种使用场景和边界情况 - 包含对短选项连写、长选项等号形式、选项终止符等特性的支持
This commit is contained in:
213
libs/argparse/src/optparse.c
Normal file
213
libs/argparse/src/optparse.c
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <optparse.h>
|
||||
|
||||
void scc_optparse_init(scc_optparse_t *parser, int argc, const char **argv) {
|
||||
parser->argc = argc;
|
||||
parser->argv = argv;
|
||||
parser->handle_positional = 0;
|
||||
parser->opts = 0;
|
||||
parser->prev = 0;
|
||||
parser->current_arg_pos = 1;
|
||||
parser->short_opt_pos = 0;
|
||||
}
|
||||
|
||||
static inline const char *str_chr(const char *s, char c) {
|
||||
while (*s && *s != c)
|
||||
s++;
|
||||
return *s ? s : 0;
|
||||
}
|
||||
|
||||
scc_optparse_opt_t *scc_optparse_get_long_name(scc_optparse_opt_t *opts,
|
||||
const char *name,
|
||||
const char end) {
|
||||
if (*name == '\0')
|
||||
return 0;
|
||||
for (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 (name[i] == '\0' || name[i] == end)
|
||||
return opt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
scc_optparse_opt_t *scc_optparse_get_short_name(scc_optparse_opt_t *opts,
|
||||
const char short_name) {
|
||||
for (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(scc_optparse_opt_t *opts,
|
||||
scc_optparse_result_t *res,
|
||||
const char *arg) {
|
||||
// caller MUST use `--` start arg
|
||||
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 int scc_optparse_parse_short_name(scc_optparse_opt_t *opts,
|
||||
scc_optparse_result_t *res,
|
||||
const char *arg,
|
||||
int arg_start) {
|
||||
for (int i = arg_start; arg[i]; ++i) {
|
||||
scc_optparse_opt_t *ret = scc_optparse_get_short_name(opts, arg[i]);
|
||||
if (ret == 0) {
|
||||
res->error = SCC_OPT_ERROR_NOT_FOUND_SHORT_ARG;
|
||||
return 0;
|
||||
}
|
||||
res->opt = ret;
|
||||
if (res->opt->parsed_args < res->opt->max_args && arg[i + 1] != '\0') {
|
||||
res->value = arg + i + 1;
|
||||
res->opt->parsed_args++;
|
||||
return 0;
|
||||
}
|
||||
return arg[i + 1] ? i + 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void scc_optparse_parse(scc_optparse_t *parser, scc_optparse_result_t *res) {
|
||||
scc_optparse_opt_t *opts = parser->opts;
|
||||
res->opt = 0;
|
||||
res->value = 0;
|
||||
res->error = SCC_OPT_ERROR_NONE;
|
||||
|
||||
if (parser->short_opt_pos != 0) {
|
||||
parser->short_opt_pos = scc_optparse_parse_short_name(
|
||||
opts, res, parser->argv[parser->current_arg_pos],
|
||||
parser->short_opt_pos);
|
||||
if (parser->short_opt_pos == 0) {
|
||||
parser->short_opt_pos = 0;
|
||||
parser->current_arg_pos += 1;
|
||||
}
|
||||
goto RETURN;
|
||||
}
|
||||
|
||||
while (parser->current_arg_pos < parser->argc) {
|
||||
const char *arg = parser->argv[parser->current_arg_pos];
|
||||
|
||||
if (arg[0] == '\0' || parser->handle_positional) {
|
||||
return scc_optparse_parse_position_arg(parser, arg, res);
|
||||
}
|
||||
|
||||
for (const scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) {
|
||||
if (arg[0] != opt->prefix) {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
if (!res->opt || res->error) {
|
||||
goto RETURN;
|
||||
}
|
||||
parser->prev = 0;
|
||||
if (res->value)
|
||||
res->opt->parsed_args++;
|
||||
parser->current_arg_pos++;
|
||||
|
||||
if (res->opt->parsed_args < res->opt->max_args &&
|
||||
res->opt->max_args > 0) {
|
||||
parser->prev = res->opt;
|
||||
}
|
||||
|
||||
if (res->opt->parsed_args < res->opt->min_args && !res->value) {
|
||||
goto NEXT;
|
||||
}
|
||||
goto RETURN;
|
||||
} else {
|
||||
// short option like -
|
||||
if (arg[1] == '\0') {
|
||||
// the `-`
|
||||
res->value = arg;
|
||||
parser->handle_positional = 1;
|
||||
parser->current_arg_pos++;
|
||||
goto RETURN;
|
||||
}
|
||||
parser->short_opt_pos =
|
||||
scc_optparse_parse_short_name(opts, res, arg, 1);
|
||||
if (!res->opt || res->error) {
|
||||
goto RETURN;
|
||||
}
|
||||
if (parser->short_opt_pos == 0) {
|
||||
parser->current_arg_pos++;
|
||||
}
|
||||
parser->prev = 0;
|
||||
|
||||
if (res->opt->parsed_args < res->opt->max_args &&
|
||||
res->opt->max_args > 0) {
|
||||
parser->prev = res->opt;
|
||||
}
|
||||
if (res->opt->parsed_args < res->opt->min_args) {
|
||||
goto NEXT;
|
||||
}
|
||||
goto RETURN;
|
||||
}
|
||||
}
|
||||
// position arg
|
||||
if (parser->prev &&
|
||||
parser->prev->parsed_args < parser->prev->max_args) {
|
||||
res->opt = parser->prev;
|
||||
res->value = arg;
|
||||
res->opt->parsed_args++;
|
||||
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 &&
|
||||
res->opt->parsed_args > res->opt->max_args) {
|
||||
res->error = SCC_OPT_ERROR_TOO_MANY_ARGS;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user