diff --git a/doc/reference/shell/index.rst b/doc/reference/shell/index.rst index 2159bab7ccc..94fcd5dec8c 100644 --- a/doc/reference/shell/index.rst +++ b/doc/reference/shell/index.rst @@ -82,10 +82,22 @@ Use the following macros for adding shell commands: * :c:macro:`SHELL_CMD_REGISTER` - Create root command. All root commands must have different name. +* :c:macro:`SHELL_COND_CMD_REGISTER` - Conditionally (if compile time flag is + set) create root command. All root commands must have different name. * :c:macro:`SHELL_CMD_ARG_REGISTER` - Create root command with arguments. All root commands must have different name. +* :c:macro:`SHELL_COND_CMD_ARG_REGISTER` - Conditionally (if compile time flag + is set) create root command with arguments. All root commands must have + different name. * :c:macro:`SHELL_CMD` - Initialize a command. +* :c:macro:`SHELL_COND_CMD` - Initialize a command if compile time flag is set. +* :c:macro:`SHELL_EXPR_CMD` - Initialize a command if compile time expression is + non-zero. * :c:macro:`SHELL_CMD_ARG` - Initialize a command with arguments. +* :c:macro:`SHELL_COND_CMD_ARG` - Initialize a command with arguments if compile + time flag is set. +* :c:macro:`SHELL_EXPR_CMD_ARG` - Initialize a command with arguments if compile + time expression is non-zero. * :c:macro:`SHELL_STATIC_SUBCMD_SET_CREATE` - Create a static subcommands array. * :c:macro:`SHELL_DYNAMIC_CMD_CREATE` - Create a dynamic subcommands array. diff --git a/include/shell/shell.h b/include/shell/shell.h index 0429c6b4b9e..7092c550aa3 100644 --- a/include/shell/shell.h +++ b/include/shell/shell.h @@ -129,6 +129,41 @@ struct shell_static_entry { .u = {.entry = &UTIL_CAT(_shell_, syntax)} \ } +/** + * @brief Macro for defining and adding a conditional root command (level 0) + * with required number of arguments. + * + * @see SHELL_CMD_ARG_REGISTER for details. + * + * Macro can be used to create a command which can be conditionally present. + * It is and alternative to \#ifdefs around command registration and command + * handler. If command is disabled handler and subcommands are removed from + * the application. + * + * @param[in] flag Compile time flag. Command is present only if flag + * exists and equals 1. + * @param[in] syntax Command syntax (for example: history). + * @param[in] subcmd Pointer to a subcommands array. + * @param[in] help Pointer to a command help string. + * @param[in] handler Pointer to a function handler. + * @param[in] mandatory Number of mandatory arguments. + * @param[in] optional Number of optional arguments. + */ +#define SHELL_COND_CMD_ARG_REGISTER(flag, syntax, subcmd, help, handler, \ + mandatory, optional) \ + COND_CODE_1(\ + flag, \ + (\ + SHELL_CMD_ARG_REGISTER(syntax, subcmd, help, handler, \ + mandatory, optional) \ + ), \ + (\ + static shell_cmd_handler dummy_##syntax##handler \ + __attribute__((unused)) = handler;\ + static const struct shell_cmd_entry *dummy_subcmd_##syntax \ + __attribute__((unused)) = subcmd\ + )\ + ) /** * @brief Macro for defining and adding a root command (level 0) with * arguments. @@ -141,15 +176,23 @@ struct shell_static_entry { * @param[in] handler Pointer to a function handler. */ #define SHELL_CMD_REGISTER(syntax, subcmd, help, handler) \ - static const struct shell_static_entry UTIL_CAT(_shell_, syntax) = \ - SHELL_CMD(syntax, subcmd, help, handler); \ - const struct shell_cmd_entry UTIL_CAT(shell_cmd_, syntax) \ - __attribute__ ((section("." \ - STRINGIFY(UTIL_CAT(shell_root_cmd_, syntax))))) \ - __attribute__((used)) = { \ - .is_dynamic = false, \ - .u = { .entry = &UTIL_CAT(_shell_, syntax) } \ - } + SHELL_CMD_ARG_REGISTER(syntax, subcmd, help, handler, 0, 0) + +/** + * @brief Macro for defining and adding a conditional root command (level 0) + * with arguments. + * + * @see SHELL_COND_CMD_ARG_REGISTER. + * + * @param[in] flag Compile time flag. Command is present only if flag + * exists and equals 1. + * @param[in] syntax Command syntax (for example: history). + * @param[in] subcmd Pointer to a subcommands array. + * @param[in] help Pointer to a command help string. + * @param[in] handler Pointer to a function handler. + */ +#define SHELL_COND_CMD_REGISTER(flag, syntax, subcmd, help, handler) \ + SHELL_COND_CMD_ARG_REGISTER(flag, syntax, subcmd, help, handler, 0, 0) /** * @brief Macro for creating a subcommand set. It must be used outside of any @@ -226,21 +269,68 @@ struct shell_static_entry { * @note If a command will be called with wrong number of arguments shell will * print an error message and command handler will not be called. * + * @param[in] syntax Command syntax (for example: history). + * @param[in] subcmd Pointer to a subcommands array. + * @param[in] help Pointer to a command help string. + * @param[in] handler Pointer to a function handler. + * @param[in] mand Number of mandatory arguments. + * @param[in] opt Number of optional arguments. + */ +#define SHELL_CMD_ARG(syntax, subcmd, help, handler, mand, opt) \ + SHELL_EXPR_CMD_ARG(1, syntax, subcmd, help, handler, mand, opt) + +/** + * @brief Initializes a conditional shell command with arguments. + * + * @see SHELL_CMD_ARG. Based on the flag, creates a valid entry or an empty + * command which is ignored by the shell. It is an alternative to \#ifdefs + * around command registration and command handler. However, empty structure is + * present in the flash even if command is disabled (subcommands and handler are + * removed). Macro internally handles case if flag is not defined so flag must + * be provided without any wrapper, e.g.: SHELL_COND_CMD_ARG(CONFIG_FOO, ...) + * + * @param[in] flag Compile time flag. Command is present only if flag + * exists and equals 1. + * @param[in] syntax Command syntax (for example: history). + * @param[in] subcmd Pointer to a subcommands array. + * @param[in] help Pointer to a command help string. + * @param[in] handler Pointer to a function handler. + * @param[in] mand Number of mandatory arguments. + * @param[in] opt Number of optional arguments. + */ +#define SHELL_COND_CMD_ARG(flag, syntax, subcmd, help, handler, mand, opt) \ + SHELL_EXPR_CMD_ARG(IS_ENABLED(flag), syntax, subcmd, help, \ + handler, mand, opt) + +/** + * @brief Initializes a conditional shell command with arguments if expression + * gives non-zero result at compile time. + * + * @see SHELL_CMD_ARG. Based on the expression, creates a valid entry or an + * empty command which is ignored by the shell. It should be used instead of + * @ref SHELL_COND_CMD_ARG if condition is not a single configuration flag, + * e.g.: + * SHELL_EXPR_CMD_ARG(IS_ENABLED(CONFIG_FOO) && + * IS_ENABLED(CONFIG_FOO_SETTING_1), ...) + * + * @param[in] _expr Expression. * @param[in] _syntax Command syntax (for example: history). * @param[in] _subcmd Pointer to a subcommands array. * @param[in] _help Pointer to a command help string. * @param[in] _handler Pointer to a function handler. - * @param[in] _mandatory Number of mandatory arguments. - * @param[in] _optional Number of optional arguments. + * @param[in] _mand Number of mandatory arguments. + * @param[in] _opt Number of optional arguments. */ -#define SHELL_CMD_ARG(_syntax, _subcmd, _help, _handler, \ - _mandatory, _optional) { \ - .syntax = (const char *)STRINGIFY(_syntax), \ - .help = (const char *)_help, \ - .subcmd = _subcmd, \ - .handler = _handler, \ - .args = {. mandatory = _mandatory, .optional = _optional} \ -} +#define SHELL_EXPR_CMD_ARG(_expr, _syntax, _subcmd, _help, _handler, \ + _mand, _opt) \ + { \ + .syntax = (_expr) ? (const char *)STRINGIFY(_syntax) : "", \ + .help = (_expr) ? (const char *)_help : NULL, \ + .subcmd = (const struct shell_cmd_entry *)((_expr) ? \ + _subcmd : NULL), \ + .handler = (shell_cmd_handler)((_expr) ? _handler : NULL), \ + .args = { .mandatory = _mand, .optional = _opt} \ + } /** * @brief Initializes a shell command. @@ -253,6 +343,36 @@ struct shell_static_entry { #define SHELL_CMD(_syntax, _subcmd, _help, _handler) \ SHELL_CMD_ARG(_syntax, _subcmd, _help, _handler, 0, 0) +/** + * @brief Initializes a conditional shell command. + * + * @see SHELL_COND_CMD_ARG. + * + * @param[in] _flag Compile time flag. Command is present only if flag + * exists and equals 1. + * @param[in] _syntax Command syntax (for example: history). + * @param[in] _subcmd Pointer to a subcommands array. + * @param[in] _help Pointer to a command help string. + * @param[in] _handler Pointer to a function handler. + */ +#define SHELL_COND_CMD(_flag, _syntax, _subcmd, _help, _handler) \ + SHELL_COND_CMD_ARG(_flag, _syntax, _subcmd, _help, _handler, 0, 0) + +/** + * @brief Initializes shell command if expression gives non-zero result at + * compile time. + * + * @see SHELL_EXPR_CMD_ARG. + * + * @param[in] _expr Compile time expression. Command is present only if + * expression is non-zero. + * @param[in] _syntax Command syntax (for example: history). + * @param[in] _subcmd Pointer to a subcommands array. + * @param[in] _help Pointer to a command help string. + * @param[in] _handler Pointer to a function handler. + */ +#define SHELL_EXPR_CMD(_expr, _syntax, _subcmd, _help, _handler) \ + SHELL_EXPR_CMD_ARG(_expr, _syntax, _subcmd, _help, _handler, 0, 0) /** * @internal @brief Internal shell state in response to data received from the diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index 73cae22139d..745ac0328b8 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -321,6 +321,15 @@ static bool tab_prepare(const struct shell *shell, return true; } +/* Empty command is identified by null handler and subcommand but contrary + * to array termination null command, it has non-null syntax address. + */ +static inline bool is_empty_cmd(const struct shell_static_entry *entry) +{ + return entry->syntax && + (entry->handler == NULL) && (entry->subcmd == NULL); +} + static inline bool is_completion_candidate(const char *candidate, const char *str, size_t len) { @@ -342,6 +351,9 @@ static void find_completion_candidates(const struct shell_static_entry *cmd, *cnt = 0; while (true) { + bool is_empty; + bool is_candidate; + shell_cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, idx, &candidate, &dynamic_entry); @@ -349,8 +361,10 @@ static void find_completion_candidates(const struct shell_static_entry *cmd, break; } - if (is_completion_candidate(candidate->syntax, incompl_cmd, - incompl_cmd_len)) { + is_empty = is_empty_cmd(candidate); + is_candidate = is_completion_candidate(candidate->syntax, + incompl_cmd, incompl_cmd_len); + if (!is_empty && is_candidate) { size_t slen = strlen(candidate->syntax); *longest = (slen > *longest) ? slen : *longest; @@ -438,15 +452,17 @@ static void tab_options_print(const struct shell *shell, tab_item_print(shell, SHELL_INIT_OPTION_PRINTER, longest); while (cnt) { + bool is_empty; + /* shell->ctx->active_cmd can be safely used outside of command * context to save stack */ shell_cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, idx, &match, &shell->ctx->active_cmd); idx++; - - if (str && match->syntax && - !is_completion_candidate(match->syntax, str, str_len)) { + is_empty = is_empty_cmd(match); + if (is_empty || (str && match->syntax && + !is_completion_candidate(match->syntax, str, str_len))) { continue; }