Skip to content

Commit

Permalink
shell: Add wildcard support
Browse files Browse the repository at this point in the history
Extended shell to support wildcard characters (*, ?) and expand commands
accordingly.

Sign-off-by: Jakub Rzeszutko <jakub.rzeszutko@nordicsemi.no>
  • Loading branch information
nordic-krch authored and Jakub Rzeszutko committed Aug 9, 2018
1 parent de333d4 commit 76d6cb7
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 0 deletions.
5 changes: 5 additions & 0 deletions subsys/shell/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ zephyr_sources_ifdef(
CONFIG_LOG
shell_log_backend.c
)

zephyr_sources_ifdef(
CONFIG_SHELL_WILDCARD
shell_wildcard.c
)
6 changes: 6 additions & 0 deletions subsys/shell/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ config SHELL_ARGC_MAX
If command is composed of more than defined, argument SHELL_ARGC_MAX
and follwing 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
Expand Down
68 changes: 68 additions & 0 deletions subsys/shell/shell.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -385,6 +386,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) {
Expand Down Expand Up @@ -904,6 +914,7 @@ static void shell_execute(const struct shell *shell)
struct shell_cmd_entry const *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;
Expand All @@ -915,6 +926,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);
}

shell_op_cursor_end_move(shell);
cursor_next_line_move(shell);

Expand Down Expand Up @@ -966,6 +981,27 @@ 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)) {
Expand All @@ -975,6 +1011,27 @@ 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");
flag_help_clear(shell);

return;
}
}

shell->ctx->active_cmd = *p_static_entry;
cmd_with_handler_lvl = cmd_lvl;
}
Expand All @@ -985,6 +1042,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) {
Expand Down
210 changes: 210 additions & 0 deletions subsys/shell/shell_wildcard.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <string.h>
#include <lib/fnmatch/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.
*
* 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 (0 == fnmatch(pattern, p_static_entry->syntax, 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 is copied to Temp buffer.
* 2. Algorithm goes through Command buffer to find handlers and
* subcommands.
* 3. If algorithm will find a wildcard character it switches to Temp
* buffer.
* 4. In the Temp buffer command with found wildcard character is
* changed into matching command(s).
* 5. Algorithm switch back to Command buffer and analyzes next command.
* 6. When all arguments are analyzed from Command buffer, Temp buffer
* is copied to Command buffer.
* 7. Last found handler is executed with all arguments in the Command
* buffer.
*/

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 a 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]. If match is found wildcard pattern
* will be replaced by matching commands in temp_buffer. If there is no
* space 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;
}
32 changes: 32 additions & 0 deletions subsys/shell/shell_wildcard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 */
};


bool shell_wildcard_character_exist(const char *str);

void shell_wildcard_prepare(const struct shell *shell);

/* Function expands wildcards in the shell temporary buffer */
enum shell_wildcard_status shell_wildcard_process(const struct shell *shell,
const struct shell_cmd_entry *cmd,
const char *pattern);

void shell_wildcard_finalize(const struct shell *shell);


#endif /* SHELL_SHELL_WILDCARDS_H__ */

0 comments on commit 76d6cb7

Please sign in to comment.