feat(argparse): 实现高级命令行参数解析库

- 添加完整的参数解析API,支持子命令、选项和参数定义
- 实现多种数据类型支持:字符串、布尔值、整数、浮点数、枚举等
- 添加约束规范结构体,支持必填项、多值、隐藏选项等功能
- 实现国际化支持,包含中英文错误提示和帮助信息
- 添加模糊匹配功能,当用户输入错误参数时提供相似建议
- 实现详细的帮助信息打印功能,包括使用方法、选项说明等
- 修改底层optparse库,优化选项处理和错误报告机制
- 添加向量类型支持用于管理参数、选项和子命令集合
This commit is contained in:
zzy
2026-02-12 21:41:57 +08:00
parent 34d7eb3c42
commit 191cdcef53
6 changed files with 777 additions and 67 deletions

View 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);
}
}