shell: Add wildcard support
Extended shell to support wildcard characters: * and ? and expand commands accordingly. Increased default stack size. Signed-off-by: Jakub Rzeszutko <jakub.rzeszutko@nordicsemi.no> Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
parent
486b731b40
commit
68249ce966
5 changed files with 365 additions and 4 deletions
|
@ -33,3 +33,8 @@ zephyr_sources_ifdef(
|
|||
CONFIG_LOG
|
||||
shell_log_backend.c
|
||||
)
|
||||
|
||||
zephyr_sources_ifdef(
|
||||
CONFIG_SHELL_WILDCARD
|
||||
shell_wildcard.c
|
||||
)
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
menu "Shell Options"
|
||||
|
||||
config CONSOLE_SHELL
|
||||
bool "Enable console input handler [ Experimental ]"
|
||||
menuconfig CONSOLE_SHELL
|
||||
bool "Enable legacy shell [ Experimental ]"
|
||||
select CONSOLE_HANDLER
|
||||
select CONSOLE_SUBSYS
|
||||
help
|
||||
|
@ -34,7 +35,7 @@ source "subsys/shell/modules/Kconfig"
|
|||
|
||||
endif
|
||||
|
||||
config SHELL
|
||||
menuconfig SHELL
|
||||
bool "Enable shell"
|
||||
select LOG_RUNTIME_FILTERING
|
||||
select POLL
|
||||
|
@ -47,7 +48,7 @@ source "subsys/logging/Kconfig.template.log_config"
|
|||
|
||||
config SHELL_STACK_SIZE
|
||||
int "Shell thread stack size"
|
||||
default 1024 if MULTITHREADING
|
||||
default 2048 if MULTITHREADING
|
||||
default 0 if !MULTITHREADING
|
||||
help
|
||||
Stack size for thread created for each instance.
|
||||
|
@ -89,6 +90,12 @@ config SHELL_ARGC_MAX
|
|||
If command is composed of more than defined, argument SHELL_ARGC_MAX
|
||||
and following are passed as one argument in the string.
|
||||
|
||||
config SHELL_WILDCARD
|
||||
bool "Enable wildcard support in shell"
|
||||
select FNMATCH
|
||||
help
|
||||
Enables using * in shell.
|
||||
|
||||
config SHELL_ECHO_STATUS
|
||||
bool "Enable echo on shell"
|
||||
default y
|
||||
|
@ -116,6 +123,7 @@ config SHELL_HELP
|
|||
|
||||
config SHELL_HELP_ON_WRONG_ARGUMENT_COUNT
|
||||
bool "Enable printing help on wrong argument count"
|
||||
depends on SHELL_HELP
|
||||
default y
|
||||
|
||||
config SHELL_HISTORY
|
||||
|
@ -163,3 +171,5 @@ config SHELL_CMDS_RESIZE
|
|||
Resize command can be turned off to safe code memory (~0,5k).
|
||||
|
||||
endif #SHELL
|
||||
endmenu
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <shell/shell.h>
|
||||
#include "shell_utils.h"
|
||||
#include "shell_ops.h"
|
||||
#include "shell_wildcard.h"
|
||||
#include "shell_vt100.h"
|
||||
#include <assert.h>
|
||||
#include <atomic.h>
|
||||
|
@ -381,6 +382,15 @@ static const struct shell_static_entry *get_last_command(
|
|||
*match_arg = SHELL_CMD_ROOT_LVL;
|
||||
|
||||
while (*match_arg < argc) {
|
||||
|
||||
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
|
||||
/* ignore wildcard argument */
|
||||
if (shell_wildcard_character_exist(argv[*match_arg])) {
|
||||
(*match_arg)++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
entry = find_cmd(prev_cmd, *match_arg, argv[*match_arg],
|
||||
d_entry);
|
||||
if (entry) {
|
||||
|
@ -922,6 +932,7 @@ static void shell_execute(const struct shell *shell)
|
|||
const struct shell_cmd_entry *p_cmd = NULL;
|
||||
size_t cmd_lvl = SHELL_CMD_ROOT_LVL;
|
||||
size_t cmd_with_handler_lvl = 0;
|
||||
bool wildcard_found = false;
|
||||
size_t cmd_idx;
|
||||
size_t argc;
|
||||
char quote;
|
||||
|
@ -938,6 +949,10 @@ static void shell_execute(const struct shell *shell)
|
|||
history_put(shell, shell->ctx->cmd_buff,
|
||||
shell->ctx->cmd_buff_len);
|
||||
|
||||
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
|
||||
shell_wildcard_prepare(shell);
|
||||
}
|
||||
|
||||
/* create argument list */
|
||||
quote = shell_make_argv(&argc, &argv[0], shell->ctx->cmd_buff,
|
||||
CONFIG_SHELL_ARGC_MAX);
|
||||
|
@ -985,6 +1000,28 @@ static void shell_execute(const struct shell *shell)
|
|||
break;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
|
||||
enum shell_wildcard_status status;
|
||||
|
||||
status = shell_wildcard_process(shell, p_cmd,
|
||||
argv[cmd_lvl]);
|
||||
/* Wildcard character found but there is no matching
|
||||
* command.
|
||||
*/
|
||||
if (status == SHELL_WILDCARD_CMD_NO_MATCH_FOUND) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Wildcard character was not found function can process
|
||||
* argument.
|
||||
*/
|
||||
if (status != SHELL_WILDCARD_NOT_FOUND) {
|
||||
++cmd_lvl;
|
||||
wildcard_found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cmd_get(p_cmd, cmd_lvl, cmd_idx++, &p_static_entry, &d_entry);
|
||||
|
||||
if ((cmd_idx == 0) || (p_static_entry == NULL)) {
|
||||
|
@ -994,6 +1031,28 @@ static void shell_execute(const struct shell *shell)
|
|||
if (strcmp(argv[cmd_lvl], p_static_entry->syntax) == 0) {
|
||||
/* checking if command has a handler */
|
||||
if (p_static_entry->handler != NULL) {
|
||||
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
|
||||
if (wildcard_found) {
|
||||
shell_op_cursor_end_move(shell);
|
||||
shell_op_cond_next_line(shell);
|
||||
|
||||
/* An error occurred, fnmatch
|
||||
* argument cannot be followed
|
||||
* by argument with a handler to
|
||||
* avoid multiple function
|
||||
* calls.
|
||||
*/
|
||||
shell_fprintf(shell,
|
||||
SHELL_ERROR,
|
||||
"Error: requested"
|
||||
" multiple function"
|
||||
" executions\r\n");
|
||||
help_flag_clear(shell);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shell->ctx->active_cmd = *p_static_entry;
|
||||
cmd_with_handler_lvl = cmd_lvl;
|
||||
}
|
||||
|
@ -1004,6 +1063,17 @@ static void shell_execute(const struct shell *shell)
|
|||
}
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
|
||||
shell_wildcard_finalize(shell);
|
||||
/* cmd_buffer has been overwritten by function finalize function
|
||||
* with all expanded commands. Hence shell_make_argv needs to
|
||||
* be called again.
|
||||
*/
|
||||
(void)shell_make_argv(&argc, &argv[0],
|
||||
shell->ctx->cmd_buff,
|
||||
CONFIG_SHELL_ARGC_MAX);
|
||||
}
|
||||
|
||||
/* Executing the deepest found handler. */
|
||||
if (shell->ctx->active_cmd.handler == NULL) {
|
||||
if (shell->ctx->active_cmd.help) {
|
||||
|
|
218
subsys/shell/shell_wildcard.c
Normal file
218
subsys/shell/shell_wildcard.c
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <fnmatch.h>
|
||||
#include "shell_wildcard.h"
|
||||
#include "shell_utils.h"
|
||||
|
||||
static void subcmd_get(const struct shell_cmd_entry *cmd,
|
||||
size_t idx, const struct shell_static_entry **entry,
|
||||
struct shell_static_entry *d_entry)
|
||||
{
|
||||
assert(entry != NULL);
|
||||
assert(st_entry != NULL);
|
||||
|
||||
if (cmd == NULL) {
|
||||
*entry = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd->is_dynamic) {
|
||||
cmd->u.dynamic_get(idx, d_entry);
|
||||
*entry = (d_entry->syntax != NULL) ? d_entry : NULL;
|
||||
} else {
|
||||
*entry = (cmd->u.entry[idx].syntax != NULL) ?
|
||||
&cmd->u.entry[idx] : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static enum shell_wildcard_status command_add(char *buff, u16_t *buff_len,
|
||||
char const *cmd,
|
||||
char const *pattern)
|
||||
{
|
||||
u16_t cmd_len = shell_strlen(cmd);
|
||||
char *completion_addr;
|
||||
u16_t shift;
|
||||
|
||||
/* +1 for space */
|
||||
if ((*buff_len + cmd_len + 1) > CONFIG_SHELL_CMD_BUFF_SIZE) {
|
||||
return SHELL_WILDCARD_CMD_MISSING_SPACE;
|
||||
}
|
||||
|
||||
completion_addr = strstr(buff, pattern);
|
||||
|
||||
if (!completion_addr) {
|
||||
return SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
|
||||
}
|
||||
|
||||
shift = shell_strlen(completion_addr);
|
||||
|
||||
/* make place for new command: + 1 for space + 1 for EOS */
|
||||
memmove(completion_addr + cmd_len + 1, completion_addr, shift + 1);
|
||||
memcpy(completion_addr, cmd, cmd_len);
|
||||
/* adding space to not brake next command in the buffer */
|
||||
completion_addr[cmd_len] = ' ';
|
||||
|
||||
*buff_len += cmd_len + 1; /* + 1 for space */
|
||||
|
||||
return SHELL_WILDCARD_CMD_ADDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal @brief Function for searching and adding commands to the temporary
|
||||
* shell buffer matching to wildcard pattern.
|
||||
*
|
||||
* Function will search commands tree for commands matching wildcard pattern
|
||||
* stored in argv[cmd_lvl]. When match is found wildcard pattern will be
|
||||
* replaced by matching commands. If there is no space in the buffer to add all
|
||||
* matching commands function will add as many as possible. Next it will
|
||||
* continue to search for next wildcard pattern and it will try to add matching
|
||||
* commands.
|
||||
*
|
||||
*
|
||||
* This function is internal to shell module and shall be not called directly.
|
||||
*
|
||||
* @param[in/out] shell Pointer to the CLI instance.
|
||||
* @param[in] cmd Pointer to command which will be processed
|
||||
* @param[in] pattern Pointer to wildcard pattern.
|
||||
*
|
||||
* @retval WILDCARD_CMD_ADDED All matching commands added to the buffer.
|
||||
* @retval WILDCARD_CMD_ADDED_MISSING_SPACE Not all matching commands added
|
||||
* because CONFIG_SHELL_CMD_BUFF_SIZE
|
||||
* is too small.
|
||||
* @retval WILDCARD_CMD_NO_MATCH_FOUND No matching command found.
|
||||
*/
|
||||
static enum shell_wildcard_status commands_expand(const struct shell *shell,
|
||||
const struct shell_cmd_entry *cmd,
|
||||
const char *pattern)
|
||||
{
|
||||
enum shell_wildcard_status ret_val = SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
|
||||
struct shell_static_entry const *p_static_entry = NULL;
|
||||
struct shell_static_entry static_entry;
|
||||
size_t cmd_idx = 0;
|
||||
size_t cnt = 0;
|
||||
|
||||
do {
|
||||
subcmd_get(cmd, cmd_idx++, &p_static_entry, &static_entry);
|
||||
|
||||
if (!p_static_entry) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (fnmatch(pattern, p_static_entry->syntax, 0) == 0) {
|
||||
ret_val = command_add(shell->ctx->temp_buff,
|
||||
&shell->ctx->cmd_tmp_buff_len,
|
||||
p_static_entry->syntax, pattern);
|
||||
if (ret_val == SHELL_WILDCARD_CMD_MISSING_SPACE) {
|
||||
shell_fprintf(shell,
|
||||
SHELL_WARNING,
|
||||
"Command buffer is too short to"
|
||||
" expand all commands matching"
|
||||
" wildcard pattern: %s\r\n",
|
||||
pattern);
|
||||
break;
|
||||
} else if (ret_val != SHELL_WILDCARD_CMD_ADDED) {
|
||||
break;
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
} while (cmd_idx);
|
||||
|
||||
if (cnt > 0) {
|
||||
shell_pattern_remove(shell->ctx->temp_buff,
|
||||
&shell->ctx->cmd_tmp_buff_len, pattern);
|
||||
}
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
bool shell_wildcard_character_exist(const char *str)
|
||||
{
|
||||
size_t str_len = shell_strlen(str);
|
||||
|
||||
for (size_t i = 0; i < str_len; i++) {
|
||||
if ((str[i] == '?') || (str[i] == '*')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void shell_wildcard_prepare(const struct shell *shell)
|
||||
{
|
||||
/* Wildcard can be correctly handled under following conditions:
|
||||
* - wildcard command does not have a handler
|
||||
* - wildcard command is on the deepest commands level
|
||||
* - other commands on the same level as wildcard command shall also not
|
||||
* have a handler
|
||||
*
|
||||
* Algorithm:
|
||||
* 1. Command buffer: ctx->cmd_buff is copied to temporary buffer:
|
||||
* ctx->temp_buff.
|
||||
* 2. Algorithm goes through command buffer to find handlers and
|
||||
* subcommands.
|
||||
* 3. If algorithm will find a wildcard character it switches to
|
||||
* Temporary buffer.
|
||||
* 4. In the Temporary buffer command containing wildcard character is
|
||||
* replaced by matching command(s).
|
||||
* 5. Algorithm switch back to Command buffer and analyzes next command.
|
||||
* 6. When all arguments are analyzed from Command buffer, Temporary
|
||||
* buffer with all expanded commands is copied to Command buffer.
|
||||
* 7. Deepest found handler is executed and all lower level commands,
|
||||
* including expanded commands, are passed as arguments.
|
||||
*/
|
||||
|
||||
memset(shell->ctx->temp_buff, 0, sizeof(shell->ctx->temp_buff));
|
||||
memcpy(shell->ctx->temp_buff,
|
||||
shell->ctx->cmd_buff,
|
||||
shell->ctx->cmd_buff_len);
|
||||
|
||||
/* Function shell_spaces_trim must be used instead of shell_make_argv.
|
||||
* At this point it is important to keep temp_buff as one string.
|
||||
* It will allow to find wildcard commands easily with strstr function.
|
||||
*/
|
||||
shell_spaces_trim(shell->ctx->temp_buff);
|
||||
|
||||
/* +1 for EOS*/
|
||||
shell->ctx->cmd_tmp_buff_len = shell_strlen(shell->ctx->temp_buff) + 1;
|
||||
}
|
||||
|
||||
|
||||
enum shell_wildcard_status shell_wildcard_process(const struct shell *shell,
|
||||
const struct shell_cmd_entry *cmd,
|
||||
const char *pattern)
|
||||
{
|
||||
enum shell_wildcard_status ret_val = SHELL_WILDCARD_NOT_FOUND;
|
||||
|
||||
if (cmd == NULL) {
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
if (!shell_wildcard_character_exist(pattern)) {
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
/* Function will search commands tree for commands matching wildcard
|
||||
* pattern stored in argv[cmd_lvl]. When match is found wildcard pattern
|
||||
* will be replaced by matching commands. If there is no space in the
|
||||
* buffer to add all matching commands function will add as many as
|
||||
* possible. Next it will continue to search for next wildcard pattern
|
||||
* and it will try to add matching commands.
|
||||
*/
|
||||
ret_val = commands_expand(shell, cmd, pattern);
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
void shell_wildcard_finalize(const struct shell *shell)
|
||||
{
|
||||
memcpy(shell->ctx->cmd_buff,
|
||||
shell->ctx->temp_buff,
|
||||
shell->ctx->cmd_tmp_buff_len);
|
||||
shell->ctx->cmd_buff_len = shell->ctx->cmd_tmp_buff_len;
|
||||
}
|
58
subsys/shell/shell_wildcard.h
Normal file
58
subsys/shell/shell_wildcard.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef SHELL_SHELL_WILDCARDS_H__
|
||||
#define SHELL_SHELL_WILDCARDS_H__
|
||||
|
||||
#include <shell/shell.h>
|
||||
|
||||
enum shell_wildcard_status {
|
||||
SHELL_WILDCARD_CMD_ADDED,
|
||||
SHELL_WILDCARD_CMD_MISSING_SPACE,
|
||||
SHELL_WILDCARD_CMD_NO_MATCH_FOUND, /* no matching command */
|
||||
SHELL_WILDCARD_NOT_FOUND /* wildcard character not found */
|
||||
};
|
||||
|
||||
/* Function initializing wildcard expansion procedure.
|
||||
*
|
||||
* @param[in] shell Pointer to the shell instance.
|
||||
*/
|
||||
void shell_wildcard_prepare(const struct shell *shell);
|
||||
|
||||
/* Function returns true if string contains wildcard character: '?' or '*'
|
||||
*
|
||||
* @param[in] str Pointer to string.
|
||||
*
|
||||
* @retval true wildcard character found
|
||||
* @retval false wildcard character not found
|
||||
*/
|
||||
bool shell_wildcard_character_exist(const char *str);
|
||||
|
||||
/* Function expands wildcards in the shell temporary buffer
|
||||
*
|
||||
* @brief Function evaluates one command. If command contains wildcard patter,
|
||||
* function will expand this command with all commands matching wildcard
|
||||
* pattern.
|
||||
*
|
||||
* Function will print a help string with: the currently entered command, its
|
||||
* options,and subcommands (if they exist).
|
||||
*
|
||||
* @param[in] shell Pointer to the shell instance.
|
||||
* @param[in] cmd Pointer to command which will be processed.
|
||||
* @param[in] pattern Pointer to wildcard pattern.
|
||||
*/
|
||||
enum shell_wildcard_status shell_wildcard_process(const struct shell *shell,
|
||||
const struct shell_cmd_entry *cmd,
|
||||
const char *pattern);
|
||||
|
||||
/* Function finalizing wildcard expansion procedure.
|
||||
*
|
||||
* @param[in] shell Pointer to the shell instance.
|
||||
*/
|
||||
void shell_wildcard_finalize(const struct shell *shell);
|
||||
|
||||
|
||||
#endif /* SHELL_SHELL_WILDCARDS_H__ */
|
Loading…
Add table
Add a link
Reference in a new issue