Files
scc/libs/argparse/src/optparse.c
zzy d1b215861c feat(argparse): 新增命令行参数解析库
新增 argparse 库的基础框架,包含以下组件:

- 创建了 argparse 库的 cbuild.toml 配置文件,定义包信息和依赖关系
- 实现了核心的参数解析功能,包括 optparse.h 和 optparse.c
- 定义了参数解析相关的数据结构和枚举类型
- 实现了完整的命令行选项解析逻辑,支持长短选项、参数绑定等功能
- 添加了全面的单元测试,覆盖各种使用场景和边界情况
- 包含对短选项连写、长选项等号形式、选项终止符等特性的支持
2026-02-03 12:35:45 +08:00

214 lines
6.9 KiB
C

#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;
}