From 890ddb640e053a1fe68bb5806553c76123c536d6 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Fri, 26 Feb 2021 16:46:16 +0100 Subject: [PATCH] core: allow to sort subcommands of a command group --- librz/core/cmd_api.c | 52 +++++++++++++++++++++- librz/core/cmd_descs/cmd_descs.c | 2 + librz/core/cmd_descs/cmd_descs_generate.py | 2 + librz/include/rz_cmd.h | 16 +++++++ librz/util/str.c | 9 +++- test/unit/test_autocmplt.c | 16 +++---- test/unit/test_cmd.c | 38 ++++++++++++++++ 7 files changed, 123 insertions(+), 12 deletions(-) diff --git a/librz/core/cmd_api.c b/librz/core/cmd_api.c index 56f00645848..84bbe0acd5a 100644 --- a/librz/core/cmd_api.c +++ b/librz/core/cmd_api.c @@ -49,6 +49,7 @@ static const RzCmdDescHelp not_defined_help = { static const RzCmdDescHelp root_help = { .usage = "[.][times][cmd][~grep][@[@iter]addr!size][|>pipe] ; ...", .description = "", + .sort_subcommands = true, }; static const struct argv_modes_t { @@ -81,7 +82,13 @@ static int value = 0; #define NCMDS (sizeof(cmd->cmds) / sizeof(*cmd->cmds)) RZ_LIB_VERSION(rz_cmd); -static bool cmd_desc_set_parent(RzCmdDesc *cd, RzCmdDesc *parent) { +static int cd_sort(const void *a, const void *b) { + RzCmdDesc *ca = (RzCmdDesc *)a; + RzCmdDesc *cb = (RzCmdDesc *)b; + return rz_str_casecmp(ca->name, cb->name); +} + +static bool cmd_desc_set_parent(RzCmd *cmd, RzCmdDesc *cd, RzCmdDesc *parent) { rz_return_val_if_fail(cd && !cd->parent, false); if (parent) { switch (parent->type) { @@ -99,6 +106,9 @@ static bool cmd_desc_set_parent(RzCmdDesc *cd, RzCmdDesc *parent) { if (parent) { cd->parent = parent; rz_pvector_push(&parent->children, cd); + if (!cmd->batch && parent->help->sort_subcommands) { + rz_pvector_sort(&parent->children, cd_sort); + } parent->n_children++; } return true; @@ -148,7 +158,7 @@ static RzCmdDesc *create_cmd_desc(RzCmd *cmd, RzCmdDesc *parent, RzCmdDescType t if (ht_insert && !ht_pp_insert(cmd->ht_cmds, name, res)) { goto err; } - cmd_desc_set_parent(res, parent); + cmd_desc_set_parent(cmd, res, parent); return res; err: cmd_desc_free(res); @@ -200,10 +210,48 @@ RZ_API RzCmd *rz_cmd_free(RzCmd *cmd) { return NULL; } +/** + * \brief Get the root command descriptor + */ RZ_API RzCmdDesc *rz_cmd_get_root(RzCmd *cmd) { return cmd->root_cmd_desc; } +/** + * \brief Mark the start of the batched changes to RzCmd + * + * Commands added after this call won't be sorted until \p rz_cmd_batch_end is + * called. + */ +RZ_API void rz_cmd_batch_start(RzCmd *cmd) { + cmd->batch = true; +} + +static void sort_groups(RzCmdDesc *group) { + void **it_cd; + + if (group->help->sort_subcommands) { + rz_pvector_sort(&group->children, cd_sort); + } + rz_cmd_desc_children_foreach(group, it_cd) { + RzCmdDesc *cd = *(RzCmdDesc **)it_cd; + if (cd->n_children) { + sort_groups(cd); + } + } +} + +/** + * \brief Mark the end of the batched changes to RzCmd + * + * All groups are sorted, if necessary. Call \p rz_cmd_batch_start before using + * this function. + */ +RZ_API void rz_cmd_batch_end(RzCmd *cmd) { + cmd->batch = false; + sort_groups(rz_cmd_get_root(cmd)); +} + static RzOutputMode suffix2mode(const char *suffix) { size_t i; for (i = 0; i < RZ_ARRAY_SIZE(argv_modes); i++) { diff --git a/librz/core/cmd_descs/cmd_descs.c b/librz/core/cmd_descs/cmd_descs.c index 19248260344..689ad319e08 100644 --- a/librz/core/cmd_descs/cmd_descs.c +++ b/librz/core/cmd_descs/cmd_descs.c @@ -3269,6 +3269,7 @@ static const RzCmdDescHelp grep_help = { RZ_IPI void newshell_cmddescs_init(RzCore *core) { RzCmdDesc *root_cd = rz_cmd_get_root(core->rcmd); + rz_cmd_batch_start(core->rcmd); RzCmdDesc *cmd_system_cd = rz_cmd_desc_oldinput_new(core->rcmd, root_cd, "!", rz_cmd_system, &cmd_system_help); rz_warn_if_fail(cmd_system_cd); @@ -3944,4 +3945,5 @@ RZ_IPI void newshell_cmddescs_init(RzCore *core) { RzCmdDesc *grep_cd = rz_cmd_desc_fake_new(core->rcmd, root_cd, "~", &grep_help); rz_warn_if_fail(grep_cd); + rz_cmd_batch_end(core->rcmd); } diff --git a/librz/core/cmd_descs/cmd_descs_generate.py b/librz/core/cmd_descs/cmd_descs_generate.py index 02fe7373950..2c5dd282baa 100755 --- a/librz/core/cmd_descs/cmd_descs_generate.py +++ b/librz/core/cmd_descs/cmd_descs_generate.py @@ -19,7 +19,9 @@ {helps} RZ_IPI void newshell_cmddescs_init(RzCore *core) {{ \tRzCmdDesc *root_cd = rz_cmd_get_root(core->rcmd); +\trz_cmd_batch_start(core->rcmd); {init_code} +\trz_cmd_batch_end(core->rcmd); }} """ diff --git a/librz/include/rz_cmd.h b/librz/include/rz_cmd.h index a89ac9ed9cf..d72725244dc 100644 --- a/librz/include/rz_cmd.h +++ b/librz/include/rz_cmd.h @@ -270,6 +270,13 @@ typedef struct rz_cmd_desc_help_t { * Optional. */ const char *options; + /** + * When true, the subcommands are automatically sorted alphabetically. By + * default subcommands are shown in the order provided by the developer. + * + * Optional. + */ + bool sort_subcommands; /** * NULL-terminated array of details sections used to better explain how * to use the command. This is shown together with the long description. @@ -408,6 +415,13 @@ typedef struct rz_cmd_t { * non-initialized RzCons. */ bool has_cons; + /** + * True when you want to add multiple commands in batch. This is an + * optimization mainly for groups that require sorted sub-commands, so + * instead of sorting on each addition we just sort one time at the end. + * False by default. + */ + bool batch; } RzCmd; // TODO: remove this once transitioned to RzCmdDesc @@ -425,6 +439,8 @@ typedef bool (*RzCmdForeachNameCb)(RzCmd *cmd, const RzCmdDesc *desc, void *user RZ_API RzCmd *rz_cmd_new(bool has_cons); RZ_API RzCmd *rz_cmd_free(RzCmd *cmd); RZ_API int rz_cmd_set_data(RzCmd *cmd, void *data); +RZ_API void rz_cmd_batch_start(RzCmd *cmd); +RZ_API void rz_cmd_batch_end(RzCmd *cmd); RZ_API int rz_cmd_add(RzCmd *cmd, const char *command, RzCmdCb callback); RZ_API int rz_core_del(RzCmd *cmd, const char *command); RZ_API int rz_cmd_call(RzCmd *cmd, const char *command); diff --git a/librz/util/str.c b/librz/util/str.c index f042d14c8ec..84e9805417f 100644 --- a/librz/util/str.c +++ b/librz/util/str.c @@ -33,11 +33,16 @@ static const char *rwxstr[] = { }; RZ_API int rz_str_casecmp(const char *s1, const char *s2) { + int res; #ifdef _MSC_VER - return stricmp(s1, s2); + res = stricmp(s1, s2); #else - return strcasecmp(s1, s2); + res = strcasecmp(s1, s2); #endif + if (res == 0) { + res = strcmp(s1, s2); + } + return res; } RZ_API int rz_str_ncasecmp(const char *s1, const char *s2, size_t n) { diff --git a/test/unit/test_autocmplt.c b/test/unit/test_autocmplt.c index 59086f29b13..bace633fee2 100644 --- a/test/unit/test_autocmplt.c +++ b/test/unit/test_autocmplt.c @@ -137,10 +137,10 @@ static bool test_autocmplt_newcommand(void) { mu_assert_eq(r->start, 0, "should autocomplete starting from 0"); mu_assert_eq(r->end, 0, "should autocomplete ending at 0"); mu_assert_eq(rz_pvector_len(&r->options), 4, "there are 4 commands available"); - mu_assert_streq(rz_pvector_at(&r->options, 0), "xd", "one is xd"); - mu_assert_streq(rz_pvector_at(&r->options, 1), "xe", "one is xe"); - mu_assert_streq(rz_pvector_at(&r->options, 2), "p", "one is p"); - mu_assert_streq(rz_pvector_at(&r->options, 3), "s", "one is s"); + mu_assert_streq(rz_pvector_at(&r->options, 0), "p", "one is p"); + mu_assert_streq(rz_pvector_at(&r->options, 1), "s", "one is s"); + mu_assert_streq(rz_pvector_at(&r->options, 2), "xd", "one is xd"); + mu_assert_streq(rz_pvector_at(&r->options, 3), "xe", "one is xe"); rz_line_ns_completion_result_free(r); strcpy(buf->data, "p @@c:"); @@ -152,10 +152,10 @@ static bool test_autocmplt_newcommand(void) { mu_assert_eq(r->start, buf->length, "start should be ok"); mu_assert_eq(r->end, buf->length, "end should be ok"); mu_assert_eq(rz_pvector_len(&r->options), 4, "there are 4 commands available"); - mu_assert_streq(rz_pvector_at(&r->options, 0), "xd", "one is xd"); - mu_assert_streq(rz_pvector_at(&r->options, 1), "xe", "one is xe"); - mu_assert_streq(rz_pvector_at(&r->options, 2), "p", "one is p"); - mu_assert_streq(rz_pvector_at(&r->options, 3), "s", "one is s"); + mu_assert_streq(rz_pvector_at(&r->options, 0), "p", "one is p"); + mu_assert_streq(rz_pvector_at(&r->options, 1), "s", "one is s"); + mu_assert_streq(rz_pvector_at(&r->options, 2), "xd", "one is xd"); + mu_assert_streq(rz_pvector_at(&r->options, 3), "xe", "one is xe"); rz_line_ns_completion_result_free(r); rz_core_free(core); diff --git a/test/unit/test_cmd.c b/test/unit/test_cmd.c index d8951161378..fda7a2a749f 100644 --- a/test/unit/test_cmd.c +++ b/test/unit/test_cmd.c @@ -913,6 +913,43 @@ bool test_default_value(void) { mu_end; } +bool test_sort_subcommands(void) { + RzCmdDescHelp z_group_help = { 0 }; + z_group_help.summary = "z summary"; + z_group_help.sort_subcommands = true; + RzCmd *cmd = rz_cmd_new(false); + RzCmdDesc *root = rz_cmd_get_root(cmd); + RzCmdDesc *z_cd = rz_cmd_desc_group_new(cmd, root, "z", NULL, NULL, &z_group_help); + rz_cmd_desc_argv_new(cmd, z_cd, "zx", x_array_handler, &fake_help); + rz_cmd_desc_argv_new(cmd, z_cd, "zc", x_array_handler, &fake_help); + rz_cmd_desc_argv_new(cmd, z_cd, "za", x_array_handler, &fake_help); + + const char *exp1[] = { "za", "zc", "zx" }; + void **it_cd; + size_t i = 0; + + rz_cmd_desc_children_foreach(z_cd, it_cd) { + RzCmdDesc *child = *(RzCmdDesc **)it_cd; + mu_assert_streq(child->name, exp1[i++], "children of z should be sorted"); + } + + rz_cmd_batch_start(cmd); + rz_cmd_desc_argv_new(cmd, z_cd, "zz", x_array_handler, &fake_help); + rz_cmd_desc_argv_new(cmd, z_cd, "zb", x_array_handler, &fake_help); + rz_cmd_desc_argv_new(cmd, z_cd, "zi", x_array_handler, &fake_help); + rz_cmd_batch_end(cmd); + + const char *exp2[] = { "za", "zb", "zc", "zi", "zx", "zz" }; + i = 0; + rz_cmd_desc_children_foreach(z_cd, it_cd) { + RzCmdDesc *child = *(RzCmdDesc **)it_cd; + mu_assert_streq(child->name, exp2[i++], "children of z should be sorted even with batch"); + } + + rz_cmd_free(cmd); + mu_end; +} + int all_tests() { mu_run_test(test_parsed_args_noargs); mu_run_test(test_parsed_args_onearg); @@ -943,6 +980,7 @@ int all_tests() { mu_run_test(test_get_arg); mu_run_test(test_parent_details); mu_run_test(test_default_value); + mu_run_test(test_sort_subcommands); return tests_passed != tests_run; }