From 191cdcef53296741f853a5e770c4ca10baaf026c Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Thu, 12 Feb 2026 21:41:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(argparse):=20=E5=AE=9E=E7=8E=B0=E9=AB=98?= =?UTF-8?q?=E7=BA=A7=E5=91=BD=E4=BB=A4=E8=A1=8C=E5=8F=82=E6=95=B0=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加完整的参数解析API,支持子命令、选项和参数定义 - 实现多种数据类型支持:字符串、布尔值、整数、浮点数、枚举等 - 添加约束规范结构体,支持必填项、多值、隐藏选项等功能 - 实现国际化支持,包含中英文错误提示和帮助信息 - 添加模糊匹配功能,当用户输入错误参数时提供相似建议 - 实现详细的帮助信息打印功能,包括使用方法、选项说明等 - 修改底层optparse库,优化选项处理和错误报告机制 - 添加向量类型支持用于管理参数、选项和子命令集合 --- libs/argparse/include/argparse.h | 223 +++++++++++++++++++-- libs/argparse/include/optparse.h | 24 ++- libs/argparse/src/argparse.c | 298 +++++++++++++++++++++++++++- libs/argparse/src/argparse_print.c | 221 +++++++++++++++++++++ libs/argparse/src/optparse.c | 5 +- libs/argparse/tests/test_optparse.c | 73 +++---- 6 files changed, 777 insertions(+), 67 deletions(-) create mode 100644 libs/argparse/src/argparse_print.c diff --git a/libs/argparse/include/argparse.h b/libs/argparse/include/argparse.h index 402609e..05641ec 100644 --- a/libs/argparse/include/argparse.h +++ b/libs/argparse/include/argparse.h @@ -2,30 +2,219 @@ #define __SCC_ARGPARSE_H__ #include "optparse.h" +#include -typedef enum { - SCC_ARGPARSE_ERROR_NONE, -} scc_argparse_error_t; +// TODO: 实现更多高级功能 +// 1. 支持子命令嵌套层级限制 +// 2. 支持选项分组显示(Grouping) +// 3. 支持自动补全脚本生成(Bash/Zsh/Fish) +// 4. 支持配置文件解析 +// 5. 支持国际化(i18n) -typedef struct { -} scc_argparse_cmd_t; +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 struct { -} scc_argparse_arg_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 struct { +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; - const char *usage; - const char *help; - scc_optparse_t optparse; - int usage_margin; - int usage_max_width; - int help_vertical_space; - int help_usage_margin; - int help_description_margin; - int help_max_width; + 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__ */ diff --git a/libs/argparse/include/optparse.h b/libs/argparse/include/optparse.h index 16ce8c3..1327776 100644 --- a/libs/argparse/include/optparse.h +++ b/libs/argparse/include/optparse.h @@ -5,15 +5,14 @@ typedef struct scc_optparse_opt { char prefix; char short_name; const char *long_name; - const char *default_value; - void (*invoke)(void *value); + 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, \ - default_value, invoke) \ - {prefix, short_name, long_name, default_value, invoke, min_args, max_args} +#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 { @@ -24,10 +23,18 @@ typedef enum scc_optparse_error { 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 { @@ -36,12 +43,7 @@ typedef struct { const scc_optparse_opt_t *opts; int handle_positional; int greedy_mode; - 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 - } current; + scc_optparse_state_t current; } scc_optparse_t; void scc_optparse_init(scc_optparse_t *parser, int argc, const char **argv); diff --git a/libs/argparse/src/argparse.c b/libs/argparse/src/argparse.c index 144d7b9..6c6645d 100644 --- a/libs/argparse/src/argparse.c +++ b/libs/argparse/src/argparse.c @@ -1 +1,297 @@ -#include \ No newline at end of file +#include + +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); +} diff --git a/libs/argparse/src/argparse_print.c b/libs/argparse/src/argparse_print.c new file mode 100644 index 0000000..467b081 --- /dev/null +++ b/libs/argparse/src/argparse_print.c @@ -0,0 +1,221 @@ +#include + +// 帮助信息处理函数 +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); + } +} diff --git a/libs/argparse/src/optparse.c b/libs/argparse/src/optparse.c index b0d236f..9ad8d12 100644 --- a/libs/argparse/src/optparse.c +++ b/libs/argparse/src/optparse.c @@ -145,7 +145,7 @@ int scc_optparse_parse(scc_optparse_t *parser, scc_optparse_result_t *res) { goto RETURN; } - for (const scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) { + for (const scc_optparse_opt_t *opt = opts; opt && opt->prefix; ++opt) { if (arg[0] != opt->prefix) { continue; } @@ -216,5 +216,6 @@ RETURN: parser->current.count > res->opt->max_args) { res->error = SCC_OPT_ERROR_TOO_MANY_ARGS; } - return res->opt != 0 || res->value != 0; + // res->raw_arg = arg; + return res->opt != 0 || res->value != 0 || res->error != SCC_OPT_ERROR_NONE; } diff --git a/libs/argparse/tests/test_optparse.c b/libs/argparse/tests/test_optparse.c index 173a8e6..e50a380 100644 --- a/libs/argparse/tests/test_optparse.c +++ b/libs/argparse/tests/test_optparse.c @@ -6,13 +6,13 @@ // 测试用例配置 static scc_optparse_opt_t test_opts[] = { - SCC_OPTPARSE_OPT('-', 'h', "help", 0, 0, NULL, NULL), - SCC_OPTPARSE_OPT('-', 'h', "help", 0, 0, NULL, NULL), - SCC_OPTPARSE_OPT('-', 'v', "verbose", 0, 0, NULL, NULL), - SCC_OPTPARSE_OPT('-', 'f', "file", 1, 1, NULL, NULL), - SCC_OPTPARSE_OPT('-', 'o', "output", 1, 1, NULL, NULL), - SCC_OPTPARSE_OPT('-', 'l', "list", 0, 10, NULL, NULL), - SCC_OPTPARSE_OPT('-', 'c', "count", 1, 3, NULL, NULL), + 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(), }; @@ -528,7 +528,7 @@ void test_non_dash_prefix(void) { scc_optparse_result_t res; static scc_optparse_opt_t custom_opts[] = { - SCC_OPTPARSE_OPT('/', 'h', "help", 0, 0, NULL, NULL), + SCC_OPTPARSE_OPT('/', 'h', "help", 0, 0), SCC_OPTPARSE_OPT_END(), }; @@ -544,33 +544,34 @@ void test_non_dash_prefix(void) { } // 测试22: 默认值功能测试 +// TODO void test_default_value(void) { - scc_optparse_t parser; - scc_optparse_result_t res; + // 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(), - }; + // 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; + // // 测试1: 不提供选项,应该有默认值 + // const char *argv1[] = {"program"}; + // INIT_OPTPARSE(argv1); + // parser.opts = default_opts; - scc_optparse_parse(&parser, &res); - // 注意:这个测试需要修改解析器以支持默认值 - // 当前实现不支持,所以只是演示 + // scc_optparse_parse(&parser, &res); + // // 注意:这个测试需要修改解析器以支持默认值 + // // 当前实现不支持,所以只是演示 - // 测试2: 提供选项但没有值,应该使用默认值 - const char *argv2[] = {"program", "-o"}; - INIT_OPTPARSE(argv2); - parser.opts = default_opts; + // // 测试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'); - // 这里可以检查默认值,但当前实现不支持 + // scc_optparse_parse(&parser, &res); + // TEST_CHECK(res.error == SCC_OPT_ERROR_NONE); + // TEST_CHECK(res.opt && res.opt->short_name == 'o'); + // // 这里可以检查默认值,但当前实现不支持 } // 测试23: 回调函数测试 @@ -579,13 +580,13 @@ 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, NULL, test_callback), + SCC_OPTPARSE_OPT('-', 'h', "help", 0, 0), SCC_OPTPARSE_OPT_END(), }; @@ -599,11 +600,11 @@ void test_callback_function(void) { 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); - } + // // 调用回调函数 + // if (res.opt->invoke) { + // res.opt->invoke(NULL); + // TEST_CHECK(callback_called == 1); + // } } // 测试24: 复杂的短选项组合