Compare commits
3 Commits
2a90e165a5
...
191cdcef53
| Author | SHA1 | Date | |
|---|---|---|---|
| 191cdcef53 | |||
| 34d7eb3c42 | |||
| d1b215861c |
10
libs/argparse/cbuild.toml
Normal file
10
libs/argparse/cbuild.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "argparse"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = []
|
||||||
|
description = ""
|
||||||
|
|
||||||
|
dependencies = [{ name = "scc_core", path = "../../runtime/scc_core" }]
|
||||||
|
# dependencies = []
|
||||||
|
# features = {}
|
||||||
|
# default_features = []
|
||||||
220
libs/argparse/include/argparse.h
Normal file
220
libs/argparse/include/argparse.h
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#ifndef __SCC_ARGPARSE_H__
|
||||||
|
#define __SCC_ARGPARSE_H__
|
||||||
|
|
||||||
|
#include "optparse.h"
|
||||||
|
#include <scc_core.h>
|
||||||
|
|
||||||
|
// TODO: 实现更多高级功能
|
||||||
|
// 1. 支持子命令嵌套层级限制
|
||||||
|
// 2. 支持选项分组显示(Grouping)
|
||||||
|
// 3. 支持自动补全脚本生成(Bash/Zsh/Fish)
|
||||||
|
// 4. 支持配置文件解析
|
||||||
|
// 5. 支持国际化(i18n)
|
||||||
|
|
||||||
|
typedef struct scc_argparse_spec scc_argparse_spec_t;
|
||||||
|
typedef struct scc_argparse_arg scc_argparse_arg_t;
|
||||||
|
typedef struct scc_argparse_opt scc_argparse_opt_t;
|
||||||
|
typedef struct scc_argparse_cmd scc_argparse_cmd_t;
|
||||||
|
|
||||||
|
typedef SCC_VEC(scc_argparse_arg_t) scc_argparse_arg_vec_t;
|
||||||
|
typedef SCC_VEC(scc_argparse_opt_t) scc_argparse_opt_vec_t;
|
||||||
|
typedef SCC_VEC(scc_argparse_cmd_t) scc_argparse_cmd_vec_t;
|
||||||
|
|
||||||
|
typedef enum scc_argparse_val_type {
|
||||||
|
SCC_ARGPARSE_VAL_TYPE_UNKNOWN,
|
||||||
|
SCC_ARGPARSE_VAL_TYPE_STRING,
|
||||||
|
SCC_ARGPARSE_VAL_TYPE_BOOL,
|
||||||
|
SCC_ARGPARSE_VAL_TYPE_INT,
|
||||||
|
SCC_ARGPARSE_VAL_TYPE_FLOAT,
|
||||||
|
SCC_ARGPARSE_VAL_TYPE_ENUM,
|
||||||
|
SCC_ARGPARSE_VAL_TYPE_LIST,
|
||||||
|
SCC_ARGPARSE_VAL_TYPE_COUNT,
|
||||||
|
} scc_argparse_val_type_t;
|
||||||
|
|
||||||
|
typedef enum scc_argparse_err {
|
||||||
|
SCC_ARGPARSE_ERR_NONE,
|
||||||
|
SCC_ARGPARSE_ERR_UNKNOWN_ERR,
|
||||||
|
SCC_ARGPARSE_ERR_INVALID_ARG,
|
||||||
|
SCC_ARGPARSE_ERR_INVALID_VALUE,
|
||||||
|
SCC_ARGPARSE_ERR_MISSING_ARG,
|
||||||
|
SCC_ARGPARSE_ERR_MISSING_VALUE,
|
||||||
|
SCC_ARGPARSE_ERR_UNKNOWN_ARG,
|
||||||
|
SCC_ARGPARSE_ERR_UNKNOWN_VALUE,
|
||||||
|
} scc_argparse_err_t;
|
||||||
|
|
||||||
|
// 约束规范结构体
|
||||||
|
struct scc_argparse_spec {
|
||||||
|
scc_argparse_val_type_t value_type; // 值类型
|
||||||
|
const char *raw_value;
|
||||||
|
|
||||||
|
// 存储位置
|
||||||
|
union {
|
||||||
|
cbool *bool_store; // 布尔值存储
|
||||||
|
int *int_store; // 整数存储
|
||||||
|
float *float_store; // 浮点数存储
|
||||||
|
const char **str_store; // 字符串存储
|
||||||
|
char **str_alloc_store; // 字符串存储(使用alloc,需要free)
|
||||||
|
void **ptr_store; // 通用指针存储
|
||||||
|
} store;
|
||||||
|
|
||||||
|
// 枚举值约束
|
||||||
|
struct {
|
||||||
|
const char **values; // 枚举值数组
|
||||||
|
int count; // 枚举值数量
|
||||||
|
} choices;
|
||||||
|
|
||||||
|
// 其他约束标志
|
||||||
|
cbool flag_required; // 是否必须
|
||||||
|
cbool flag_store_as_count; // 是否存储为计数(如 -vvv 返回3)
|
||||||
|
cbool flag_allow_empty; // 是否允许空字符串
|
||||||
|
cbool flag_hidden; // 是否隐藏(不显示在帮助)
|
||||||
|
cbool flag_takes_multiple; // 是否接受多个值(如 -I dir1 -I dir2)
|
||||||
|
cbool flag_parse_complex; // 是否进行复杂解析(如 1K, 2M, 3G)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct scc_argparse_arg {
|
||||||
|
scc_argparse_spec_t spec;
|
||||||
|
const char *name;
|
||||||
|
const char *description;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct scc_argparse_opt {
|
||||||
|
scc_argparse_spec_t spec;
|
||||||
|
char short_name;
|
||||||
|
const char *long_name;
|
||||||
|
const char *description;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct scc_argparse_cmd {
|
||||||
|
const char *name;
|
||||||
|
const char *description;
|
||||||
|
scc_argparse_arg_vec_t args;
|
||||||
|
scc_argparse_opt_vec_t opts;
|
||||||
|
scc_argparse_cmd_vec_t subcmds;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SCC_ARGPARSE_LANG_EN,
|
||||||
|
SCC_ARGPARSE_LANG_ZH,
|
||||||
|
} scc_argparse_lang_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *prog_name;
|
||||||
|
const char *version;
|
||||||
|
const char *description;
|
||||||
|
const char *epilog;
|
||||||
|
scc_argparse_cmd_t root_cmd;
|
||||||
|
scc_argparse_lang_t lang;
|
||||||
|
cbool need_help;
|
||||||
|
cbool need_version;
|
||||||
|
cbool need_debug;
|
||||||
|
} scc_argparse_t;
|
||||||
|
|
||||||
|
typedef SCC_VEC(scc_optparse_opt_t) scc_optparse_opt_vec_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
scc_argparse_cmd_t *current_cmd; // 当前正在解析的命令
|
||||||
|
scc_optparse_t optparse; // 底层解析器
|
||||||
|
scc_optparse_opt_vec_t opts; // 当前命令的选项列表
|
||||||
|
scc_optparse_result_t result;
|
||||||
|
int positional_index; // 当前处理的位置参数索引
|
||||||
|
cbool parsing_done; // 是否已完成解析
|
||||||
|
scc_argparse_lang_t lang;
|
||||||
|
} scc_argparse_context_t;
|
||||||
|
|
||||||
|
void scc_argparse_init(scc_argparse_t *parser, const char *program_name,
|
||||||
|
const char *description);
|
||||||
|
scc_argparse_cmd_t *scc_argparse_get_root(scc_argparse_t *parser);
|
||||||
|
void scc_argparse_drop(scc_argparse_t *parser);
|
||||||
|
int scc_argparse_parse(scc_argparse_t *parser, int argc, const char **argv);
|
||||||
|
|
||||||
|
void scc_argparse_cmd_init(scc_argparse_cmd_t *cmd, const char *name,
|
||||||
|
const char *description);
|
||||||
|
void scc_argparse_cmd_drop(scc_argparse_cmd_t *cmd);
|
||||||
|
|
||||||
|
void scc_argparse_cmd_add_arg(scc_argparse_cmd_t *cmd,
|
||||||
|
const scc_argparse_arg_t *arg);
|
||||||
|
void scc_argparse_cmd_add_opt(scc_argparse_cmd_t *cmd,
|
||||||
|
const scc_argparse_opt_t *opt);
|
||||||
|
void scc_argparse_cmd_add_subcmd(scc_argparse_cmd_t *cmd,
|
||||||
|
const scc_argparse_cmd_t *subcmd);
|
||||||
|
|
||||||
|
void scc_argparse_print_help(scc_argparse_t *parser, scc_argparse_cmd_t *cmd);
|
||||||
|
void scc_argparse_print_error(scc_argparse_context_t *ctx,
|
||||||
|
scc_argparse_err_t err);
|
||||||
|
|
||||||
|
static inline void scc_argparse_spec_init(scc_argparse_spec_t *spec) {
|
||||||
|
spec->value_type = SCC_ARGPARSE_VAL_TYPE_STRING;
|
||||||
|
spec->raw_value = null;
|
||||||
|
spec->store.ptr_store = null;
|
||||||
|
|
||||||
|
spec->choices.count = 0;
|
||||||
|
spec->choices.values = null;
|
||||||
|
|
||||||
|
spec->flag_required = false;
|
||||||
|
spec->flag_store_as_count = false;
|
||||||
|
spec->flag_allow_empty = false;
|
||||||
|
spec->flag_hidden = false;
|
||||||
|
spec->flag_takes_multiple = false;
|
||||||
|
spec->flag_parse_complex = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void scc_argparse_opt_init(scc_argparse_opt_t *opt,
|
||||||
|
char short_name, const char *long_name,
|
||||||
|
const char *desc) {
|
||||||
|
opt->short_name = short_name;
|
||||||
|
opt->long_name = long_name;
|
||||||
|
opt->description = desc;
|
||||||
|
scc_argparse_spec_init(&opt->spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void scc_argparse_arg_init(scc_argparse_arg_t *arg,
|
||||||
|
const char *name, const char *desc) {
|
||||||
|
arg->name = name;
|
||||||
|
arg->description = desc;
|
||||||
|
scc_argparse_spec_init(&arg->spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void scc_argparse_spec_setup_bool(scc_argparse_spec_t *spec,
|
||||||
|
cbool *store) {
|
||||||
|
spec->value_type = SCC_ARGPARSE_VAL_TYPE_BOOL;
|
||||||
|
spec->store.bool_store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void scc_argparse_spec_setup_count(scc_argparse_spec_t *spec,
|
||||||
|
int *store) {
|
||||||
|
spec->value_type = SCC_ARGPARSE_VAL_TYPE_COUNT;
|
||||||
|
spec->flag_store_as_count = true; // 自动设为计数模式
|
||||||
|
spec->store.int_store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void scc_argparse_spec_setup_string(scc_argparse_spec_t *spec,
|
||||||
|
const char **store) {
|
||||||
|
spec->value_type = SCC_ARGPARSE_VAL_TYPE_STRING;
|
||||||
|
spec->store.str_store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void scc_argparse_spec_setup_int(scc_argparse_spec_t *spec,
|
||||||
|
int *store) {
|
||||||
|
spec->value_type = SCC_ARGPARSE_VAL_TYPE_INT;
|
||||||
|
spec->store.int_store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void scc_argparse_spec_setup_float(scc_argparse_spec_t *spec,
|
||||||
|
float *store) {
|
||||||
|
spec->value_type = SCC_ARGPARSE_VAL_TYPE_FLOAT;
|
||||||
|
spec->store.float_store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SCC_ARGPARSE_MACRO_SETTER(attr) \
|
||||||
|
static inline void scc_argparse_spec_set_##attr(scc_argparse_spec_t *spec, \
|
||||||
|
cbool flag) { \
|
||||||
|
spec->flag_##attr = flag; \
|
||||||
|
}
|
||||||
|
SCC_ARGPARSE_MACRO_SETTER(required)
|
||||||
|
SCC_ARGPARSE_MACRO_SETTER(allow_empty)
|
||||||
|
SCC_ARGPARSE_MACRO_SETTER(hidden)
|
||||||
|
SCC_ARGPARSE_MACRO_SETTER(takes_multiple)
|
||||||
|
SCC_ARGPARSE_MACRO_SETTER(parse_complex)
|
||||||
|
|
||||||
|
#endif /* __SCC_ARGPARSE_H__ */
|
||||||
55
libs/argparse/include/optparse.h
Normal file
55
libs/argparse/include/optparse.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef __SCC_OPTPARSER_H__
|
||||||
|
#define __SCC_OPTPARSER_H__
|
||||||
|
|
||||||
|
typedef struct scc_optparse_opt {
|
||||||
|
char prefix;
|
||||||
|
char short_name;
|
||||||
|
const char *long_name;
|
||||||
|
void (*handle)(void *user_data);
|
||||||
|
void *user_data;
|
||||||
|
int min_args;
|
||||||
|
int max_args;
|
||||||
|
} scc_optparse_opt_t;
|
||||||
|
|
||||||
|
#define SCC_OPTPARSE_OPT(prefix, short_name, long_name, min_args, max_args) \
|
||||||
|
{prefix, short_name, long_name, 0, 0, min_args, max_args}
|
||||||
|
#define SCC_OPTPARSE_OPT_END() {0}
|
||||||
|
|
||||||
|
typedef enum scc_optparse_error {
|
||||||
|
SCC_OPT_ERROR_NONE,
|
||||||
|
SCC_OPT_ERROR_NOT_FOUND_LONG_ARG,
|
||||||
|
SCC_OPT_ERROR_NOT_FOUND_SHORT_ARG,
|
||||||
|
SCC_OPT_ERROR_NOT_ENOUGH_ARGS,
|
||||||
|
SCC_OPT_ERROR_TOO_MANY_ARGS,
|
||||||
|
} scc_optparse_error_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const scc_optparse_opt_t *opt;
|
||||||
|
int count; // check for min_args <= count <= max_args
|
||||||
|
int arg_pos; // for argv pos
|
||||||
|
int opt_pos; // for short pos
|
||||||
|
} scc_optparse_state_t;
|
||||||
|
|
||||||
|
typedef struct scc_optparse_result {
|
||||||
|
const scc_optparse_opt_t *opt;
|
||||||
|
const char *value;
|
||||||
|
int error;
|
||||||
|
const char *raw_arg;
|
||||||
|
} scc_optparse_result_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int argc;
|
||||||
|
const char **argv;
|
||||||
|
const scc_optparse_opt_t *opts;
|
||||||
|
int handle_positional;
|
||||||
|
int greedy_mode;
|
||||||
|
scc_optparse_state_t current;
|
||||||
|
} scc_optparse_t;
|
||||||
|
|
||||||
|
void scc_optparse_init(scc_optparse_t *parser, int argc, const char **argv);
|
||||||
|
void scc_optparse_drop(scc_optparse_t *parser);
|
||||||
|
void scc_optparse_set(scc_optparse_t *parser, const scc_optparse_opt_t *opts);
|
||||||
|
void scc_optparse_reset(scc_optparse_t *parser);
|
||||||
|
int scc_optparse_parse(scc_optparse_t *parser, scc_optparse_result_t *res);
|
||||||
|
|
||||||
|
#endif /* __SCC_OPTPARSER_H__ */
|
||||||
297
libs/argparse/src/argparse.c
Normal file
297
libs/argparse/src/argparse.c
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
#include <argparse.h>
|
||||||
|
|
||||||
|
void scc_argparse_init(scc_argparse_t *parser, const char *program_name,
|
||||||
|
const char *description) {
|
||||||
|
parser->prog_name = program_name;
|
||||||
|
parser->version = "0.1.0";
|
||||||
|
parser->description = description;
|
||||||
|
parser->epilog = null;
|
||||||
|
|
||||||
|
parser->lang = SCC_ARGPARSE_LANG_EN;
|
||||||
|
parser->need_help = true;
|
||||||
|
parser->need_version = true;
|
||||||
|
parser->need_debug = false;
|
||||||
|
scc_argparse_cmd_init(&parser->root_cmd, parser->prog_name,
|
||||||
|
parser->description);
|
||||||
|
}
|
||||||
|
|
||||||
|
scc_argparse_cmd_t *scc_argparse_get_root(scc_argparse_t *parser) {
|
||||||
|
return &parser->root_cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scc_argparse_drop(scc_argparse_t *parser) {
|
||||||
|
scc_argparse_cmd_drop(&parser->root_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline scc_argparse_cmd_t *is_subcommand(scc_argparse_cmd_t *cmd,
|
||||||
|
const char *name) {
|
||||||
|
if (!scc_vec_size(cmd->subcmds)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
scc_vec_foreach(cmd->subcmds, i) {
|
||||||
|
scc_argparse_cmd_t *subcmd = &scc_vec_at(cmd->subcmds, i);
|
||||||
|
if (scc_strcmp(subcmd->name, name) == 0) {
|
||||||
|
return subcmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void parse_cmd(scc_optparse_t *optparse,
|
||||||
|
scc_optparse_opt_vec_t *opts,
|
||||||
|
scc_argparse_cmd_t *cmd) {
|
||||||
|
scc_vec_free(*opts);
|
||||||
|
scc_vec_init(*opts);
|
||||||
|
scc_vec_foreach(cmd->opts, i) {
|
||||||
|
scc_argparse_opt_t *opt = &scc_vec_at(cmd->opts, i);
|
||||||
|
int min_args = 0;
|
||||||
|
int max_args = 0;
|
||||||
|
|
||||||
|
// 根据选项类型推断 min_args 和 max_args
|
||||||
|
switch (opt->spec.value_type) {
|
||||||
|
case SCC_ARGPARSE_VAL_TYPE_STRING:
|
||||||
|
case SCC_ARGPARSE_VAL_TYPE_INT:
|
||||||
|
case SCC_ARGPARSE_VAL_TYPE_FLOAT:
|
||||||
|
min_args = 1;
|
||||||
|
max_args = 1;
|
||||||
|
break;
|
||||||
|
case SCC_ARGPARSE_VAL_TYPE_BOOL:
|
||||||
|
min_args = 0;
|
||||||
|
max_args = 0;
|
||||||
|
break;
|
||||||
|
case SCC_ARGPARSE_VAL_TYPE_COUNT:
|
||||||
|
min_args = 0;
|
||||||
|
max_args = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
min_args = 0;
|
||||||
|
max_args = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
scc_vec_push(*opts, ((scc_optparse_opt_t){
|
||||||
|
.prefix = '-',
|
||||||
|
.short_name = opt->short_name,
|
||||||
|
.long_name = opt->long_name,
|
||||||
|
.min_args = min_args,
|
||||||
|
.max_args = max_args,
|
||||||
|
.user_data = opt,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
scc_vec_push(*opts, (scc_optparse_opt_t)SCC_OPTPARSE_OPT_END());
|
||||||
|
scc_optparse_set(optparse, opts->data);
|
||||||
|
}
|
||||||
|
static void push_help(scc_argparse_cmd_t *cmd) {
|
||||||
|
if (cmd == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scc_vec_push(cmd->opts, ((scc_argparse_opt_t){
|
||||||
|
.short_name = 'h',
|
||||||
|
.long_name = "help",
|
||||||
|
.description = 0,
|
||||||
|
.spec.value_type = SCC_ARGPARSE_VAL_TYPE_BOOL,
|
||||||
|
}));
|
||||||
|
scc_vec_foreach(cmd->subcmds, i) {
|
||||||
|
push_help(&scc_vec_at(cmd->subcmds, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_context(scc_argparse_context_t *ctx, scc_argparse_t *parser,
|
||||||
|
int argc, const char **argv) {
|
||||||
|
ctx->current_cmd = scc_argparse_get_root(parser);
|
||||||
|
if (parser->need_help) {
|
||||||
|
push_help(ctx->current_cmd);
|
||||||
|
}
|
||||||
|
scc_optparse_init(&ctx->optparse, argc, argv);
|
||||||
|
scc_vec_init(ctx->opts);
|
||||||
|
parse_cmd(&ctx->optparse, &ctx->opts, ctx->current_cmd);
|
||||||
|
ctx->positional_index = 0;
|
||||||
|
ctx->parsing_done = false;
|
||||||
|
ctx->lang = parser->lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_parse_error(scc_argparse_t *parser,
|
||||||
|
scc_argparse_context_t *ctx) {
|
||||||
|
scc_argparse_err_t error = SCC_ARGPARSE_ERR_NONE;
|
||||||
|
switch (ctx->result.error) {
|
||||||
|
case SCC_OPT_ERROR_NOT_FOUND_SHORT_ARG:
|
||||||
|
case SCC_OPT_ERROR_NOT_FOUND_LONG_ARG:
|
||||||
|
error = SCC_ARGPARSE_ERR_UNKNOWN_ARG;
|
||||||
|
break;
|
||||||
|
case SCC_OPT_ERROR_NOT_ENOUGH_ARGS:
|
||||||
|
error = SCC_ARGPARSE_ERR_MISSING_VALUE;
|
||||||
|
break;
|
||||||
|
case SCC_OPT_ERROR_TOO_MANY_ARGS:
|
||||||
|
error = SCC_ARGPARSE_ERR_INVALID_ARG;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = SCC_ARGPARSE_ERR_UNKNOWN_ERR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scc_argparse_print_error(ctx, error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validate_and_cleanup(scc_argparse_context_t *ctx,
|
||||||
|
scc_argparse_t *parser) {
|
||||||
|
// 检查必需参数是否都已提供
|
||||||
|
scc_vec_foreach(ctx->current_cmd->args, i) {
|
||||||
|
scc_argparse_arg_t *arg = &scc_vec_at(ctx->current_cmd->args, i);
|
||||||
|
if (arg->spec.flag_required && *arg->spec.store.str_store == NULL) {
|
||||||
|
scc_argparse_print_error(ctx, SCC_ARGPARSE_ERR_MISSING_ARG);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理资源
|
||||||
|
scc_vec_free(ctx->opts);
|
||||||
|
scc_optparse_drop(&ctx->optparse);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_option(scc_argparse_context_t *ctx, scc_argparse_t *parser) {
|
||||||
|
scc_argparse_opt_t *opt = (scc_argparse_opt_t *)ctx->result.opt->user_data;
|
||||||
|
|
||||||
|
if (parser->need_help && scc_strcmp(opt->long_name, "help") == 0) {
|
||||||
|
scc_argparse_print_help(parser, ctx->current_cmd);
|
||||||
|
ctx->parsing_done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt->spec.flag_store_as_count) {
|
||||||
|
(*opt->spec.store.int_store)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt->spec.value_type == SCC_ARGPARSE_VAL_TYPE_BOOL) {
|
||||||
|
*opt->spec.store.bool_store = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->result.value) {
|
||||||
|
opt->spec.raw_value = ctx->result.value;
|
||||||
|
*opt->spec.store.str_store = ctx->result.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // opt value == null or value != null
|
||||||
|
// scc_argparse_opt_t *org_opt =
|
||||||
|
// (scc_argparse_opt_t *)opt_res.opt->user_data;
|
||||||
|
// if (parser->need_help) {
|
||||||
|
// if (scc_strcmp(org_opt->long_name, "help") == 0) {
|
||||||
|
// scc_argparse_print_help(parser, current_cmd);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (org_opt->spec.store_as_count) {
|
||||||
|
// *org_opt->spec.store.int_store += 1;
|
||||||
|
// }
|
||||||
|
// if (org_opt->spec.value_type == SCC_ARGPARSE_VAL_TYPE_BOOL) {
|
||||||
|
// *org_opt->spec.store.bool_store = true;
|
||||||
|
// }
|
||||||
|
// if (opt_res.value) {
|
||||||
|
// org_opt->spec.raw_value = opt_res.value;
|
||||||
|
// // TODO
|
||||||
|
// *org_opt->spec.store.str_store = opt_res.value;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_positional_arg(scc_argparse_context_t *ctx,
|
||||||
|
scc_argparse_t *parser) {
|
||||||
|
scc_argparse_cmd_t *subcmd =
|
||||||
|
is_subcommand(ctx->current_cmd, ctx->result.value);
|
||||||
|
if (subcmd != NULL) {
|
||||||
|
ctx->current_cmd = subcmd;
|
||||||
|
parse_cmd(&ctx->optparse, &ctx->opts, ctx->current_cmd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->positional_index < scc_vec_size(ctx->current_cmd->args)) {
|
||||||
|
scc_argparse_arg_t *arg =
|
||||||
|
&scc_vec_at(ctx->current_cmd->args, ctx->positional_index);
|
||||||
|
*arg->spec.store.str_store = ctx->result.value;
|
||||||
|
arg->spec.raw_value = ctx->result.value;
|
||||||
|
ctx->positional_index++;
|
||||||
|
} else {
|
||||||
|
scc_argparse_print_error(ctx, SCC_ARGPARSE_ERR_UNKNOWN_ARG);
|
||||||
|
ctx->parsing_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // position arg
|
||||||
|
// scc_argparse_cmd_t *cmd = is_subcommand(current_cmd,
|
||||||
|
// opt_res.value);
|
||||||
|
|
||||||
|
// if (cmd != null) {
|
||||||
|
// current_cmd = cmd;
|
||||||
|
// parse_cmd(&optparse, &opts, current_cmd);
|
||||||
|
// } else {
|
||||||
|
// // TODO argument
|
||||||
|
// scc_vec_foreach(current_cmd->args, i) {
|
||||||
|
// scc_argparse_arg_t *arg = &scc_vec_at(current_cmd->args,
|
||||||
|
// i); if (*arg->spec.store.str_store == 0) {
|
||||||
|
// *arg->spec.store.str_store = opt_res.value;
|
||||||
|
// arg->spec.raw_value = opt_res.value;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
int scc_argparse_parse(scc_argparse_t *parser, int argc, const char **argv) {
|
||||||
|
scc_argparse_context_t ctx = {0};
|
||||||
|
init_context(&ctx, parser, argc, argv); // 初始化上下文
|
||||||
|
|
||||||
|
while (!ctx.parsing_done &&
|
||||||
|
scc_optparse_parse(&ctx.optparse, &ctx.result)) {
|
||||||
|
if (parser->need_debug) {
|
||||||
|
scc_printf("[%c:%s:%d] %s\n",
|
||||||
|
ctx.result.opt ? ctx.result.opt->short_name : '-',
|
||||||
|
ctx.result.opt ? ctx.result.opt->long_name : "--",
|
||||||
|
ctx.result.error, ctx.result.value);
|
||||||
|
}
|
||||||
|
if (ctx.result.error) {
|
||||||
|
handle_parse_error(parser, &ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.result.opt != null) {
|
||||||
|
handle_option(&ctx, parser);
|
||||||
|
} else if (ctx.result.value != null) {
|
||||||
|
handle_positional_arg(&ctx, parser);
|
||||||
|
} else {
|
||||||
|
UNREACHABLE(); // 不应到达此处
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_and_cleanup(&ctx, parser);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scc_argparse_cmd_init(scc_argparse_cmd_t *cmd, const char *name,
|
||||||
|
const char *description) {
|
||||||
|
cmd->name = name ? name : "cmd_name";
|
||||||
|
cmd->description = description ? description : "cmd_description";
|
||||||
|
scc_vec_init(cmd->args);
|
||||||
|
scc_vec_init(cmd->opts);
|
||||||
|
scc_vec_init(cmd->subcmds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scc_argparse_cmd_drop(scc_argparse_cmd_t *cmd) {
|
||||||
|
scc_vec_free(cmd->args);
|
||||||
|
scc_vec_free(cmd->opts);
|
||||||
|
scc_vec_foreach(cmd->subcmds, i) {
|
||||||
|
scc_argparse_cmd_drop(&scc_vec_at(cmd->subcmds, i));
|
||||||
|
}
|
||||||
|
scc_vec_free(cmd->subcmds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scc_argparse_cmd_add_arg(scc_argparse_cmd_t *cmd,
|
||||||
|
const scc_argparse_arg_t *arg) {
|
||||||
|
scc_vec_push(cmd->args, *arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scc_argparse_cmd_add_opt(scc_argparse_cmd_t *cmd,
|
||||||
|
const scc_argparse_opt_t *opt) {
|
||||||
|
scc_vec_push(cmd->opts, *opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scc_argparse_cmd_add_subcmd(scc_argparse_cmd_t *cmd,
|
||||||
|
const scc_argparse_cmd_t *subcmd) {
|
||||||
|
scc_vec_push(cmd->subcmds, *subcmd);
|
||||||
|
}
|
||||||
221
libs/argparse/src/argparse_print.c
Normal file
221
libs/argparse/src/argparse_print.c
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
#include <argparse.h>
|
||||||
|
|
||||||
|
// 帮助信息处理函数
|
||||||
|
enum {
|
||||||
|
ARGPARSE_USAGE,
|
||||||
|
ARGPARSE_OPTION,
|
||||||
|
ARGPARSE_COMMAND,
|
||||||
|
ARGPARSE_ARGUMENT,
|
||||||
|
|
||||||
|
ARGPARSE_SHOW_ARG,
|
||||||
|
ARGPARSE_SHOW_OPT,
|
||||||
|
ARGPARSE_SHOW_CMD,
|
||||||
|
ARGPARSE_SHOW_HELP_MSG,
|
||||||
|
|
||||||
|
ARGPARSE_UNKNOWN_ARGUMENT,
|
||||||
|
ARGPARSE_INVALID_VALUE_FOR_OPTION,
|
||||||
|
ARGPARSE_OPTION_MISSING_VALUE,
|
||||||
|
ARGPARSE_DID_YOU_MEAN,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *fmt_en[] = {
|
||||||
|
[ARGPARSE_USAGE] = "Usage: %s",
|
||||||
|
[ARGPARSE_OPTION] = " [Options]",
|
||||||
|
[ARGPARSE_COMMAND] = " [Commands]",
|
||||||
|
|
||||||
|
[ARGPARSE_SHOW_ARG] = "Arguments:\n",
|
||||||
|
[ARGPARSE_SHOW_OPT] = "Options:\n",
|
||||||
|
[ARGPARSE_SHOW_CMD] = "Commands:\n",
|
||||||
|
[ARGPARSE_SHOW_HELP_MSG] = "Show this help message and exit",
|
||||||
|
|
||||||
|
[ARGPARSE_UNKNOWN_ARGUMENT] = "Unknown argument '%s'",
|
||||||
|
[ARGPARSE_INVALID_VALUE_FOR_OPTION] = "Invalid value for option '%s'\n",
|
||||||
|
[ARGPARSE_OPTION_MISSING_VALUE] = "Option '%s' missing value\n",
|
||||||
|
[ARGPARSE_DID_YOU_MEAN] = "; Did you mean: '%s'?\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *fmt_zh[] = {
|
||||||
|
[ARGPARSE_USAGE] = "用法: %s",
|
||||||
|
[ARGPARSE_OPTION] = " [选项]",
|
||||||
|
[ARGPARSE_COMMAND] = " [命令]",
|
||||||
|
|
||||||
|
[ARGPARSE_SHOW_ARG] = "参数:\n",
|
||||||
|
[ARGPARSE_SHOW_OPT] = "选项:\n",
|
||||||
|
[ARGPARSE_SHOW_CMD] = "命令:\n",
|
||||||
|
[ARGPARSE_SHOW_HELP_MSG] = "显示帮助信息并退出",
|
||||||
|
|
||||||
|
[ARGPARSE_UNKNOWN_ARGUMENT] = "未知的参数 '%s'",
|
||||||
|
[ARGPARSE_INVALID_VALUE_FOR_OPTION] = "无效的选项值 '%s'\n",
|
||||||
|
[ARGPARSE_OPTION_MISSING_VALUE] = "选项 '%s' 缺少值\n",
|
||||||
|
[ARGPARSE_DID_YOU_MEAN] = "; 您是不是想要输入: '%s'?\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算两个字符串的编辑距离(Levenshtein距离)
|
||||||
|
* @param s1 字符串1(非空,以'\0'结尾)
|
||||||
|
* @param s2 字符串2(非空,以'\0'结尾)
|
||||||
|
* @return 编辑距离,内存分配失败返回 -1
|
||||||
|
*/
|
||||||
|
static int scc_levenshtein(const char *s1, const char *s2) {
|
||||||
|
usize len1 = scc_strlen(s1);
|
||||||
|
usize len2 = scc_strlen(s2);
|
||||||
|
|
||||||
|
// 保证 len1 >= len2 以减少内存占用
|
||||||
|
if (len1 < len2) {
|
||||||
|
const char *tmp = s1;
|
||||||
|
s1 = s2;
|
||||||
|
s2 = tmp;
|
||||||
|
usize tlen = len1;
|
||||||
|
len1 = len2;
|
||||||
|
len2 = tlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分配滚动数组(只需 len2+1 个 int)
|
||||||
|
int *dp = (int *)scc_malloc(sizeof(int) * (len2 + 1));
|
||||||
|
if (!dp)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// 初始化第0行
|
||||||
|
for (usize j = 0; j <= len2; ++j)
|
||||||
|
dp[j] = (int)j;
|
||||||
|
|
||||||
|
for (usize i = 1; i <= len1; ++i) {
|
||||||
|
int prev = dp[0];
|
||||||
|
dp[0] = (int)i;
|
||||||
|
for (usize j = 1; j <= len2; ++j) {
|
||||||
|
int temp = dp[j];
|
||||||
|
int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
|
||||||
|
dp[j] = scc_min(scc_min(dp[j] + 1, dp[j - 1] + 1), prev + cost);
|
||||||
|
prev = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int dist = dp[len2];
|
||||||
|
scc_free(dp);
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scc_argparse_print_help(scc_argparse_t *parser, scc_argparse_cmd_t *cmd) {
|
||||||
|
const char **lines =
|
||||||
|
(parser->lang == SCC_ARGPARSE_LANG_ZH) ? fmt_zh : fmt_en;
|
||||||
|
scc_printf(lines[ARGPARSE_USAGE],
|
||||||
|
cmd->name ? cmd->name : parser->prog_name);
|
||||||
|
if (scc_vec_size(cmd->opts) > 0)
|
||||||
|
scc_printf(lines[ARGPARSE_OPTION]);
|
||||||
|
scc_vec_foreach(cmd->args, i) {
|
||||||
|
scc_argparse_arg_t *arg = &scc_vec_at(cmd->args, i);
|
||||||
|
scc_printf(arg->spec.flag_required ? " <%s>" : " [%s]", arg->name);
|
||||||
|
}
|
||||||
|
if (scc_vec_size(cmd->subcmds) > 0)
|
||||||
|
scc_printf(lines[ARGPARSE_COMMAND]);
|
||||||
|
scc_printf("\n\n");
|
||||||
|
if (cmd->description)
|
||||||
|
scc_printf("%s\n\n", cmd->description);
|
||||||
|
|
||||||
|
if (scc_vec_size(cmd->args) > 0) {
|
||||||
|
scc_printf(lines[ARGPARSE_SHOW_ARG]);
|
||||||
|
scc_vec_foreach(cmd->args, i) {
|
||||||
|
scc_argparse_arg_t *arg = &scc_vec_at(cmd->args, i);
|
||||||
|
scc_printf(" %-20s %s", arg->name,
|
||||||
|
arg->description ? arg->description : "");
|
||||||
|
// if (!arg->spec.required && arg->spec.flags.has_default)
|
||||||
|
// scc_printf(" (default: %s)", arg->spec.default_value);
|
||||||
|
scc_printf("\n");
|
||||||
|
}
|
||||||
|
scc_printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scc_vec_size(cmd->opts) > 0) {
|
||||||
|
scc_printf(lines[ARGPARSE_SHOW_OPT]);
|
||||||
|
scc_vec_foreach(cmd->opts, i) {
|
||||||
|
scc_argparse_opt_t *opt = &scc_vec_at(cmd->opts, i);
|
||||||
|
if (opt->spec.flag_hidden)
|
||||||
|
continue;
|
||||||
|
if (opt->short_name == 'h')
|
||||||
|
opt->description = lines[ARGPARSE_SHOW_HELP_MSG];
|
||||||
|
char buf[64];
|
||||||
|
int pos = 0;
|
||||||
|
if (opt->short_name) {
|
||||||
|
buf[pos++] = '-';
|
||||||
|
buf[pos++] = opt->short_name;
|
||||||
|
if (opt->long_name) {
|
||||||
|
buf[pos++] = ',';
|
||||||
|
buf[pos++] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opt->long_name) {
|
||||||
|
buf[pos++] = '-';
|
||||||
|
buf[pos++] = '-';
|
||||||
|
for (const char *p = opt->long_name; *p; ++p)
|
||||||
|
buf[pos++] = *p;
|
||||||
|
}
|
||||||
|
buf[pos] = '\0';
|
||||||
|
scc_printf(" %-25s %s", buf,
|
||||||
|
opt->description ? opt->description : "");
|
||||||
|
// if (opt->spec.default_value)
|
||||||
|
// scc_printf(" (default: %s)", opt->spec.default_value);
|
||||||
|
scc_printf("\n");
|
||||||
|
}
|
||||||
|
scc_printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scc_vec_size(cmd->subcmds) > 0) {
|
||||||
|
scc_printf(lines[ARGPARSE_SHOW_CMD]);
|
||||||
|
scc_vec_foreach(cmd->subcmds, i) {
|
||||||
|
scc_argparse_cmd_t *sub = &scc_vec_at(cmd->subcmds, i);
|
||||||
|
scc_printf(" %-20s %s\n", sub->name,
|
||||||
|
sub->description ? sub->description : "");
|
||||||
|
}
|
||||||
|
scc_printf("\n");
|
||||||
|
}
|
||||||
|
if (parser->epilog)
|
||||||
|
scc_printf("%s\n", parser->epilog);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *scc_argparse_find_similar_arg(scc_argparse_cmd_t *cmd,
|
||||||
|
const char *arg) {
|
||||||
|
if (arg[0] == '-' && arg[1] == '-' && arg[2] != '\0') {
|
||||||
|
// opt arg
|
||||||
|
scc_vec_foreach(cmd->opts, i) {
|
||||||
|
if (scc_levenshtein(arg + 2, scc_vec_at(cmd->opts, i).long_name) <=
|
||||||
|
2) {
|
||||||
|
return scc_vec_at(cmd->opts, i).long_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// subcmd arg
|
||||||
|
scc_vec_foreach(cmd->subcmds, i) {
|
||||||
|
if (scc_levenshtein(arg, scc_vec_at(cmd->subcmds, i).name) <= 2) {
|
||||||
|
return scc_vec_at(cmd->subcmds, i).name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scc_argparse_print_error(scc_argparse_context_t *ctx,
|
||||||
|
scc_argparse_err_t err) {
|
||||||
|
const char **lines = (ctx->lang == SCC_ARGPARSE_LANG_ZH) ? fmt_zh : fmt_en;
|
||||||
|
const char *optname = ctx->result.raw_arg;
|
||||||
|
switch (err) {
|
||||||
|
case SCC_ARGPARSE_ERR_INVALID_ARG:
|
||||||
|
case SCC_ARGPARSE_ERR_INVALID_VALUE:
|
||||||
|
scc_printf(lines[ARGPARSE_INVALID_VALUE_FOR_OPTION], optname);
|
||||||
|
break;
|
||||||
|
case SCC_ARGPARSE_ERR_MISSING_ARG:
|
||||||
|
case SCC_ARGPARSE_ERR_MISSING_VALUE:
|
||||||
|
scc_printf(lines[ARGPARSE_OPTION_MISSING_VALUE], optname);
|
||||||
|
break;
|
||||||
|
case SCC_ARGPARSE_ERR_UNKNOWN_ARG:
|
||||||
|
case SCC_ARGPARSE_ERR_UNKNOWN_VALUE:
|
||||||
|
scc_printf(lines[ARGPARSE_UNKNOWN_ARGUMENT], optname);
|
||||||
|
const char *similar_arg =
|
||||||
|
scc_argparse_find_similar_arg(ctx->current_cmd, optname);
|
||||||
|
if (similar_arg != 0) {
|
||||||
|
scc_printf(lines[ARGPARSE_DID_YOU_MEAN], similar_arg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
scc_printf("Unknown error: %d\n", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
221
libs/argparse/src/optparse.c
Normal file
221
libs/argparse/src/optparse.c
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
#include <optparse.h>
|
||||||
|
|
||||||
|
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 (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;
|
||||||
|
}
|
||||||
728
libs/argparse/tests/test_optparse.c
Normal file
728
libs/argparse/tests/test_optparse.c
Normal file
@@ -0,0 +1,728 @@
|
|||||||
|
#include <optparse.h>
|
||||||
|
#include <utest/acutest.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// 测试用例配置
|
||||||
|
static scc_optparse_opt_t test_opts[] = {
|
||||||
|
SCC_OPTPARSE_OPT('-', 'h', "help", 0, 0),
|
||||||
|
SCC_OPTPARSE_OPT('-', 'h', "help", 0, 0),
|
||||||
|
SCC_OPTPARSE_OPT('-', 'v', "verbose", 0, 0),
|
||||||
|
SCC_OPTPARSE_OPT('-', 'f', "file", 1, 1),
|
||||||
|
SCC_OPTPARSE_OPT('-', 'o', "output", 1, 1),
|
||||||
|
SCC_OPTPARSE_OPT('-', 'l', "list", 0, 10),
|
||||||
|
SCC_OPTPARSE_OPT('-', 'c', "count", 1, 3),
|
||||||
|
SCC_OPTPARSE_OPT_END(),
|
||||||
|
};
|
||||||
|
|
||||||
|
#define INIT_OPTPARSE(argv) \
|
||||||
|
scc_optparse_init(&parser, sizeof(argv) / sizeof(argv[0]), argv); \
|
||||||
|
scc_optparse_set(&parser, test_opts);
|
||||||
|
|
||||||
|
// 测试1: 基础初始化
|
||||||
|
void test_init(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
const char *argv[] = {"program", "-h"};
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
scc_optparse_set(&parser, 0);
|
||||||
|
TEST_CHECK(parser.argc == 2);
|
||||||
|
TEST_CHECK(parser.argv == argv);
|
||||||
|
TEST_CHECK(parser.handle_positional == 0);
|
||||||
|
TEST_CHECK(parser.current.arg_pos == 1);
|
||||||
|
TEST_CHECK(parser.current.opt_pos == 0);
|
||||||
|
TEST_CHECK(parser.opts == 0);
|
||||||
|
TEST_CHECK(parser.current.opt == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试2: 简单短选项
|
||||||
|
void test_simple_short_options(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-h", "-v"};
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析第一个选项 -h
|
||||||
|
TEST_CHECK(scc_optparse_parse(&parser, &res) != 0);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'h');
|
||||||
|
TEST_CHECK(res.value == NULL);
|
||||||
|
|
||||||
|
// 解析第二个选项 -v
|
||||||
|
TEST_CHECK(scc_optparse_parse(&parser, &res) != 0);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'v');
|
||||||
|
TEST_CHECK(res.value == NULL);
|
||||||
|
|
||||||
|
// 没有更多选项
|
||||||
|
TEST_CHECK(scc_optparse_parse(&parser, &res) == 0);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(res.value == NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_simple_position_arg(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "a", "b"};
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
// 解析第一个选项
|
||||||
|
TEST_CHECK(scc_optparse_parse(&parser, &res) != 0);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "a") == 0);
|
||||||
|
|
||||||
|
// 解析第二个选项
|
||||||
|
TEST_CHECK(scc_optparse_parse(&parser, &res) != 0);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "b") == 0);
|
||||||
|
|
||||||
|
// 没有更多选项
|
||||||
|
TEST_CHECK(scc_optparse_parse(&parser, &res) == 0);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(res.value == NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试3: 带参数的短选项
|
||||||
|
void test_short_options_with_args(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-f", "file.txt", "-o", "output.txt"};
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -f file.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "file.txt") == 0);
|
||||||
|
|
||||||
|
// 解析 -o output.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'o');
|
||||||
|
TEST_CHECK(strcmp(res.value, "output.txt") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试4: 长选项
|
||||||
|
void test_long_options(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "--help", "--file=test.txt", "--output",
|
||||||
|
"out.txt"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 --help
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "help") == 0);
|
||||||
|
|
||||||
|
// 解析 --file=test.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "file") == 0);
|
||||||
|
TEST_CHECK(strcmp(res.value, "test.txt") == 0);
|
||||||
|
|
||||||
|
// 解析 --output out.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "output") == 0);
|
||||||
|
TEST_CHECK(strcmp(res.value, "out.txt") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试5: 混合选项和位置参数
|
||||||
|
void test_mixed_options_positional(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-h", "-f",
|
||||||
|
"input.txt", "positional1", "positional2"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -h
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'h');
|
||||||
|
|
||||||
|
// 解析 -f input.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "input.txt") == 0);
|
||||||
|
|
||||||
|
// 解析第一个位置参数
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "positional1") == 0);
|
||||||
|
|
||||||
|
// 解析第二个位置参数
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "positional2") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试6: 短选项连写
|
||||||
|
void test_combined_short_options(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-vh"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -v
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'v');
|
||||||
|
|
||||||
|
// 解析 -h
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'h');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试7: 错误处理 - 未知选项
|
||||||
|
void test_unknown_options(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-x", "--unknown"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -x (未知短选项)
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NOT_FOUND_SHORT_ARG);
|
||||||
|
|
||||||
|
// 重置解析器测试长选项
|
||||||
|
const char *argv2[] = {"program", "--unknown"};
|
||||||
|
INIT_OPTPARSE(argv2);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NOT_FOUND_LONG_ARG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试8: 选项值紧跟在短选项后
|
||||||
|
void test_short_option_with_attached_value(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-finput.txt", "-ooutput.txt"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -finput.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "input.txt") == 0);
|
||||||
|
|
||||||
|
// 解析 -ooutput.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'o');
|
||||||
|
TEST_CHECK(strcmp(res.value, "output.txt") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试9: 选项终止符 --
|
||||||
|
void test_option_terminator(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-h", "--", "-f", "file.txt"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -h
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'h');
|
||||||
|
|
||||||
|
// 解析 -- (应该触发位置参数模式)
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(parser.handle_positional == 1);
|
||||||
|
|
||||||
|
// 解析 -f (作为位置参数)
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "-f") == 0);
|
||||||
|
|
||||||
|
// 解析 file.txt (作为位置参数)
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "file.txt") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试10: 空参数和边界情况
|
||||||
|
void test_edge_cases(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
|
||||||
|
// 测试空参数数组
|
||||||
|
const char *argv1[] = {"program"};
|
||||||
|
INIT_OPTPARSE(argv1);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(res.value == NULL);
|
||||||
|
|
||||||
|
// 测试空字符串参数
|
||||||
|
const char *argv2[] = {"program", "-f", "", "-o", " "};
|
||||||
|
INIT_OPTPARSE(argv2);
|
||||||
|
|
||||||
|
// 解析 -f ""
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "") == 0);
|
||||||
|
|
||||||
|
// 解析 -o " "
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'o');
|
||||||
|
TEST_CHECK(strcmp(res.value, " ") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试11: 多参数选项
|
||||||
|
void test_multi_argument_option(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-l", "item1", "item2", "item3"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -l
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'l');
|
||||||
|
TEST_CHECK(res.value == NULL);
|
||||||
|
|
||||||
|
// 由于 -l 可以接受多个参数,后续的参数应该作为 -l 的值
|
||||||
|
// 但根据当前实现,可能需要多次调用
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "item1") == 0);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "item2") == 0);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "item3") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试12: 参数不足的错误
|
||||||
|
void test_insufficient_arguments(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-f"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
// 注意:当前实现可能需要额外检查
|
||||||
|
// TEST_CHECK(res.error == SCC_OPT_ERROR_NOT_ENOUGH_ARGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试13: 长选项带等号但无值
|
||||||
|
void test_long_option_with_equal_no_value(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "--file="};
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NOT_ENOUGH_ARGS);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "file") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试14: 重置和重用解析器
|
||||||
|
void test_parser_reuse(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
|
||||||
|
// 第一次使用
|
||||||
|
const char *argv1[] = {"program", "-h", "-v"};
|
||||||
|
INIT_OPTPARSE(argv1);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'h');
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'v');
|
||||||
|
|
||||||
|
// 重置并重用
|
||||||
|
const char *argv2[] = {"program", "-f", "test.txt"};
|
||||||
|
INIT_OPTPARSE(argv2);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "test.txt") == 0);
|
||||||
|
}
|
||||||
|
// 测试15: 短选项连写中带参数的情况
|
||||||
|
void test_combined_short_with_arg(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-vfinput.txt"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -v
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'v');
|
||||||
|
TEST_CHECK(res.value == NULL);
|
||||||
|
|
||||||
|
// 解析 -finput.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "input.txt") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试16: 参数超过最大值的情况
|
||||||
|
void test_too_many_arguments(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-f", "file1", "file2"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -f file1
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "file1") == 0);
|
||||||
|
|
||||||
|
// 尝试给 -f 第二个参数,应该返回错误
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "file2") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试17: 混合短选项和位置参数
|
||||||
|
void test_mixed_short_and_positional(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-v", "-f", "input.txt", "positional"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -v
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'v');
|
||||||
|
|
||||||
|
// 解析 -f input.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "input.txt") == 0);
|
||||||
|
|
||||||
|
// 解析位置参数
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "positional") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试18: 复杂的多参数选项
|
||||||
|
void test_complex_multi_argument(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-c", "1", "2", "3", "extra"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 -c 1
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'c');
|
||||||
|
TEST_CHECK(strcmp(res.value, "1") == 0);
|
||||||
|
|
||||||
|
// 解析 -c 2
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'c');
|
||||||
|
TEST_CHECK(strcmp(res.value, "2") == 0);
|
||||||
|
|
||||||
|
// 解析 -c 3
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'c');
|
||||||
|
TEST_CHECK(strcmp(res.value, "3") == 0);
|
||||||
|
|
||||||
|
// 第4个参数应该是位置参数
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "extra") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试19: 长选项带多个参数
|
||||||
|
void test_long_option_multi_args(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "--list", "item1", "item2", "item3"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
// 解析 --list
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "list") == 0);
|
||||||
|
TEST_CHECK(res.value == NULL);
|
||||||
|
|
||||||
|
// 解析 item1
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "list") == 0);
|
||||||
|
TEST_CHECK(strcmp(res.value, "item1") == 0);
|
||||||
|
|
||||||
|
// 解析 item2
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "list") == 0);
|
||||||
|
TEST_CHECK(strcmp(res.value, "item2") == 0);
|
||||||
|
|
||||||
|
// 解析 item3
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "list") == 0);
|
||||||
|
TEST_CHECK(strcmp(res.value, "item3") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试20: 空长选项名(只有--)
|
||||||
|
void test_empty_long_name(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "--"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(parser.handle_positional == 1);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "--") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试21: 选项前缀非'-'的情况
|
||||||
|
void test_non_dash_prefix(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
|
||||||
|
static scc_optparse_opt_t custom_opts[] = {
|
||||||
|
SCC_OPTPARSE_OPT('/', 'h', "help", 0, 0),
|
||||||
|
SCC_OPTPARSE_OPT_END(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *argv[] = {"program", "/h"};
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
parser.opts = custom_opts;
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt != NULL);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'h');
|
||||||
|
TEST_CHECK(res.opt && res.opt->prefix == '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试22: 默认值功能测试
|
||||||
|
// TODO
|
||||||
|
void test_default_value(void) {
|
||||||
|
// scc_optparse_t parser;
|
||||||
|
// scc_optparse_result_t res;
|
||||||
|
|
||||||
|
// static scc_optparse_opt_t default_opts[] = {
|
||||||
|
// SCC_OPTPARSE_OPT('-', 'o', "output", 0, 1, "default.txt", NULL),
|
||||||
|
// SCC_OPTPARSE_OPT_END(),
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // 测试1: 不提供选项,应该有默认值
|
||||||
|
// const char *argv1[] = {"program"};
|
||||||
|
// INIT_OPTPARSE(argv1);
|
||||||
|
// parser.opts = default_opts;
|
||||||
|
|
||||||
|
// scc_optparse_parse(&parser, &res);
|
||||||
|
// // 注意:这个测试需要修改解析器以支持默认值
|
||||||
|
// // 当前实现不支持,所以只是演示
|
||||||
|
|
||||||
|
// // 测试2: 提供选项但没有值,应该使用默认值
|
||||||
|
// const char *argv2[] = {"program", "-o"};
|
||||||
|
// INIT_OPTPARSE(argv2);
|
||||||
|
// parser.opts = default_opts;
|
||||||
|
|
||||||
|
// scc_optparse_parse(&parser, &res);
|
||||||
|
// TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
// TEST_CHECK(res.opt && res.opt->short_name == 'o');
|
||||||
|
// // 这里可以检查默认值,但当前实现不支持
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试23: 回调函数测试
|
||||||
|
static int callback_called = 0;
|
||||||
|
static void test_callback(void *value) {
|
||||||
|
(void)value;
|
||||||
|
callback_called = 1;
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
void test_callback_function(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
|
||||||
|
static scc_optparse_opt_t callback_opts[] = {
|
||||||
|
SCC_OPTPARSE_OPT('-', 'h', "help", 0, 0),
|
||||||
|
SCC_OPTPARSE_OPT_END(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *argv[] = {"program", "--help"};
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
parser.opts = callback_opts;
|
||||||
|
|
||||||
|
callback_called = 0;
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && strcmp(res.opt->long_name, "help") == 0);
|
||||||
|
|
||||||
|
// // 调用回调函数
|
||||||
|
// if (res.opt->invoke) {
|
||||||
|
// res.opt->invoke(NULL);
|
||||||
|
// TEST_CHECK(callback_called == 1);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试24: 复杂的短选项组合
|
||||||
|
void test_complex_short_combination(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-vhfinput.txt"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
scc_optparse_set(&parser, test_opts);
|
||||||
|
|
||||||
|
// 解析 -v
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'v');
|
||||||
|
|
||||||
|
// 解析 -h
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'h');
|
||||||
|
|
||||||
|
// 解析 -finput.txt
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.error == SCC_OPT_ERROR_NONE);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
TEST_CHECK(strcmp(res.value, "input.txt") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试25: 边界情况 - 单个横杠
|
||||||
|
void test_single_dash(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-", "--", "arg"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
scc_optparse_set(&parser, test_opts);
|
||||||
|
|
||||||
|
// 解析单个横杠(通常表示标准输入)
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.opt == NULL);
|
||||||
|
TEST_CHECK(strcmp(res.value, "-") == 0);
|
||||||
|
|
||||||
|
// 解析 --
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(parser.handle_positional == 1);
|
||||||
|
|
||||||
|
// 解析 arg
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(strcmp(res.value, "arg") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试26: 重置解析器状态
|
||||||
|
void test_parser_state_reset(void) {
|
||||||
|
scc_optparse_t parser;
|
||||||
|
scc_optparse_result_t res;
|
||||||
|
const char *argv[] = {"program", "-f", "file1", "--", "arg1", "arg2"};
|
||||||
|
|
||||||
|
INIT_OPTPARSE(argv);
|
||||||
|
scc_optparse_set(&parser, test_opts);
|
||||||
|
|
||||||
|
// 解析 -f file1
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'f');
|
||||||
|
|
||||||
|
// 解析 --
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(parser.handle_positional == 1);
|
||||||
|
|
||||||
|
// 解析 arg1
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(strcmp(res.value, "arg1") == 0);
|
||||||
|
|
||||||
|
// 解析 arg2
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(strcmp(res.value, "arg2") == 0);
|
||||||
|
|
||||||
|
// 重置解析器
|
||||||
|
const char *argv2[] = {"program", "-v", "-h"};
|
||||||
|
INIT_OPTPARSE(argv2);
|
||||||
|
|
||||||
|
// 应该能正确解析新的参数
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'v');
|
||||||
|
|
||||||
|
scc_optparse_parse(&parser, &res);
|
||||||
|
TEST_CHECK(res.opt && res.opt->short_name == 'h');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新测试列表
|
||||||
|
TEST_LIST = {
|
||||||
|
{"test_init", test_init},
|
||||||
|
{"test_simple_short_options", test_simple_short_options},
|
||||||
|
{"test_simple_position_arg", test_simple_position_arg},
|
||||||
|
{"test_short_options_with_args", test_short_options_with_args},
|
||||||
|
{"test_long_options", test_long_options},
|
||||||
|
{"test_mixed_options_positional", test_mixed_options_positional},
|
||||||
|
{"test_combined_short_options", test_combined_short_options},
|
||||||
|
{"test_unknown_options", test_unknown_options},
|
||||||
|
{"test_short_option_with_attached_value",
|
||||||
|
test_short_option_with_attached_value},
|
||||||
|
{"test_option_terminator", test_option_terminator},
|
||||||
|
{"test_edge_cases", test_edge_cases},
|
||||||
|
{"test_multi_argument_option", test_multi_argument_option},
|
||||||
|
{"test_insufficient_arguments", test_insufficient_arguments},
|
||||||
|
{"test_long_option_with_equal_no_value",
|
||||||
|
test_long_option_with_equal_no_value},
|
||||||
|
{"test_parser_reuse", test_parser_reuse},
|
||||||
|
{"test_combined_short_with_arg", test_combined_short_with_arg},
|
||||||
|
{"test_too_many_arguments", test_too_many_arguments},
|
||||||
|
{"test_mixed_short_and_positional", test_mixed_short_and_positional},
|
||||||
|
{"test_complex_multi_argument", test_complex_multi_argument},
|
||||||
|
{"test_long_option_multi_args", test_long_option_multi_args},
|
||||||
|
{"test_empty_long_name", test_empty_long_name},
|
||||||
|
{"test_non_dash_prefix", test_non_dash_prefix},
|
||||||
|
{"test_default_value", test_default_value},
|
||||||
|
{"test_callback_function", test_callback_function},
|
||||||
|
{"test_complex_short_combination", test_complex_short_combination},
|
||||||
|
{"test_single_dash", test_single_dash},
|
||||||
|
{"test_parser_state_reset", test_parser_state_reset},
|
||||||
|
{NULL, NULL},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user