From 8cfa9e31e39c870bd1ba2d937610c1aeaeb3c13c Mon Sep 17 00:00:00 2001 From: wargio Date: Sat, 6 Feb 2021 16:24:04 +0100 Subject: [PATCH] Support in newshell for `?*[j]` and `j*[j] ` --- librz/core/cautocmpl.c | 8 +- librz/core/cmd.c | 154 +++++++++++++------ librz/core/cmd_api.c | 205 +++++++++++++++++++++++--- librz/core/cmd_descs.c | 18 +++ librz/core/cmd_descs.h | 1 + librz/core/cmd_descs.yaml | 10 ++ librz/include/rz_cmd.h | 8 +- shlr/rizin-shell-parser/src/scanner.c | 12 +- test/db/cmd/cmd_help | 52 ++++++- test/unit/test_cmd.c | 56 ++++++- 10 files changed, 437 insertions(+), 87 deletions(-) diff --git a/librz/core/cautocmpl.c b/librz/core/cautocmpl.c index 2f331d5a52..5ccf76959d 100644 --- a/librz/core/cautocmpl.c +++ b/librz/core/cautocmpl.c @@ -89,10 +89,10 @@ err: return NULL; } -static bool do_autocmplt_cmdidentifier(RzCmd *cmd, const char *name, void *user) { +static bool do_autocmplt_cmdidentifier(RzCmd *cmd, const RzCmdDesc *desc, void *user) { struct autocmplt_cmdidentifier_t *u = (struct autocmplt_cmdidentifier_t *)user; - if (!strncmp(name, u->s, u->len)) { - rz_line_ns_completion_result_add(u->res, name); + if (!strncmp(desc->name, u->s, u->len)) { + rz_line_ns_completion_result_add(u->res, desc->name); } return true; } @@ -103,7 +103,7 @@ static void autocmplt_cmdidentifier(RzCore *core, RzLineNSCompletionResult *res, .s = s, .len = len, }; - rz_cmd_foreach_cmdname(core->rcmd, do_autocmplt_cmdidentifier, &u); + rz_cmd_foreach_cmdname(core->rcmd, NULL, do_autocmplt_cmdidentifier, &u); } static void autocmplt_cmd_arg_file(RzLineNSCompletionResult *res, const char *s, size_t len) { diff --git a/librz/core/cmd.c b/librz/core/cmd.c index c7bf3e21f9..5526a1e584 100644 --- a/librz/core/cmd.c +++ b/librz/core/cmd.c @@ -4618,7 +4618,8 @@ DEFINE_HANDLE_TS_FCN_AND_SYMBOL(arged_command) { if (!help_pra) { goto err; } - char *help_msg = rz_cmd_get_help(state->core->rcmd, help_pra, true); + bool use_color = state->core->print->flags & RZ_PRINT_FLAGS_COLOR; + char *help_msg = rz_cmd_get_help(state->core->rcmd, help_pra, use_color); if (!help_msg) { goto help_pra_err; } @@ -4760,55 +4761,116 @@ DEFINE_HANDLE_TS_FCN_AND_SYMBOL(redirect_command) { return res; } +typedef struct _search_help { + bool color; + RzStrBuf *sb; + PJ *pj; +} RzHelpSearch; + +static bool help_search_cmd_desc_entry(RzCmd *cmd, const RzCmdDesc *cd, void *user) { + rz_return_val_if_fail(cd, false); + RzHelpSearch *hs = (RzHelpSearch *)user; + if (hs->pj) { + rz_cmd_get_help_json(cmd, cd, hs->pj); + } else { + rz_cmd_get_help_strbuf(cmd, cd, hs->color, hs->sb); + } + return true; +} + +RZ_IPI RzCmdStatus rz_cmd_help_search_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) { + RzCmdStatus status = RZ_CMD_STATUS_OK; + RzCmdDesc *begin = NULL; + + if (argc == 2) { + begin = rz_cmd_get_desc(core->rcmd, argv[1]); + if (!begin) { + status = RZ_CMD_STATUS_NONEXISTINGCMD; + goto exit_status; + } + } + + RzHelpSearch hs = { + .color = core->print->flags & RZ_PRINT_FLAGS_COLOR, + .pj = NULL, + .sb = NULL, + }; + + if (mode & RZ_OUTPUT_MODE_JSON) { + hs.pj = pj_new(); + if (!hs.pj) { + status = RZ_CMD_STATUS_ERROR; + goto exit_status; + } + pj_o(hs.pj); + } else { + hs.sb = rz_strbuf_new(NULL); + if (!hs.sb) { + status = RZ_CMD_STATUS_ERROR; + goto exit_status; + } + } + + rz_cmd_foreach_cmdname(core->rcmd, begin, help_search_cmd_desc_entry, &hs); + + if (mode & RZ_OUTPUT_MODE_JSON) { + pj_end(hs.pj); + rz_cons_printf("%s\n", pj_string(hs.pj)); + pj_free(hs.pj); + } else { + char *help = rz_strbuf_drain(hs.sb); + rz_cons_printf("%s", help); + free(help); + } +exit_status: + return status; +} + DEFINE_HANDLE_TS_FCN_AND_SYMBOL(help_command) { size_t node_str_len = strlen(node_string); if (node_str_len >= 2 && !strcmp(node_string + node_str_len - 2, "?*")) { - // TODO: get recursive help from RzCmdDesc - int detail = 0; - if (node_str_len > 3 && node_string[node_str_len - 3] == '?') { - detail++; - if (node_str_len > 4 && node_string[node_str_len - 4] == '?') { - detail++; - } - } - node_string[node_str_len - 2 - detail] = '\0'; - recursive_help(state->core, detail, node_string); - return RZ_CMD_STATUS_OK; - } else { - TSNode command = ts_node_child_by_field_name(node, "command", strlen("command")); - char *command_str = ts_node_sub_string(command, state->input); - TSNode args = ts_node_child_by_field_name(node, "args", strlen("args")); - RzCmdParsedArgs *pr_args = NULL; - RzCmdStatus res = RZ_CMD_STATUS_INVALID; - if (!ts_node_is_null(args)) { - RzCmdDesc *cd = rz_cmd_get_desc(state->core->rcmd, command_str); - bool do_unwrap = cd && cd->type != RZ_CMD_DESC_TYPE_OLDINPUT; - pr_args = ts_node_handle_arg_prargs(state, node, args, 1, do_unwrap); - if (!pr_args) { - goto err_else; - } - rz_cmd_parsed_args_setcmd(pr_args, command_str); - } else { - pr_args = rz_cmd_parsed_args_newcmd(command_str); - if (!pr_args) { - goto err_else; - } - } - - // let's try first with the new auto-generated help, if - // something fails fallback to old behaviour - char *help_msg = rz_cmd_get_help(state->core->rcmd, pr_args, state->core->print->flags & RZ_PRINT_FLAGS_COLOR); - if (help_msg) { - rz_cons_printf("%s", help_msg); - free(help_msg); - res = RZ_CMD_STATUS_OK; - } - err_else: - rz_cmd_parsed_args_free(pr_args); - free(command_str); - return res; + node_string[node_str_len - 2] = 0; + const char *argv[2] = { NULL, node_string }; + return rz_cmd_help_search_handler(state->core, 2, argv, RZ_OUTPUT_MODE_STANDARD); + } else if (node_str_len >= 3 && !strcmp(node_string + node_str_len - 3, "?*j")) { + node_string[node_str_len - 3] = 0; + const char *argv[2] = { NULL, node_string }; + return rz_cmd_help_search_handler(state->core, 2, argv, RZ_OUTPUT_MODE_JSON); } - return RZ_CMD_STATUS_OK; + + TSNode command = ts_node_child_by_field_name(node, "command", strlen("command")); + char *command_str = ts_node_sub_string(command, state->input); + TSNode args = ts_node_child_by_field_name(node, "args", strlen("args")); + RzCmdParsedArgs *pr_args = NULL; + RzCmdStatus res = RZ_CMD_STATUS_INVALID; + if (!ts_node_is_null(args)) { + RzCmdDesc *cd = rz_cmd_get_desc(state->core->rcmd, command_str); + bool do_unwrap = cd && cd->type != RZ_CMD_DESC_TYPE_OLDINPUT; + pr_args = ts_node_handle_arg_prargs(state, node, args, 1, do_unwrap); + if (!pr_args) { + goto err_else; + } + rz_cmd_parsed_args_setcmd(pr_args, command_str); + } else { + pr_args = rz_cmd_parsed_args_newcmd(command_str); + if (!pr_args) { + goto err_else; + } + } + + // let's try first with the new auto-generated help, if + // something fails fallback to old behaviour + bool use_color = state->core->print->flags & RZ_PRINT_FLAGS_COLOR; + char *help_msg = rz_cmd_get_help(state->core->rcmd, pr_args, use_color); + if (help_msg) { + rz_cons_printf("%s", help_msg); + free(help_msg); + res = RZ_CMD_STATUS_OK; + } +err_else: + rz_cmd_parsed_args_free(pr_args); + free(command_str); + return res; } DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_seek_command) { diff --git a/librz/core/cmd_api.c b/librz/core/cmd_api.c index b309318b2d..f7b7d4eca2 100644 --- a/librz/core/cmd_api.c +++ b/librz/core/cmd_api.c @@ -642,7 +642,7 @@ static size_t strbuf_append_calc(RzStrBuf *sb, const char *s) { return strlen(s); } -static void fill_modes_children_chars(RzStrBuf *sb, RzCmdDesc *cd) { +static void fill_modes_children_chars(RzStrBuf *sb, const RzCmdDesc *cd) { // RZ_CMD_DESC_TYPE_ARGV_MODES does not have actual children for // the output modes, so we consider it separately size_t i; @@ -653,7 +653,7 @@ static void fill_modes_children_chars(RzStrBuf *sb, RzCmdDesc *cd) { } } -static size_t fill_children_chars(RzStrBuf *sb, RzCmdDesc *cd) { +static size_t fill_children_chars(RzStrBuf *sb, const RzCmdDesc *cd) { if (cd->help->options) { return strbuf_append_calc(sb, cd->help->options); } @@ -663,7 +663,7 @@ static size_t fill_children_chars(RzStrBuf *sb, RzCmdDesc *cd) { void **it; bool has_other_commands = false; - RzCmdDesc *exec_cd = rz_cmd_desc_get_exec(cd); + const RzCmdDesc *exec_cd = rz_cmd_desc_get_exec((RzCmdDesc *)cd); if (exec_cd) { switch (exec_cd->type) { case RZ_CMD_DESC_TYPE_ARGV_MODES: @@ -706,7 +706,7 @@ static size_t fill_children_chars(RzStrBuf *sb, RzCmdDesc *cd) { return res; } -static bool show_children_shortcut(RzCmdDesc *cd) { +static bool show_children_shortcut(const RzCmdDesc *cd) { return cd->n_children || cd->help->options || cd->type == RZ_CMD_DESC_TYPE_OLDINPUT || cd->type == RZ_CMD_DESC_TYPE_ARGV_MODES; } @@ -739,7 +739,7 @@ static void fill_wrapped_comment(RzCmd *cmd, RzStrBuf *sb, const char *comment, } } -static size_t fill_args(RzStrBuf *sb, RzCmdDesc *cd) { +static size_t fill_args(RzStrBuf *sb, const RzCmdDesc *cd) { const RzCmdDescArg *arg; size_t n_optionals = 0; size_t len = 0; @@ -836,11 +836,11 @@ static void fill_usage_strbuf(RzCmd *cmd, RzStrBuf *sb, RzCmdDesc *cd, bool use_ rz_strbuf_append(sb, "\n"); } -static size_t calc_padding_len(RzCmdDesc *cd, const char *name) { +static size_t calc_padding_len(const RzCmdDesc *cd, const char *name, bool show_children) { size_t name_len = strlen(name); size_t args_len = 0; size_t children_length = 0; - if (show_children_shortcut(cd)) { + if (show_children && show_children_shortcut(cd)) { RzStrBuf sb; rz_strbuf_init(&sb); fill_children_chars(&sb, cd); @@ -859,14 +859,14 @@ static size_t calc_padding_len(RzCmdDesc *cd, const char *name) { return name_len + args_len + children_length; } -static void update_minmax_len(RzCmdDesc *cd, size_t *max_len, size_t *min_len) { - size_t val = calc_padding_len(cd, cd->name); +static void update_minmax_len(RzCmdDesc *cd, size_t *max_len, size_t *min_len, bool show_children) { + size_t val = calc_padding_len(cd, cd->name, show_children); *max_len = val > *max_len ? val : *max_len; *min_len = val < *min_len ? val : *min_len; } -static void do_print_child_help(RzCmd *cmd, RzStrBuf *sb, RzCmdDesc *cd, const char *name, const char *summary, bool show_children, size_t max_len, bool use_color) { - size_t str_len = calc_padding_len(cd, name); +static void do_print_child_help(RzCmd *cmd, RzStrBuf *sb, const RzCmdDesc *cd, const char *name, const char *summary, bool show_children, size_t max_len, bool use_color) { + size_t str_len = calc_padding_len(cd, name, show_children); int padding = str_len < max_len ? max_len - str_len : 0; const char *pal_args_color = "", *pal_opt_color = "", @@ -918,7 +918,7 @@ static char *group_get_help(RzCmd *cmd, RzCmdDesc *cd, bool use_color) { rz_cmd_desc_children_foreach(cd, it_cd) { RzCmdDesc *child = *(RzCmdDesc **)it_cd; - update_minmax_len(child, &max_len, &min_len); + update_minmax_len(child, &max_len, &min_len, true); } if (max_len - min_len > MAX_RIGHT_ALIGHNMENT) { max_len = min_len + MAX_RIGHT_ALIGHNMENT; @@ -936,7 +936,7 @@ static char *argv_modes_get_help(RzCmd *cmd, RzCmdDesc *cd, bool use_color) { fill_usage_strbuf(cmd, sb, cd, use_color); size_t max_len = 0, min_len = SIZE_MAX; - update_minmax_len(cd, &max_len, &min_len); + update_minmax_len(cd, &max_len, &min_len, true); max_len++; // consider the suffix letter if (max_len - min_len > MAX_RIGHT_ALIGHNMENT) { max_len = min_len + MAX_RIGHT_ALIGHNMENT; @@ -1022,17 +1022,18 @@ static char *argv_get_help(RzCmd *cmd, RzCmdDesc *cd, size_t detail, bool use_co switch (detail) { case 1: - return rz_strbuf_drain(sb); + break; case 2: if (cd->help->description) { rz_strbuf_appendf(sb, "\n%s\n", cd->help->description); } fill_details(cmd, cd, sb, use_color); - return rz_strbuf_drain(sb); + break; default: rz_strbuf_free(sb); return NULL; } + return rz_strbuf_drain(sb); } static char *fake_get_help(RzCmd *cmd, RzCmdDesc *cd, bool use_color) { @@ -1089,6 +1090,142 @@ static char *get_help(RzCmd *cmd, RzCmdDesc *cd, RzCmdParsedArgs *args, bool use return NULL; } +static void fill_args_json(const RzCmdDesc *cd, PJ *j) { + const RzCmdDescArg *arg; + bool has_array = false; + pj_ka(j, "args"); + const char *argtype = NULL; + for (arg = cd->help->args; arg && arg->name; arg++) { + if (has_array) { + rz_warn_if_reached(); + break; + } + pj_o(j); +#define CASE_TYPE(x, y) \ + case (x): \ + argtype = (y); \ + break + switch (arg->type) { + CASE_TYPE(RZ_CMD_ARG_TYPE_FAKE, "fake"); + CASE_TYPE(RZ_CMD_ARG_TYPE_NUM, "number"); + CASE_TYPE(RZ_CMD_ARG_TYPE_RZNUM, "expression"); + CASE_TYPE(RZ_CMD_ARG_TYPE_STRING, "string"); + CASE_TYPE(RZ_CMD_ARG_TYPE_ENV, "environment_variable"); + CASE_TYPE(RZ_CMD_ARG_TYPE_ZIGN, "zignature"); + CASE_TYPE(RZ_CMD_ARG_TYPE_ZIGN_SPACE, "zignature_space"); + CASE_TYPE(RZ_CMD_ARG_TYPE_CHOICES, "choice"); + CASE_TYPE(RZ_CMD_ARG_TYPE_FCN, "function"); + CASE_TYPE(RZ_CMD_ARG_TYPE_FILE, "filename"); + CASE_TYPE(RZ_CMD_ARG_TYPE_OPTION, "option"); + CASE_TYPE(RZ_CMD_ARG_TYPE_CMD, "command"); + CASE_TYPE(RZ_CMD_ARG_TYPE_MACRO, "macro"); + CASE_TYPE(RZ_CMD_ARG_TYPE_EVAL_KEY, "evaluable"); + CASE_TYPE(RZ_CMD_ARG_TYPE_EVAL_FULL, "evaluable_full"); +#undef CASE_TYPE + default: + argtype = "unknown"; + break; + } + pj_ks(j, "type", argtype); + pj_ks(j, "name", arg->name); + if (arg->type == RZ_CMD_ARG_TYPE_FAKE) { + pj_end(j); + continue; + } + if (arg->no_space) { + pj_kb(j, "nospace", true); + } + if (!arg->optional) { + pj_kb(j, "required", true); + } + if (arg->flags & RZ_CMD_ARG_FLAG_LAST) { + pj_kb(j, "is_last", true); + } + if (arg->flags & RZ_CMD_ARG_FLAG_ARRAY) { + pj_kb(j, "is_array", true); + } + if (arg->flags & RZ_CMD_ARG_FLAG_OPTION) { + pj_kb(j, "is_option", true); + } + if (arg->default_value) { + pj_ks(j, "default", arg->default_value); + } + if (arg->type == RZ_CMD_ARG_TYPE_CHOICES) { + pj_ka(j, "choices"); + const char **choice = arg->choices; + for (; *choice; choice++) { + pj_s(j, *choice); + } + pj_end(j); + } + pj_end(j); + } + pj_end(j); +} + +/** + * \brief Generates a JSON output of the given help message description + * + * \param cmd reference to RzCmd + * \param cd reference to RzCmdDesc + * \param j reference to PJ + * + * \return returns false if an invalid argument was given, otherwise true. + */ +RZ_API bool rz_cmd_get_help_json(RzCmd *cmd, const RzCmdDesc *cd, PJ *j) { + rz_return_val_if_fail(cmd && cd && j, false); + pj_ko(j, cd->name); + pj_ks(j, "cmd", cd->name); + const char *type; + switch (cd->type) { +#define CASE_CDTYPE(x, y) \ + case (x): \ + type = (y); \ + break + CASE_CDTYPE(RZ_CMD_DESC_TYPE_OLDINPUT, "oldinput"); + CASE_CDTYPE(RZ_CMD_DESC_TYPE_ARGV, "argv"); + CASE_CDTYPE(RZ_CMD_DESC_TYPE_GROUP, "group"); + CASE_CDTYPE(RZ_CMD_DESC_TYPE_INNER, "inner"); + CASE_CDTYPE(RZ_CMD_DESC_TYPE_FAKE, "fake"); + CASE_CDTYPE(RZ_CMD_DESC_TYPE_ARGV_MODES, "argv_modes"); +#undef CASE_CDTYPE + default: + type = "unknown"; + break; + } + pj_ks(j, "type", type); + if (cd->help->args_str) { + pj_ks(j, "args_str", cd->help->args_str); + } else { + RzStrBuf *sb = rz_strbuf_new(NULL); + fill_args(sb, cd); + char *args = rz_strbuf_drain(sb); + pj_ks(j, "args_str", args); + free(args); + } + fill_args_json(cd, j); + pj_ks(j, "description", cd->help->description ? cd->help->description : ""); + pj_ks(j, "summary", cd->help->summary ? cd->help->summary : ""); + pj_end(j); + return true; +} + +/** + * \brief Generates a text output of the given help message description (summary format) + * + * \param cmd reference to RzCmd + * \param cd reference to RzCmdDesc + * \param use_color output strings with color codes. + * \param sb reference to RzStrBuf + * + * \return returns false if an invalid argument was given, otherwise true. + */ +RZ_API bool rz_cmd_get_help_strbuf(RzCmd *cmd, const RzCmdDesc *cd, bool use_color, RzStrBuf *sb) { + rz_return_val_if_fail(cmd && cd && sb, false); + do_print_child_help(cmd, sb, cd, cd->name, cd->help->summary, false, MAX_RIGHT_ALIGHNMENT, use_color); + return true; +} + RZ_API char *rz_cmd_get_help(RzCmd *cmd, RzCmdParsedArgs *args, bool use_color) { char *cmdid = strdup(rz_cmd_parsed_args_cmd(args)); char *cmdid_p = cmdid + strlen(cmdid) - 1; @@ -1822,7 +1959,7 @@ RZ_API RzCmdDesc *rz_cmd_desc_parent(RzCmdDesc *cd) { return cd->parent; } -RZ_API bool rz_cmd_desc_has_handler(RzCmdDesc *cd) { +RZ_API bool rz_cmd_desc_has_handler(const RzCmdDesc *cd) { rz_return_val_if_fail(cd, false); switch (cd->type) { case RZ_CMD_DESC_TYPE_ARGV: @@ -1879,6 +2016,17 @@ RZ_API const RzCmdDescArg *rz_cmd_desc_get_arg(RzCmd *cmd, const RzCmdDesc *cd, return NULL; } +static RzCmdDescHelp *mode_cmd_desc_help(RzCmdDescHelp *dst, const RzCmdDescHelp *src, const char *suffix) { + dst->summary = rz_str_newf("%s%s", src->summary, suffix); + dst->description = src->description; + dst->args_str = src->args_str; + dst->usage = src->usage; + dst->options = src->options; + dst->details = src->details; + dst->args = src->args; + return dst; +} + static void cmd_foreach_cmdname(RzCmd *cmd, RzCmdDesc *cd, RzCmdForeachNameCb cb, void *user) { if (!cd) { return; @@ -1890,15 +2038,25 @@ static void cmd_foreach_cmdname(RzCmd *cmd, RzCmdDesc *cd, RzCmdForeachNameCb cb switch (cd->type) { case RZ_CMD_DESC_TYPE_ARGV: if (rz_cmd_desc_has_handler(cd)) { - cb(cmd, cd->name, user); + cb(cmd, cd, user); } break; case RZ_CMD_DESC_TYPE_ARGV_MODES: for (i = 0; i < RZ_ARRAY_SIZE(argv_modes); i++) { if (cd->d.argv_modes_data.modes & argv_modes[i].mode) { - char *name = rz_str_newf("%s%s", cd->name, argv_modes[i].suffix); - cb(cmd, name, user); - free(name); + RzCmdDescHelp mode_help; + const RzCmdDescHelp *copy = cd->help; + cd->help = mode_cmd_desc_help(&mode_help, copy, argv_modes[i].summary_suffix); + + char *name = cd->name; + cd->name = rz_str_newf("%s%s", name, argv_modes[i].suffix); + + cb(cmd, cd, user); + + free(cd->name); + free((char *)mode_help.summary); + cd->name = name; + cd->help = copy; } } break; @@ -1906,7 +2064,7 @@ static void cmd_foreach_cmdname(RzCmd *cmd, RzCmdDesc *cd, RzCmdForeachNameCb cb break; case RZ_CMD_DESC_TYPE_OLDINPUT: if (rz_cmd_desc_has_handler(cd)) { - cb(cmd, cd->name, user); + cb(cmd, cd, user); } // fallthrough case RZ_CMD_DESC_TYPE_INNER: @@ -1926,11 +2084,12 @@ static void cmd_foreach_cmdname(RzCmd *cmd, RzCmdDesc *cd, RzCmdForeachNameCb cb * commands (e.g. ?, h?, etc.) are ignored. * * \param cmd Reference to RzCmd + * \param begin Reference to RzCmdDesc from where to begin the for loop; if NULL the root will be used. * \param cb Callback function that is called for each command name. * \param user Additional user data that is passed to the callback \p cb. */ -RZ_API void rz_cmd_foreach_cmdname(RzCmd *cmd, RzCmdForeachNameCb cb, void *user) { - RzCmdDesc *cd = rz_cmd_get_root(cmd); +RZ_API void rz_cmd_foreach_cmdname(RzCmd *cmd, RzCmdDesc *begin, RzCmdForeachNameCb cb, void *user) { + RzCmdDesc *cd = begin ? begin : rz_cmd_get_root(cmd); cmd_foreach_cmdname(cmd, cd, cb, user); } diff --git a/librz/core/cmd_descs.c b/librz/core/cmd_descs.c index 06ae0d4b2a..62a2877c2e 100644 --- a/librz/core/cmd_descs.c +++ b/librz/core/cmd_descs.c @@ -45,6 +45,7 @@ static const RzCmdDescArg remote_open_args[2]; static const RzCmdDescArg remote_rap_args[3]; static const RzCmdDescArg remote_tcp_args[3]; static const RzCmdDescArg remote_rap_bg_args[2]; +static const RzCmdDescArg cmd_help_search_args[2]; static const RzCmdDescArg push_escaped_args[2]; static const RzCmdDescArg eval_getset_args[2]; static const RzCmdDescArg eval_list_args[2]; @@ -598,6 +599,20 @@ static const RzCmdDescHelp remote_rap_bg_help = { .args = remote_rap_bg_args, }; +static const RzCmdDescArg cmd_help_search_args[] = { + { + .name = "search_cmd", + .type = RZ_CMD_ARG_TYPE_STRING, + .optional = true, + + }, + { 0 }, +}; +static const RzCmdDescHelp cmd_help_search_help = { + .summary = "Search help", + .args = cmd_help_search_args, +}; + static const RzCmdDescHelp cmd_help_help = { .summary = "Help or evaluate math expression", .options = "[??]", @@ -2362,6 +2377,9 @@ RZ_IPI void newshell_cmddescs_init(RzCore *core) { RzCmdDesc *remote_rap_bg_cd = rz_cmd_desc_argv_new(core->rcmd, equal__cd, "=&:", rz_remote_rap_bg_handler, &remote_rap_bg_help); rz_warn_if_fail(remote_rap_bg_cd); + RzCmdDesc *cmd_help_search_cd = rz_cmd_desc_argv_modes_new(core->rcmd, root_cd, "?*", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON, rz_cmd_help_search_handler, &cmd_help_search_help); + rz_warn_if_fail(cmd_help_search_cd); + RzCmdDesc *cmd_help_cd = rz_cmd_desc_oldinput_new(core->rcmd, root_cd, "?", rz_cmd_help, &cmd_help_help); rz_warn_if_fail(cmd_help_cd); diff --git a/librz/core/cmd_descs.h b/librz/core/cmd_descs.h index 7a83220a90..c1d0341b57 100644 --- a/librz/core/cmd_descs.h +++ b/librz/core/cmd_descs.h @@ -47,6 +47,7 @@ RZ_IPI int rz_equal_h_handler_old(void *data, const char *input); RZ_IPI int rz_equal_H_handler_old(void *data, const char *input); RZ_IPI RzCmdStatus rz_remote_tcp_handler(RzCore *core, int argc, const char **argv); RZ_IPI RzCmdStatus rz_remote_rap_bg_handler(RzCore *core, int argc, const char **argv); +RZ_IPI RzCmdStatus rz_cmd_help_search_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode); RZ_IPI int rz_cmd_help(void *data, const char *input); RZ_IPI RzCmdStatus rz_push_escaped_handler(RzCore *core, int argc, const char **argv); RZ_IPI int rz_cmd_ox(void *data, const char *input); diff --git a/librz/core/cmd_descs.yaml b/librz/core/cmd_descs.yaml index 9c3650897d..c94ec77af5 100644 --- a/librz/core/cmd_descs.yaml +++ b/librz/core/cmd_descs.yaml @@ -347,6 +347,16 @@ args: - name: port type: RZ_CMD_ARG_TYPE_NUM +- name: "?*" + cname: cmd_help_search + summary: Search help + args: + - name: search_cmd + type: RZ_CMD_ARG_TYPE_STRING + optional: true + modes: + - RZ_OUTPUT_MODE_STANDARD + - RZ_OUTPUT_MODE_JSON - name: "?" cname: cmd_help options: "[??]" diff --git a/librz/include/rz_cmd.h b/librz/include/rz_cmd.h index c601e35c6a..af9ecf291a 100644 --- a/librz/include/rz_cmd.h +++ b/librz/include/rz_cmd.h @@ -431,7 +431,7 @@ typedef struct rz_core_plugin_t { RzCmdCb fini; } RzCorePlugin; -typedef bool (*RzCmdForeachNameCb)(RzCmd *cmd, const char *name, void *user); +typedef bool (*RzCmdForeachNameCb)(RzCmd *cmd, const RzCmdDesc *desc, void *user); #ifdef RZ_API RZ_API int rz_core_plugin_init(RzCmd *cmd); @@ -449,6 +449,8 @@ RZ_API RzCmdStatus rz_cmd_call_parsed_args(RzCmd *cmd, RzCmdParsedArgs *args); RZ_API RzCmdDesc *rz_cmd_get_root(RzCmd *cmd); RZ_API RzCmdDesc *rz_cmd_get_desc(RzCmd *cmd, const char *cmd_identifier); RZ_API char *rz_cmd_get_help(RzCmd *cmd, RzCmdParsedArgs *args, bool use_color); +RZ_API bool rz_cmd_get_help_json(RzCmd *cmd, const RzCmdDesc *cd, PJ *j); +RZ_API bool rz_cmd_get_help_strbuf(RzCmd *cmd, const RzCmdDesc *cd, bool use_color, RzStrBuf *sb); static inline RzCmdStatus rz_cmd_int2status(int v) { if (v == -2) { @@ -485,9 +487,9 @@ RZ_API RzCmdDesc *rz_cmd_desc_oldinput_new(RzCmd *cmd, RzCmdDesc *parent, const RZ_API RzCmdDesc *rz_cmd_desc_fake_new(RzCmd *cmd, RzCmdDesc *parent, const char *name, const RzCmdDescHelp *help); RZ_API RzCmdDesc *rz_cmd_desc_parent(RzCmdDesc *cd); RZ_API RzCmdDesc *rz_cmd_desc_get_exec(RzCmdDesc *cd); -RZ_API bool rz_cmd_desc_has_handler(RzCmdDesc *cd); +RZ_API bool rz_cmd_desc_has_handler(const RzCmdDesc *cd); RZ_API bool rz_cmd_desc_remove(RzCmd *cmd, RzCmdDesc *cd); -RZ_API void rz_cmd_foreach_cmdname(RzCmd *cmd, RzCmdForeachNameCb cb, void *user); +RZ_API void rz_cmd_foreach_cmdname(RzCmd *cmd, RzCmdDesc *begin, RzCmdForeachNameCb cb, void *user); RZ_API const RzCmdDescArg *rz_cmd_desc_get_arg(RzCmd *cmd, const RzCmdDesc *cd, size_t i); #define rz_cmd_desc_children_foreach(root, it_cd) rz_pvector_foreach (&root->children, it_cd) diff --git a/shlr/rizin-shell-parser/src/scanner.c b/shlr/rizin-shell-parser/src/scanner.c index b28ca1a901..f443232b20 100644 --- a/shlr/rizin-shell-parser/src/scanner.c +++ b/shlr/rizin-shell-parser/src/scanner.c @@ -104,8 +104,12 @@ static bool is_concat_eq_sep(const int32_t ch) { return is_concat(ch) && ch != '='; } -static bool is_recursive_help(int id_len, const int32_t before_last_ch, const int32_t last_ch) { - return id_len >= 2 && before_last_ch == '?' && last_ch == '*'; +static bool is_recursive_help(const int32_t before_last_ch, const int32_t last_ch) { + return before_last_ch == '?' && last_ch == '*'; +} + +static bool is_recursive_help_json(const int32_t trd_last_ch, const int32_t snd_last_ch, const int32_t last_ch) { + return trd_last_ch == '?' && snd_last_ch == '*' && last_ch == 'j'; } static bool scan_number(TSLexer *lexer, const bool *valid_symbols) { @@ -178,7 +182,9 @@ bool tree_sitter_rzcmd_external_scanner_scan(void *payload, TSLexer *lexer, cons return false; } // ?? is not considered an help command, just a regular one - if ((res[i_res - 1] == '?' && strcmp (res, "??")) || (i_res >= 2 && is_recursive_help (i_res, res[i_res - 2], res[i_res - 1]))) { + if ((res[i_res - 1] == '?' && strcmp (res, "??") != 0) || + (i_res > 2 && is_recursive_help (res[i_res - 2], res[i_res - 1])) || + (i_res > 3 && is_recursive_help_json (res[i_res - 3], res[i_res - 2], res[i_res - 1]))) { if (i_res == 1) { return false; } diff --git a/test/db/cmd/cmd_help b/test/db/cmd/cmd_help index a6b15d5bd0..dc0bb54631 100644 --- a/test/db/cmd/cmd_help +++ b/test/db/cmd/cmd_help @@ -242,4 +242,54 @@ EOF EXPECT=<] # Show zignatures matching information +| zij [] # Show zignatures matching information (JSON mode) +| zi* [] # Show zignatures matching information (rizin mode) +| ziq [] # Show zignatures matching information (quiet mode) +| zii # Show zignatures matching information in range +EOF +RUN + +NAME=recursive help (json) +FILE=- +CMDS=<]","args":[{"type":"string","name":"commens"}],"description":"","summary":"Show zignatures matching information"},"zij":{"cmd":"zij","type":"argv_modes","args_str":" []","args":[{"type":"string","name":"commens"}],"description":"","summary":"Show zignatures matching information (JSON mode)"},"zi*":{"cmd":"zi*","type":"argv_modes","args_str":" []","args":[{"type":"string","name":"commens"}],"description":"","summary":"Show zignatures matching information (rizin mode)"},"ziq":{"cmd":"ziq","type":"argv_modes","args_str":" []","args":[{"type":"string","name":"commens"}],"description":"","summary":"Show zignatures matching information (quiet mode)"},"zii":{"cmd":"zii","type":"argv","args_str":" ","args":[{"type":"number","name":"from","required":true},{"type":"number","name":"to","required":true}],"description":"","summary":"Show zignatures matching information in range"}} +EOF +RUN + + +NAME=old recursive help +FILE=- +CMDS=<] # Show zignatures matching information +| zij [] # Show zignatures matching information (JSON mode) +| zi* [] # Show zignatures matching information (rizin mode) +| ziq [] # Show zignatures matching information (quiet mode) +| zii # Show zignatures matching information in range +EOF +RUN + +NAME=old recursive help (json) +FILE=- +CMDS=<]","args":[{"type":"string","name":"commens"}],"description":"","summary":"Show zignatures matching information"},"zij":{"cmd":"zij","type":"argv_modes","args_str":" []","args":[{"type":"string","name":"commens"}],"description":"","summary":"Show zignatures matching information (JSON mode)"},"zi*":{"cmd":"zi*","type":"argv_modes","args_str":" []","args":[{"type":"string","name":"commens"}],"description":"","summary":"Show zignatures matching information (rizin mode)"},"ziq":{"cmd":"ziq","type":"argv_modes","args_str":" []","args":[{"type":"string","name":"commens"}],"description":"","summary":"Show zignatures matching information (quiet mode)"},"zii":{"cmd":"zii","type":"argv","args_str":" ","args":[{"type":"number","name":"from","required":true},{"type":"number","name":"to","required":true}],"description":"","summary":"Show zignatures matching information in range"}} +EOF +RUN + diff --git a/test/unit/test_cmd.c b/test/unit/test_cmd.c index ee27d9a858..d84ab035d2 100644 --- a/test/unit/test_cmd.c +++ b/test/unit/test_cmd.c @@ -543,10 +543,10 @@ bool test_cmd_argv_modes(void) { pa = rz_cmd_parsed_args_newcmd("z?"); h = rz_cmd_get_help(cmd, pa, false); exp_h = "Usage: z[jqJ] # z summary\n" - "| z # z summary\n" - "| zj # z summary (JSON mode)\n" - "| zq # z summary (quiet mode)\n" - "| zJ # z summary (verbose JSON mode)\n"; + "| z # z summary\n" + "| zj # z summary (JSON mode)\n" + "| zq # z summary (quiet mode)\n" + "| zJ # z summary (verbose JSON mode)\n"; mu_assert_streq(h, exp_h, "zj, zJ and zq are considered in the sub help"); free(h); rz_cmd_parsed_args_free(pa); @@ -599,8 +599,8 @@ bool test_cmd_group_argv_modes(void) { mu_end; } -static bool foreach_cmdname_cb(RzCmd *cmd, const char *name, void *user) { - rz_list_append((RzList *)user, strdup(name)); +static bool foreach_cmdname_cb(RzCmd *cmd, const RzCmdDesc *desc, void *user) { + rz_list_append((RzList *)user, strdup(desc->name)); return true; } @@ -619,7 +619,7 @@ bool test_foreach_cmdname(void) { rz_cmd_desc_argv_new(cmd, v_inner_cd, "v2", zd_handler, &fake_help); RzList *res = rz_list_newf(free); - rz_cmd_foreach_cmdname(cmd, foreach_cmdname_cb, res); + rz_cmd_foreach_cmdname(cmd, NULL, foreach_cmdname_cb, res); const char *exp_regular[] = { "z", "zj", "zq", "zd", "zsq", "pi", "v", "v1", "v2" }; mu_assert_eq(rz_list_length(res), RZ_ARRAY_SIZE(exp_regular), "count regular commands that can be executed"); @@ -645,6 +645,47 @@ bool test_foreach_cmdname(void) { mu_end; } +bool test_foreach_cmdname_begin(void) { + RzCmd *cmd = rz_cmd_new(false); + RzCmdDesc *root = rz_cmd_get_root(cmd); + RzCmdDesc *z_cd = rz_cmd_desc_group_modes_new(cmd, root, "z", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON | RZ_OUTPUT_MODE_QUIET, z_modes_handler, &fake_help, &fake_help); + rz_cmd_desc_argv_new(cmd, z_cd, "zd", zd_handler, &fake_help); + rz_cmd_desc_argv_modes_new(cmd, z_cd, "zs", RZ_OUTPUT_MODE_QUIET, z_modes_handler, &fake_help); + rz_cmd_desc_fake_new(cmd, root, "x", &fake_help); + RzCmdDesc *p_cd = rz_cmd_desc_group_new(cmd, root, "p", NULL, NULL, &fake_help); + rz_cmd_desc_argv_new(cmd, p_cd, "pi", zd_handler, &fake_help); + RzCmdDesc *v_cd = rz_cmd_desc_oldinput_new(cmd, root, "v", a_oldinput_cb, &fake_help); + RzCmdDesc *v_inner_cd = rz_cmd_desc_inner_new(cmd, v_cd, "v", &fake_help); + rz_cmd_desc_argv_new(cmd, v_inner_cd, "v1", zd_handler, &fake_help); + rz_cmd_desc_argv_new(cmd, v_inner_cd, "v2", zd_handler, &fake_help); + + RzList *res = rz_list_newf(free); + rz_cmd_foreach_cmdname(cmd, v_cd, foreach_cmdname_cb, res); + + const char *exp_regular[] = { "v", "v1", "v2" }; + mu_assert_eq(rz_list_length(res), RZ_ARRAY_SIZE(exp_regular), "count regular commands that can be executed"); + + RzList *exp_regular_l = rz_list_new_from_array((const void **)exp_regular, RZ_ARRAY_SIZE(exp_regular)); + rz_list_sort(exp_regular_l, (RzListComparator)strcmp); + rz_list_sort(res, (RzListComparator)strcmp); + + RzListIter *it; + char *s; + size_t i = 0; + rz_list_foreach (exp_regular_l, it, s) { + RzStrBuf sb; + rz_strbuf_initf(&sb, "check command `%s`", s); + mu_assert_streq(rz_list_get_n(res, i++), s, rz_strbuf_get(&sb)); + rz_strbuf_fini(&sb); + } + + rz_list_free(res); + rz_list_free(exp_regular_l); + + rz_cmd_free(cmd); + mu_end; +} + bool test_arg_escaping(void) { mu_assert_streq_free(rz_cmd_escape_arg("hello", RZ_CMD_ESCAPE_ONE_ARG), "hello", "regular string remains the same"); mu_assert_streq_free(rz_cmd_escape_arg("hello world", RZ_CMD_ESCAPE_ONE_ARG), "hello\\ world", "spaces are escaped"); @@ -829,6 +870,7 @@ int all_tests() { mu_run_test(test_cmd_argv_modes); mu_run_test(test_cmd_group_argv_modes); mu_run_test(test_foreach_cmdname); + mu_run_test(test_foreach_cmdname_begin); mu_run_test(test_arg_escaping); mu_run_test(test_double_quoted_arg_escaping); mu_run_test(test_single_quoted_arg_escaping);