#include // 帮助信息处理函数 enum { ARGPARSE_USAGE, ARGPARSE_OPTION, ARGPARSE_COMMAND, ARGPARSE_ARGUMENT, ARGPARSE_SHOW_ARG, ARGPARSE_SHOW_OPT, ARGPARSE_SHOW_CMD, ARGPARSE_SHOW_HELP_MSG, ARGPRASE_USING_HELP_HINT, 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", [ARGPRASE_USING_HELP_HINT] = "Using '-h' or '--help' to get more information\n", [ARGPARSE_UNKNOWN_ARGUMENT] = "Unknown argument '%s'\n", [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] = "显示帮助信息并退出", [ARGPRASE_USING_HELP_HINT] = "使用 '-h' 或者 '--help' 获取更多信息\n", [ARGPARSE_UNKNOWN_ARGUMENT] = "未知的参数 '%s'\n", [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; } static void print_usage(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"); } 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; print_usage(parser, cmd); 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]; scc_str_t buf; scc_str_init(&buf); int pos = 0; if (opt->short_name) { scc_str_append_ch(&buf, '-'); scc_str_append_ch(&buf, opt->short_name); if (opt->long_name) { scc_str_append_ch(&buf, ','); scc_str_append_ch(&buf, ' '); } } if (opt->long_name) { scc_str_append_ch(&buf, '-'); scc_str_append_ch(&buf, '-'); scc_str_append_cstr(&buf, opt->long_name, scc_strlen(opt->long_name)); } if (opt->spec.value_type == SCC_ARGPARSE_VAL_TYPE_ENUM) { scc_str_append_ch(&buf, '='); scc_str_append_ch(&buf, '<'); for (int j = 0; j < opt->spec.choices.count; j += 1) { if (opt->spec.choices.values[j] == 0) { continue; } if (j != 0) { scc_str_append_ch(&buf, '|'); } scc_str_append_cstr( &buf, opt->spec.choices.values[j], scc_strlen(opt->spec.choices.values[j])); } scc_str_append_ch(&buf, '>'); } scc_printf(" %-25s %s", scc_str_as_cstr(&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 == nullptr || cmd == nullptr) { return nullptr; } 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_t *parser, scc_argparse_err_t err) { const char **lines = (parser->lang == SCC_ARGPARSE_LANG_ZH) ? fmt_zh : fmt_en; switch (err) { case SCC_ARGPARSE_ERR_INVALID_ARG: case SCC_ARGPARSE_ERR_INVALID_VALUE: scc_printf(lines[ARGPARSE_INVALID_VALUE_FOR_OPTION], ctx->err_ctx); break; case SCC_ARGPARSE_ERR_MISSING_ARG: case SCC_ARGPARSE_ERR_MISSING_VALUE: scc_printf(lines[ARGPARSE_OPTION_MISSING_VALUE], ctx->err_ctx); break; case SCC_ARGPARSE_ERR_UNKNOWN_ARG: case SCC_ARGPARSE_ERR_UNKNOWN_VALUE: scc_printf(lines[ARGPARSE_UNKNOWN_ARGUMENT], ctx->result.raw_arg); const char *similar_arg = scc_argparse_find_similar_arg( ctx->current_cmd, ctx->result.raw_arg); if (similar_arg != 0) { scc_printf(lines[ARGPARSE_DID_YOU_MEAN], similar_arg); } break; // FACK ERROR case SCC_ARGPARSE_ERR_PNT_DEFAULT: return; default: scc_printf("Unknown error: %d\n", err); break; } if (parser->need_help) { scc_printf(lines[ARGPRASE_USING_HELP_HINT]); } if (parser->need_error_add_help) { scc_argparse_print_help(parser, ctx->current_cmd); } else if (parser->need_error_add_usage) { print_usage(parser, ctx->current_cmd); } }