| /* |
| * Copyright (c) 2018 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <ctype.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <shell/shell.h> |
| #include <shell/shell_dummy.h> |
| #include "shell_utils.h" |
| #include "shell_ops.h" |
| #include "shell_wildcard.h" |
| #include "shell_vt100.h" |
| #include <atomic.h> |
| |
| /* 2 == 1 char for cmd + 1 char for '\0' */ |
| #if (CONFIG_SHELL_CMD_BUFF_SIZE < 2) |
| #error too small CONFIG_SHELL_CMD_BUFF_SIZE |
| #endif |
| |
| #if (CONFIG_SHELL_PRINTF_BUFF_SIZE < 1) |
| #error too small SHELL_PRINTF_BUFF_SIZE |
| #endif |
| |
| #define SHELL_MSG_COMMAND_NOT_FOUND ": command not found" |
| #define SHELL_MSG_TAB_OVERFLOWED \ |
| "Tab function: commands counter overflowed.\r\n" |
| |
| #define SHELL_INIT_OPTION_PRINTER (NULL) |
| |
| /* Initial cursor position is: (1, 1). */ |
| #define SHELL_INITIAL_CURS_POS (1u) |
| |
| #define EXIT_HISTORY(shell) \ |
| ((shell)->ctx->internal.flags.history_exit) |
| #define EXIT_HISTORY_REQUEST(shell) \ |
| ((shell)->ctx->internal.flags.history_exit = 1) |
| #define EXIT_HISTORY_CLEAR(shell) \ |
| ((shell)->ctx->internal.flags.history_exit = 0) |
| |
| static int shell_execute(const struct shell *shell); |
| |
| extern const struct shell_cmd_entry __shell_root_cmds_start[0]; |
| extern const struct shell_cmd_entry __shell_root_cmds_end[0]; |
| |
| static inline const struct shell_cmd_entry *shell_root_cmd_get(u32_t id) |
| { |
| return &__shell_root_cmds_start[id]; |
| } |
| |
| static inline u32_t shell_root_cmd_count(void) |
| { |
| return ((u8_t *)__shell_root_cmds_end - |
| (u8_t *)__shell_root_cmds_start)/ |
| sizeof(struct shell_cmd_entry); |
| } |
| |
| static inline void transport_buffer_flush(const struct shell *shell) |
| { |
| shell_fprintf_buffer_flush(shell->fprintf_ctx); |
| } |
| |
| static inline void help_flag_set(const struct shell *shell) |
| { |
| shell->ctx->internal.flags.show_help = 1; |
| } |
| static inline void help_flag_clear(const struct shell *shell) |
| { |
| shell->ctx->internal.flags.show_help = 0; |
| } |
| |
| /* Function returns true if delete escape code shall be interpreted as |
| * backspace. |
| */ |
| static inline bool flag_delete_mode_set(const struct shell *shell) |
| { |
| return shell->ctx->internal.flags.mode_delete == 1 ? true : false; |
| } |
| |
| static inline bool flag_processing_is_set(const struct shell *shell) |
| { |
| return shell->ctx->internal.flags.processing == 1 ? true : false; |
| } |
| |
| static inline void receive_state_change(const struct shell *shell, |
| enum shell_receive_state state) |
| { |
| shell->ctx->receive_state = state; |
| } |
| |
| static void shell_cmd_buffer_clear(const struct shell *shell) |
| { |
| shell->ctx->cmd_buff[0] = '\0'; /* clear command buffer */ |
| shell->ctx->cmd_buff_pos = 0; |
| shell->ctx->cmd_buff_len = 0; |
| } |
| |
| static void shell_pend_on_txdone(const struct shell *shell) |
| { |
| if (IS_ENABLED(CONFIG_MULTITHREADING)) { |
| k_poll(&shell->ctx->events[SHELL_SIGNAL_TXDONE], 1, K_FOREVER); |
| k_poll_signal_reset(&shell->ctx->signals[SHELL_SIGNAL_TXDONE]); |
| } else { |
| /* Blocking wait in case of bare metal. */ |
| while (!shell->ctx->internal.flags.tx_rdy) { |
| } |
| shell->ctx->internal.flags.tx_rdy = 0; |
| } |
| } |
| |
| /* Function sends data stream to the shell instance. Each time before the |
| * shell_write function is called, it must be ensured that IO buffer of fprintf |
| * is flushed to avoid synchronization issues. |
| * For that purpose, use function transport_buffer_flush(shell) |
| */ |
| static void shell_write(const struct shell *shell, const void *data, |
| size_t length) |
| { |
| __ASSERT_NO_MSG(shell && data); |
| |
| size_t offset = 0; |
| size_t tmp_cnt; |
| |
| while (length) { |
| int err = shell->iface->api->write(shell->iface, |
| &((const u8_t *) data)[offset], length, |
| &tmp_cnt); |
| (void)err; |
| __ASSERT_NO_MSG(err == 0); |
| __ASSERT_NO_MSG(length >= tmp_cnt); |
| offset += tmp_cnt; |
| length -= tmp_cnt; |
| if (tmp_cnt == 0 && |
| (shell->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) { |
| shell_pend_on_txdone(shell); |
| } |
| } |
| } |
| |
| /* @brief Function shall be used to search commands. |
| * |
| * It moves the pointer entry to command of static command structure. If the |
| * command cannot be found, the function will set entry to NULL. |
| * |
| * @param command Pointer to command which will be processed (no matter |
| * the root command). |
| * @param lvl Level of the requested command. |
| * @param idx Index of the requested command. |
| * @param entry Pointer which points to subcommand[idx] after function |
| * execution. |
| * @param st_entry Pointer to the structure where dynamic entry data can be |
| * stored. |
| */ |
| static void cmd_get(const struct shell_cmd_entry *command, size_t lvl, |
| size_t idx, const struct shell_static_entry **entry, |
| struct shell_static_entry *d_entry) |
| { |
| __ASSERT_NO_MSG(entry != NULL); |
| __ASSERT_NO_MSG(d_entry != NULL); |
| |
| if (lvl == SHELL_CMD_ROOT_LVL) { |
| if (idx < shell_root_cmd_count()) { |
| const struct shell_cmd_entry *cmd; |
| |
| cmd = shell_root_cmd_get(idx); |
| *entry = cmd->u.entry; |
| } else { |
| *entry = NULL; |
| } |
| return; |
| } |
| |
| if (command == NULL) { |
| *entry = NULL; |
| return; |
| } |
| |
| if (command->is_dynamic) { |
| command->u.dynamic_get(idx, d_entry); |
| *entry = (d_entry->syntax != NULL) ? d_entry : NULL; |
| } else { |
| *entry = (command->u.entry[idx].syntax != NULL) ? |
| &command->u.entry[idx] : NULL; |
| } |
| } |
| |
| static void vt100_color_set(const struct shell *shell, |
| enum shell_vt100_color color) |
| { |
| |
| if (shell->ctx->vt100_ctx.col.col == color) { |
| return; |
| } |
| |
| shell->ctx->vt100_ctx.col.col = color; |
| |
| if (color != SHELL_NORMAL) { |
| |
| u8_t cmd[] = SHELL_VT100_COLOR(color - 1); |
| |
| shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); |
| } else { |
| static const u8_t cmd[] = SHELL_VT100_MODESOFF; |
| |
| shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); |
| } |
| } |
| |
| static void vt100_bgcolor_set(const struct shell *shell, |
| enum shell_vt100_color bgcolor) |
| { |
| if ((bgcolor == SHELL_NORMAL) || |
| (shell->ctx->vt100_ctx.col.bgcol == bgcolor)) { |
| return; |
| } |
| |
| /* -1 because default value is first in enum */ |
| u8_t cmd[] = SHELL_VT100_BGCOLOR(bgcolor - 1); |
| |
| shell->ctx->vt100_ctx.col.bgcol = bgcolor; |
| shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); |
| |
| } |
| |
| static inline void vt100_colors_store(const struct shell *shell, |
| struct shell_vt100_colors *color) |
| { |
| memcpy(color, &shell->ctx->vt100_ctx.col, sizeof(*color)); |
| } |
| |
| static void vt100_colors_restore(const struct shell *shell, |
| const struct shell_vt100_colors *color) |
| { |
| vt100_color_set(shell, color->col); |
| vt100_bgcolor_set(shell, color->bgcol); |
| } |
| |
| static void shell_state_set(const struct shell *shell, enum shell_state state) |
| { |
| shell->ctx->state = state; |
| |
| if (state == SHELL_STATE_ACTIVE) { |
| shell_cmd_buffer_clear(shell); |
| shell_fprintf(shell, SHELL_INFO, "%s", shell->prompt); |
| } |
| } |
| |
| static void tab_item_print(const struct shell *shell, const char *option, |
| u16_t longest_option) |
| { |
| static const char *tab = " "; |
| u16_t columns; |
| u16_t diff; |
| |
| /* Function initialization has been requested. */ |
| if (option == NULL) { |
| shell->ctx->vt100_ctx.printed_cmd = 0; |
| return; |
| } |
| |
| longest_option += shell_strlen(tab); |
| |
| columns = (shell->ctx->vt100_ctx.cons.terminal_wid |
| - shell_strlen(tab)) / longest_option; |
| diff = longest_option - shell_strlen(option); |
| |
| if (shell->ctx->vt100_ctx.printed_cmd++ % columns == 0) { |
| shell_fprintf(shell, SHELL_OPTION, "\r\n%s%s", tab, option); |
| } else { |
| shell_fprintf(shell, SHELL_OPTION, "%s", option); |
| } |
| |
| shell_op_cursor_horiz_move(shell, diff); |
| } |
| |
| static void history_init(const struct shell *shell) |
| { |
| if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { |
| return; |
| } |
| |
| shell_history_init(shell->history); |
| } |
| |
| static void history_purge(const struct shell *shell) |
| { |
| if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { |
| return; |
| } |
| |
| shell_history_purge(shell->history); |
| } |
| |
| static void history_mode_exit(const struct shell *shell) |
| { |
| if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { |
| return; |
| } |
| |
| EXIT_HISTORY_CLEAR(shell); |
| shell_history_mode_exit(shell->history); |
| } |
| |
| static void history_put(const struct shell *shell, u8_t *line, size_t length) |
| { |
| if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { |
| return; |
| } |
| |
| shell_history_put(shell->history, line, length); |
| } |
| |
| static void history_handle(const struct shell *shell, bool up) |
| { |
| bool history_mode; |
| size_t len; |
| |
| /*optional feature */ |
| if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { |
| return; |
| } |
| |
| /* Checking if history process has been stopped */ |
| if (EXIT_HISTORY(shell)) { |
| EXIT_HISTORY_CLEAR(shell); |
| shell_history_mode_exit(shell->history); |
| } |
| |
| /* Backup command if history is entered */ |
| if (!shell_history_active(shell->history)) { |
| if (up) { |
| u16_t cmd_len = shell_strlen(shell->ctx->cmd_buff); |
| |
| if (cmd_len) { |
| strcpy(shell->ctx->temp_buff, |
| shell->ctx->cmd_buff); |
| } else { |
| shell->ctx->temp_buff[0] = '\0'; |
| } |
| } else { |
| /* Pressing 'down' not in history mode has no effect. */ |
| return; |
| } |
| } |
| |
| /* Start by checking if history is not empty. */ |
| history_mode = shell_history_get(shell->history, up, |
| shell->ctx->cmd_buff, &len); |
| |
| /* On exiting history mode print backed up command. */ |
| if (!history_mode) { |
| strcpy(shell->ctx->cmd_buff, shell->ctx->temp_buff); |
| len = shell_strlen(shell->ctx->cmd_buff); |
| } |
| |
| shell_op_cursor_home_move(shell); |
| clear_eos(shell); |
| shell_fprintf(shell, SHELL_NORMAL, "%s", shell->ctx->cmd_buff); |
| shell->ctx->cmd_buff_pos = len; |
| shell->ctx->cmd_buff_len = len; |
| shell_op_cond_next_line(shell); |
| } |
| |
| static const struct shell_static_entry *find_cmd( |
| const struct shell_cmd_entry *cmd, |
| size_t lvl, |
| char *cmd_str, |
| struct shell_static_entry *d_entry) |
| { |
| const struct shell_static_entry *entry = NULL; |
| size_t idx = 0; |
| |
| do { |
| cmd_get(cmd, lvl, idx++, &entry, d_entry); |
| if (entry && (strcmp(cmd_str, entry->syntax) == 0)) { |
| return entry; |
| } |
| } while (entry); |
| |
| return entry; |
| } |
| |
| /** @brief Function for getting last valid command in list of arguments. */ |
| static const struct shell_static_entry *get_last_command( |
| const struct shell *shell, |
| size_t argc, |
| char *argv[], |
| size_t *match_arg, |
| struct shell_static_entry *d_entry) |
| { |
| const struct shell_static_entry *prev_entry = NULL; |
| const struct shell_cmd_entry *prev_cmd = NULL; |
| const struct shell_static_entry *entry = NULL; |
| *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) { |
| prev_cmd = entry->subcmd; |
| prev_entry = entry; |
| (*match_arg)++; |
| } else { |
| entry = NULL; |
| break; |
| } |
| } |
| |
| return entry; |
| } |
| |
| static inline u16_t completion_space_get(const struct shell *shell) |
| { |
| u16_t space = (CONFIG_SHELL_CMD_BUFF_SIZE - 1) - |
| shell->ctx->cmd_buff_len; |
| return space; |
| } |
| |
| /* Prepare arguments and return number of space available for completion. */ |
| static bool shell_tab_prepare(const struct shell *shell, |
| const struct shell_static_entry **cmd, |
| char **argv, size_t *argc, |
| size_t *complete_arg_idx, |
| struct shell_static_entry *d_entry) |
| { |
| u16_t compl_space = completion_space_get(shell); |
| size_t search_argc; |
| |
| if (compl_space == 0) { |
| return false; |
| } |
| |
| /* Copy command from its beginning to cursor position. */ |
| memcpy(shell->ctx->temp_buff, shell->ctx->cmd_buff, |
| shell->ctx->cmd_buff_pos); |
| shell->ctx->temp_buff[shell->ctx->cmd_buff_pos] = '\0'; |
| |
| /* Create argument list. */ |
| (void)shell_make_argv(argc, argv, shell->ctx->temp_buff, |
| CONFIG_SHELL_ARGC_MAX); |
| |
| /* If last command is not completed (followed by space) it is treated |
| * as uncompleted one. |
| */ |
| int space = isspace((int)shell->ctx->cmd_buff[ |
| shell->ctx->cmd_buff_pos - 1]); |
| |
| /* root command completion */ |
| if ((*argc == 0) || ((space == 0) && (*argc == 1))) { |
| *complete_arg_idx = SHELL_CMD_ROOT_LVL; |
| *cmd = NULL; |
| return true; |
| } |
| |
| search_argc = space ? *argc : *argc - 1; |
| |
| *cmd = get_last_command(shell, search_argc, argv, complete_arg_idx, |
| d_entry); |
| |
| /* if search_argc == 0 (empty command line) get_last_command will return |
| * NULL tab is allowed, otherwise not. |
| */ |
| if ((*cmd == NULL) && (search_argc != 0)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static inline bool is_completion_candidate(const char *candidate, |
| const char *str, size_t len) |
| { |
| return (strncmp(candidate, str, len) == 0) ? true : false; |
| } |
| |
| static void find_completion_candidates(const struct shell_static_entry *cmd, |
| const char *incompl_cmd, |
| size_t *first_idx, size_t *cnt, |
| u16_t *longest) |
| { |
| size_t incompl_cmd_len = shell_strlen(incompl_cmd); |
| const struct shell_static_entry *candidate; |
| struct shell_static_entry dynamic_entry; |
| bool found = false; |
| size_t idx = 0; |
| |
| *longest = 0; |
| *cnt = 0; |
| |
| while (true) { |
| cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, |
| idx, &candidate, &dynamic_entry); |
| |
| if (!candidate) { |
| break; |
| } |
| |
| if (is_completion_candidate(candidate->syntax, incompl_cmd, |
| incompl_cmd_len)) { |
| size_t slen = strlen(candidate->syntax); |
| |
| *longest = (slen > *longest) ? slen : *longest; |
| (*cnt)++; |
| |
| if (!found) { |
| *first_idx = idx; |
| } |
| |
| found = true; |
| } |
| idx++; |
| } |
| } |
| |
| static void autocomplete(const struct shell *shell, |
| const struct shell_static_entry *cmd, |
| const char *arg, |
| size_t subcmd_idx) |
| { |
| const struct shell_static_entry *match; |
| size_t arg_len = shell_strlen(arg); |
| size_t cmd_len; |
| |
| /* shell->ctx->active_cmd can be safely used outside of command context |
| * to save stack |
| */ |
| cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, |
| subcmd_idx, &match, &shell->ctx->active_cmd); |
| cmd_len = shell_strlen(match->syntax); |
| |
| /* no exact match found */ |
| if (cmd_len != arg_len) { |
| shell_op_completion_insert(shell, |
| match->syntax + arg_len, |
| cmd_len - arg_len); |
| } |
| |
| /* Next character in the buffer is not 'space'. */ |
| if (!isspace((int) shell->ctx->cmd_buff[ |
| shell->ctx->cmd_buff_pos])) { |
| if (shell->ctx->internal.flags.insert_mode) { |
| shell->ctx->internal.flags.insert_mode = 0; |
| shell_op_char_insert(shell, ' '); |
| shell->ctx->internal.flags.insert_mode = 1; |
| } else { |
| shell_op_char_insert(shell, ' '); |
| } |
| } else { |
| /* case: |
| * | | -> cursor |
| * cons_name $: valid_cmd valid_sub_cmd| |argument <tab> |
| */ |
| shell_op_cursor_move(shell, 1); |
| /* result: |
| * cons_name $: valid_cmd valid_sub_cmd |a|rgument |
| */ |
| } |
| } |
| |
| static size_t shell_str_common(const char *s1, const char *s2, size_t n) |
| { |
| size_t common = 0; |
| |
| while ((n > 0) && (*s1 == *s2) && (*s1 != '\0')) { |
| s1++; |
| s2++; |
| n--; |
| common++; |
| } |
| |
| return common; |
| } |
| |
| static void tab_options_print(const struct shell *shell, |
| const struct shell_static_entry *cmd, |
| const char *str, size_t first, size_t cnt, |
| u16_t longest) |
| { |
| size_t str_len = shell_strlen(str); |
| const struct shell_static_entry *match; |
| size_t idx = first; |
| |
| /* Printing all matching commands (options). */ |
| tab_item_print(shell, SHELL_INIT_OPTION_PRINTER, longest); |
| |
| while (cnt) { |
| /* shell->ctx->active_cmd can be safely used outside of command |
| * context to save stack |
| */ |
| 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)) { |
| continue; |
| } |
| |
| tab_item_print(shell, match->syntax, longest); |
| cnt--; |
| } |
| |
| shell_fprintf(shell, SHELL_INFO, "\r\n%s", shell->prompt); |
| shell_fprintf(shell, SHELL_NORMAL, "%s", shell->ctx->cmd_buff); |
| |
| shell_op_cursor_position_synchronize(shell); |
| } |
| |
| static u16_t common_beginning_find(const struct shell_static_entry *cmd, |
| const char **str, |
| size_t first, size_t cnt) |
| { |
| struct shell_static_entry dynamic_entry; |
| const struct shell_static_entry *match; |
| u16_t common = UINT16_MAX; |
| |
| |
| cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, |
| first, &match, &dynamic_entry); |
| |
| *str = match->syntax; |
| |
| for (size_t idx = first + 1; idx < first + cnt; idx++) { |
| struct shell_static_entry dynamic_entry2; |
| const struct shell_static_entry *match2; |
| int curr_common; |
| |
| cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, |
| idx, &match2, &dynamic_entry2); |
| |
| curr_common = shell_str_common(match->syntax, match2->syntax, |
| UINT16_MAX); |
| common = (curr_common < common) ? curr_common : common; |
| } |
| |
| return common; |
| } |
| |
| static void partial_autocomplete(const struct shell *shell, |
| const struct shell_static_entry *cmd, |
| const char *arg, |
| size_t first, size_t cnt) |
| { |
| const char *completion; |
| u16_t common = common_beginning_find(cmd, &completion, first, cnt); |
| int arg_len = shell_strlen(arg); |
| |
| if (common) { |
| shell_op_completion_insert(shell, &completion[arg_len], |
| common - arg_len); |
| } |
| } |
| |
| static void shell_tab_handle(const struct shell *shell) |
| { |
| /* +1 reserved for NULL in function shell_make_argv */ |
| char *argv[CONFIG_SHELL_ARGC_MAX + 1]; |
| /* d_entry - placeholder for dynamic command */ |
| struct shell_static_entry d_entry; |
| const struct shell_static_entry *cmd; |
| size_t arg_idx; |
| u16_t longest; |
| size_t first; |
| size_t argc; |
| size_t cnt; |
| |
| |
| bool tab_possible = shell_tab_prepare(shell, &cmd, argv, &argc, |
| &arg_idx, &d_entry); |
| |
| if (tab_possible == false) { |
| return; |
| } |
| |
| find_completion_candidates(cmd, argv[arg_idx], &first, &cnt, &longest); |
| |
| if (!cnt) { |
| /* No candidates to propose. */ |
| return; |
| } else if (cnt == 1) { |
| /* Autocompletion.*/ |
| autocomplete(shell, cmd, argv[arg_idx], first); |
| } else { |
| tab_options_print(shell, cmd, argv[arg_idx], first, cnt, |
| longest); |
| partial_autocomplete(shell, cmd, argv[arg_idx], first, cnt); |
| } |
| } |
| |
| #define SHELL_ASCII_MAX_CHAR (127u) |
| static inline int ascii_filter(const char data) |
| { |
| return (u8_t) data > SHELL_ASCII_MAX_CHAR ? -EINVAL : 0; |
| } |
| |
| static void metakeys_handle(const struct shell *shell, char data) |
| { |
| /* Optional feature */ |
| if (!IS_ENABLED(CONFIG_SHELL_METAKEYS)) { |
| return; |
| } |
| |
| switch (data) { |
| case SHELL_VT100_ASCII_CTRL_A: /* CTRL + A */ |
| shell_op_cursor_home_move(shell); |
| break; |
| |
| case SHELL_VT100_ASCII_CTRL_C: /* CTRL + C */ |
| shell_op_cursor_end_move(shell); |
| if (!shell_cursor_in_empty_line(shell)) { |
| cursor_next_line_move(shell); |
| } |
| EXIT_HISTORY_REQUEST(shell); |
| shell_state_set(shell, SHELL_STATE_ACTIVE); |
| break; |
| |
| case SHELL_VT100_ASCII_CTRL_E: /* CTRL + E */ |
| shell_op_cursor_end_move(shell); |
| break; |
| |
| case SHELL_VT100_ASCII_CTRL_L: /* CTRL + L */ |
| SHELL_VT100_CMD(shell, SHELL_VT100_CURSORHOME); |
| SHELL_VT100_CMD(shell, SHELL_VT100_CLEARSCREEN); |
| shell_fprintf(shell, SHELL_INFO, "%s", shell->prompt); |
| if (flag_echo_is_set(shell)) { |
| shell_fprintf(shell, SHELL_NORMAL, "%s", |
| shell->ctx->cmd_buff); |
| shell_op_cursor_position_synchronize(shell); |
| } |
| break; |
| |
| case SHELL_VT100_ASCII_CTRL_U: /* CTRL + U */ |
| shell_op_cursor_home_move(shell); |
| shell_cmd_buffer_clear(shell); |
| EXIT_HISTORY_REQUEST(shell); |
| clear_eos(shell); |
| break; |
| |
| case SHELL_VT100_ASCII_CTRL_W: /* CTRL + W */ |
| shell_op_word_remove(shell); |
| EXIT_HISTORY_REQUEST(shell); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| static void shell_state_collect(const struct shell *shell) |
| { |
| size_t count = 0; |
| char data; |
| |
| while (true) { |
| (void)shell->iface->api->read(shell->iface, &data, |
| sizeof(data), &count); |
| if (count == 0) { |
| return; |
| } |
| |
| if (ascii_filter(data) != 0) { |
| continue; |
| } |
| |
| /* todo pwr_mgmt_feed();*/ |
| |
| switch (shell->ctx->receive_state) { |
| case SHELL_RECEIVE_DEFAULT: |
| if ((data == '\r') || (data == '\n')) { |
| if (!shell->ctx->cmd_buff_len) { |
| history_mode_exit(shell); |
| cursor_next_line_move(shell); |
| } else { |
| /* Command execution */ |
| (void)shell_execute(shell); |
| } |
| |
| shell_state_set(shell, SHELL_STATE_ACTIVE); |
| return; |
| } |
| switch (data) { |
| case SHELL_VT100_ASCII_ESC: /* ESCAPE */ |
| receive_state_change(shell, SHELL_RECEIVE_ESC); |
| break; |
| |
| case '\0': |
| break; |
| |
| case '\t': /* TAB */ |
| if (flag_echo_is_set(shell)) { |
| /* If the Tab key is pressed, "history |
| * mode" must be terminated because |
| * tab and history handlers are sharing |
| * the same array: temp_buff. |
| */ |
| EXIT_HISTORY_REQUEST(shell); |
| shell_tab_handle(shell); |
| } |
| break; |
| |
| case SHELL_VT100_ASCII_BSPACE: /* BACKSPACE */ |
| if (flag_echo_is_set(shell)) { |
| EXIT_HISTORY_REQUEST(shell); |
| shell_op_char_backspace(shell); |
| } |
| break; |
| |
| case SHELL_VT100_ASCII_DEL: /* DELETE */ |
| if (flag_echo_is_set(shell)) { |
| EXIT_HISTORY_REQUEST(shell); |
| if (flag_delete_mode_set(shell)) { |
| shell_op_char_backspace(shell); |
| |
| } else { |
| shell_op_char_delete(shell); |
| } |
| } |
| break; |
| |
| default: |
| if (isprint((int) data)) { |
| EXIT_HISTORY_REQUEST(shell); |
| shell_op_char_insert(shell, data); |
| } else { |
| metakeys_handle(shell, data); |
| } |
| break; |
| } |
| break; |
| |
| case SHELL_RECEIVE_ESC: |
| if (data == '[') { |
| receive_state_change(shell, |
| SHELL_RECEIVE_ESC_SEQ); |
| } else { |
| receive_state_change(shell, |
| SHELL_RECEIVE_DEFAULT); |
| } |
| break; |
| |
| case SHELL_RECEIVE_ESC_SEQ: |
| receive_state_change(shell, SHELL_RECEIVE_DEFAULT); |
| |
| if (!flag_echo_is_set(shell)) { |
| return; |
| } |
| |
| switch (data) { |
| case 'A': /* UP arrow */ |
| history_handle(shell, true); |
| break; |
| |
| case 'B': /* DOWN arrow */ |
| history_handle(shell, false); |
| break; |
| |
| case 'C': /* RIGHT arrow */ |
| shell_op_right_arrow(shell); |
| break; |
| |
| case 'D': /* LEFT arrow */ |
| shell_op_left_arrow(shell); |
| break; |
| |
| case '4': /* END Button in ESC[n~ mode */ |
| receive_state_change(shell, |
| SHELL_RECEIVE_TILDE_EXP); |
| /* fall through */ |
| /* no break */ |
| case 'F': /* END Button in VT100 mode */ |
| shell_op_cursor_end_move(shell); |
| break; |
| |
| case '1': /* HOME Button in ESC[n~ mode */ |
| receive_state_change(shell, |
| SHELL_RECEIVE_TILDE_EXP); |
| /* fall through */ |
| /* no break */ |
| case 'H': /* HOME Button in VT100 mode */ |
| shell_op_cursor_home_move(shell); |
| break; |
| |
| case '2': /* INSERT Button in ESC[n~ mode */ |
| receive_state_change(shell, |
| SHELL_RECEIVE_TILDE_EXP); |
| /* fall through */ |
| /* no break */ |
| case 'L': /* INSERT Button in VT100 mode */ |
| shell->ctx->internal.flags.insert_mode ^= 1; |
| break; |
| |
| case '3':/* DELETE Button in ESC[n~ mode */ |
| receive_state_change(shell, |
| SHELL_RECEIVE_TILDE_EXP); |
| if (flag_echo_is_set(shell)) { |
| shell_op_char_delete(shell); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| break; |
| |
| case SHELL_RECEIVE_TILDE_EXP: |
| receive_state_change(shell, SHELL_RECEIVE_DEFAULT); |
| break; |
| |
| default: |
| receive_state_change(shell, SHELL_RECEIVE_DEFAULT); |
| break; |
| } |
| } |
| } |
| |
| static void cmd_trim(const struct shell *shell) |
| { |
| shell_buffer_trim(shell->ctx->cmd_buff, &shell->ctx->cmd_buff_len); |
| shell->ctx->cmd_buff_pos = shell->ctx->cmd_buff_len; |
| } |
| |
| /* Function returning pointer to root command matching requested syntax. */ |
| static const struct shell_cmd_entry *root_cmd_find(const char *syntax) |
| { |
| const size_t cmd_count = shell_root_cmd_count(); |
| const struct shell_cmd_entry *cmd; |
| |
| for (size_t cmd_idx = 0; cmd_idx < cmd_count; ++cmd_idx) { |
| cmd = shell_root_cmd_get(cmd_idx); |
| if (strcmp(syntax, cmd->u.entry->syntax) == 0) { |
| return cmd; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int exec_cmd(const struct shell *shell, size_t argc, char **argv) |
| { |
| int ret_val = 0; |
| |
| if (shell->ctx->active_cmd.handler == NULL) { |
| if (shell->ctx->active_cmd.help) { |
| shell_help_print(shell, NULL, 0); |
| } else { |
| shell_fprintf(shell, SHELL_ERROR, |
| SHELL_MSG_SPECIFY_SUBCOMMAND); |
| } |
| |
| ret_val = -ENOEXEC; |
| goto clear; |
| } |
| |
| if (shell->ctx->active_cmd.args) { |
| const struct shell_static_args *args; |
| |
| args = shell->ctx->active_cmd.args; |
| |
| if (args->optional > 0) { |
| /* Check if argc is within allowed range */ |
| ret_val = shell_cmd_precheck(shell, |
| ((argc >= args->mandatory) |
| && |
| (argc <= args->mandatory + |
| args->optional)), |
| NULL, 0); |
| } else { |
| /* Perform exact match if there are no optional args */ |
| ret_val = shell_cmd_precheck(shell, |
| (args->mandatory == argc), |
| NULL, 0); |
| } |
| } |
| |
| if (!ret_val) { |
| ret_val = shell->ctx->active_cmd.handler(shell, argc, argv); |
| } |
| |
| clear: |
| help_flag_clear(shell); |
| |
| return ret_val; |
| } |
| |
| |
| /* Function is analyzing the command buffer to find matching commands. Next, it |
| * invokes the last recognized command which has a handler and passes the rest |
| * of command buffer as arguments. |
| */ |
| static int shell_execute(const struct shell *shell) |
| { |
| struct shell_static_entry d_entry; /* Memory for dynamic commands. */ |
| char *argv[CONFIG_SHELL_ARGC_MAX + 1]; /* +1 reserved for NULL */ |
| const struct shell_static_entry *p_static_entry = NULL; |
| 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; |
| |
| shell_op_cursor_end_move(shell); |
| if (!shell_cursor_in_empty_line(shell)) { |
| cursor_next_line_move(shell); |
| } |
| |
| memset(&shell->ctx->active_cmd, 0, sizeof(shell->ctx->active_cmd)); |
| |
| cmd_trim(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); |
| |
| if (!argc) { |
| return -ENOEXEC; |
| } |
| |
| if (quote != 0) { |
| shell_fprintf(shell, SHELL_ERROR, "not terminated: %c\r\n", |
| quote); |
| return -ENOEXEC; |
| } |
| |
| /* Searching for a matching root command. */ |
| p_cmd = root_cmd_find(argv[0]); |
| if (p_cmd == NULL) { |
| shell_fprintf(shell, SHELL_ERROR, "%s%s\r\n", argv[0], |
| SHELL_MSG_COMMAND_NOT_FOUND); |
| return -ENOEXEC; |
| } |
| |
| /* Root command shall be always static. */ |
| __ASSERT_NO_MSG(p_cmd->is_dynamic == false); |
| |
| /* checking if root command has a handler */ |
| shell->ctx->active_cmd = *p_cmd->u.entry; |
| |
| p_cmd = p_cmd->u.entry->subcmd; |
| cmd_lvl++; |
| cmd_idx = 0; |
| |
| /* Below loop is analyzing subcommands of found root command. */ |
| while (true) { |
| if (cmd_lvl >= argc) { |
| break; |
| } |
| |
| if (!strcmp(argv[cmd_lvl], "-h") || |
| !strcmp(argv[cmd_lvl], "--help")) { |
| /* Command called with help option so it makes no sense |
| * to search deeper commands. |
| */ |
| help_flag_set(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)) { |
| break; |
| } |
| |
| 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 -ENOEXEC; |
| } |
| } |
| |
| shell->ctx->active_cmd = *p_static_entry; |
| cmd_with_handler_lvl = cmd_lvl; |
| } |
| |
| cmd_lvl++; |
| cmd_idx = 0; |
| p_cmd = p_static_entry->subcmd; |
| } |
| } |
| |
| 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. */ |
| return exec_cmd(shell, argc - cmd_with_handler_lvl, |
| &argv[cmd_with_handler_lvl]); |
| } |
| |
| static void shell_transport_evt_handler(enum shell_transport_evt evt_type, |
| void *context) |
| { |
| struct shell *shell = (struct shell *)context; |
| struct k_poll_signal *signal; |
| |
| signal = (evt_type == SHELL_TRANSPORT_EVT_RX_RDY) ? |
| &shell->ctx->signals[SHELL_SIGNAL_RXRDY] : |
| &shell->ctx->signals[SHELL_SIGNAL_TXDONE]; |
| k_poll_signal_raise(signal, 0); |
| } |
| |
| static void shell_current_command_erase(const struct shell *shell) |
| { |
| shell_multiline_data_calc(&shell->ctx->vt100_ctx.cons, |
| shell->ctx->cmd_buff_pos, |
| shell->ctx->cmd_buff_len); |
| shell_op_cursor_horiz_move(shell, -shell->ctx->vt100_ctx.cons.cur_x); |
| shell_op_cursor_vert_move(shell, shell->ctx->vt100_ctx.cons.cur_y - 1); |
| |
| clear_eos(shell); |
| } |
| |
| static void shell_current_command_print(const struct shell *shell) |
| { |
| shell_fprintf(shell, SHELL_INFO, "%s", shell->prompt); |
| |
| if (flag_echo_is_set(shell)) { |
| shell_fprintf(shell, SHELL_NORMAL, "%s", shell->ctx->cmd_buff); |
| shell_op_cursor_position_synchronize(shell); |
| } |
| } |
| |
| static void shell_log_process(const struct shell *shell) |
| { |
| bool processed; |
| int signaled; |
| int result; |
| |
| do { |
| shell_current_command_erase(shell); |
| processed = shell_log_backend_process(shell->log_backend); |
| shell_current_command_print(shell); |
| |
| /* Arbitrary delay added to ensure that prompt is readable and |
| * can be used to enter further commands. |
| */ |
| if (shell->ctx->cmd_buff_len) { |
| k_sleep(K_MSEC(15)); |
| } |
| |
| k_poll_signal_check(&shell->ctx->signals[SHELL_SIGNAL_RXRDY], |
| &signaled, &result); |
| |
| } while (processed && !signaled); |
| } |
| |
| static int shell_instance_init(const struct shell *shell, const void *p_config, |
| bool use_colors) |
| { |
| __ASSERT_NO_MSG(shell); |
| __ASSERT_NO_MSG(shell->ctx && shell->iface && shell->prompt); |
| __ASSERT_NO_MSG((shell->shell_flag == SHELL_FLAG_CRLF_DEFAULT) || |
| (shell->shell_flag == SHELL_FLAG_OLF_CRLF)); |
| |
| int err; |
| |
| err = shell->iface->api->init(shell->iface, p_config, |
| shell_transport_evt_handler, |
| (void *) shell); |
| if (err != 0) { |
| return err; |
| } |
| |
| history_init(shell); |
| |
| memset(shell->ctx, 0, sizeof(*shell->ctx)); |
| |
| if (IS_ENABLED(CONFIG_SHELL_BACKSPACE_MODE_DELETE)) { |
| shell->ctx->internal.flags.mode_delete = 1; |
| } |
| |
| if (IS_ENABLED(CONFIG_SHELL_STATS)) { |
| shell->stats->log_lost_cnt = 0; |
| } |
| |
| shell->ctx->internal.flags.tx_rdy = 1; |
| shell->ctx->internal.flags.echo = CONFIG_SHELL_ECHO_STATUS; |
| shell->ctx->state = SHELL_STATE_INITIALIZED; |
| shell->ctx->vt100_ctx.cons.terminal_wid = SHELL_DEFAULT_TERMINAL_WIDTH; |
| shell->ctx->vt100_ctx.cons.terminal_hei = SHELL_DEFAULT_TERMINAL_HEIGHT; |
| shell->ctx->vt100_ctx.cons.name_len = shell_strlen(shell->prompt); |
| shell->ctx->internal.flags.use_colors = |
| IS_ENABLED(CONFIG_SHELL_VT100_COLORS); |
| |
| return 0; |
| } |
| |
| static int shell_instance_uninit(const struct shell *shell); |
| |
| void shell_thread(void *shell_handle, void *arg_log_backend, |
| void *arg_log_level) |
| { |
| struct shell *shell = (struct shell *)shell_handle; |
| bool log_backend = (bool)arg_log_backend; |
| u32_t log_level = (u32_t)arg_log_level; |
| int err; |
| int i; |
| |
| for (i = 0; i < SHELL_SIGNALS; i++) { |
| k_poll_signal_init(&shell->ctx->signals[i]); |
| k_poll_event_init(&shell->ctx->events[i], |
| K_POLL_TYPE_SIGNAL, |
| K_POLL_MODE_NOTIFY_ONLY, |
| &shell->ctx->signals[i]); |
| } |
| |
| err = shell_start(shell); |
| if (err != 0) { |
| return; |
| } |
| |
| if (log_backend && IS_ENABLED(CONFIG_LOG)) { |
| shell_log_backend_enable(shell->log_backend, (void *)shell, |
| log_level); |
| } |
| |
| while (true) { |
| int signaled; |
| int result; |
| |
| err = k_poll(shell->ctx->events, SHELL_SIGNALS, K_FOREVER); |
| (void)err; |
| |
| k_poll_signal_check(&shell->ctx->signals[SHELL_SIGNAL_KILL], |
| &signaled, &result); |
| |
| if (signaled) { |
| k_poll_signal_reset( |
| &shell->ctx->signals[SHELL_SIGNAL_KILL]); |
| (void)shell_instance_uninit(shell); |
| |
| k_thread_abort(k_current_get()); |
| } |
| |
| k_poll_signal_check(&shell->ctx->signals[SHELL_SIGNAL_LOG_MSG], |
| &signaled, &result); |
| |
| if (!signaled) { |
| /* Other signals handled together.*/ |
| k_poll_signal_reset( |
| &shell->ctx->signals[SHELL_SIGNAL_RXRDY]); |
| k_poll_signal_reset( |
| &shell->ctx->signals[SHELL_SIGNAL_TXDONE]); |
| shell_process(shell); |
| } else if (IS_ENABLED(CONFIG_LOG)) { |
| k_poll_signal_reset( |
| &shell->ctx->signals[SHELL_SIGNAL_LOG_MSG]); |
| /* process log msg */ |
| shell_log_process(shell); |
| } |
| } |
| } |
| |
| int shell_init(const struct shell *shell, const void *transport_config, |
| bool use_colors, bool log_backend, u32_t init_log_level) |
| { |
| __ASSERT_NO_MSG(shell); |
| int err; |
| |
| err = shell_instance_init(shell, transport_config, use_colors); |
| if (err != 0) { |
| return err; |
| } |
| |
| k_tid_t tid = k_thread_create(shell->thread, |
| shell->stack, CONFIG_SHELL_STACK_SIZE, |
| shell_thread, (void *)shell, (void *)log_backend, |
| (void *)init_log_level, |
| CONFIG_SHELL_THREAD_PRIO, 0, K_NO_WAIT); |
| |
| k_thread_name_set(tid, "shell"); |
| |
| return 0; |
| } |
| |
| static int shell_instance_uninit(const struct shell *shell) |
| { |
| __ASSERT_NO_MSG(shell); |
| __ASSERT_NO_MSG(shell->ctx && shell->iface && shell->prompt); |
| int err; |
| |
| if (flag_processing_is_set(shell)) { |
| return -EBUSY; |
| } |
| |
| if (IS_ENABLED(CONFIG_LOG)) { |
| /* todo purge log queue */ |
| shell_log_backend_disable(shell->log_backend); |
| } |
| |
| err = shell->iface->api->uninit(shell->iface); |
| if (err != 0) { |
| return err; |
| } |
| |
| history_purge(shell); |
| |
| shell->ctx->state = SHELL_STATE_UNINITIALIZED; |
| |
| return 0; |
| } |
| |
| int shell_uninit(const struct shell *shell) |
| { |
| if (IS_ENABLED(CONFIG_MULTITHREADING)) { |
| /* signal kill message */ |
| (void)k_poll_signal_raise(&shell->ctx->signals[SHELL_SIGNAL_KILL], 0); |
| |
| return 0; |
| } else { |
| return shell_instance_uninit(shell); |
| } |
| } |
| |
| int shell_start(const struct shell *shell) |
| { |
| __ASSERT_NO_MSG(shell); |
| __ASSERT_NO_MSG(shell->ctx && shell->iface && shell->prompt); |
| int err; |
| |
| if (shell->ctx->state != SHELL_STATE_INITIALIZED) { |
| return -ENOTSUP; |
| } |
| |
| err = shell->iface->api->enable(shell->iface, false); |
| if (err != 0) { |
| return err; |
| } |
| |
| if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS)) { |
| vt100_color_set(shell, SHELL_NORMAL); |
| } |
| |
| shell_raw_fprintf(shell->fprintf_ctx, "\r\n\n"); |
| |
| shell_state_set(shell, SHELL_STATE_ACTIVE); |
| |
| return 0; |
| } |
| |
| int shell_stop(const struct shell *shell) |
| { |
| __ASSERT_NO_MSG(shell); |
| |
| if ((shell->ctx->state == SHELL_STATE_INITIALIZED) || |
| (shell->ctx->state == SHELL_STATE_UNINITIALIZED)) { |
| return -ENOTSUP; |
| } |
| |
| shell_state_set(shell, SHELL_STATE_INITIALIZED); |
| |
| return 0; |
| } |
| |
| void shell_process(const struct shell *shell) |
| { |
| __ASSERT_NO_MSG(shell); |
| |
| union shell_internal internal; |
| |
| internal.value = 0; |
| internal.flags.processing = 1; |
| |
| (void)atomic_or((atomic_t *)&shell->ctx->internal.value, |
| internal.value); |
| |
| switch (shell->ctx->state) { |
| case SHELL_STATE_UNINITIALIZED: |
| case SHELL_STATE_INITIALIZED: |
| /* Console initialized but not started. */ |
| break; |
| |
| case SHELL_STATE_ACTIVE: |
| shell_state_collect(shell); |
| break; |
| |
| default: |
| break; |
| } |
| |
| transport_buffer_flush(shell); |
| |
| internal.value = 0xFFFFFFFF; |
| internal.flags.processing = 0; |
| (void)atomic_and((atomic_t *)&shell->ctx->internal.value, |
| internal.value); |
| } |
| |
| /* Function shall be only used by the fprintf module. */ |
| void shell_print_stream(const void *user_ctx, const char *data, |
| size_t data_len) |
| { |
| shell_write((const struct shell *) user_ctx, data, data_len); |
| } |
| |
| void shell_fprintf(const struct shell *shell, enum shell_vt100_color color, |
| const char *p_fmt, ...) |
| { |
| __ASSERT_NO_MSG(shell); |
| |
| va_list args = { 0 }; |
| |
| va_start(args, p_fmt); |
| |
| if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) && |
| shell->ctx->internal.flags.use_colors && |
| (color != shell->ctx->vt100_ctx.col.col)) { |
| struct shell_vt100_colors col; |
| |
| vt100_colors_store(shell, &col); |
| vt100_color_set(shell, color); |
| |
| shell_fprintf_fmt(shell->fprintf_ctx, p_fmt, args); |
| |
| vt100_colors_restore(shell, &col); |
| } else { |
| shell_fprintf_fmt(shell->fprintf_ctx, p_fmt, args); |
| } |
| |
| va_end(args); |
| } |
| |
| /* Function prints a string on terminal screen with requested margin. |
| * It takes care to not divide words. |
| * shell Pointer to shell instance. |
| * p_str Pointer to string to be printed. |
| * terminal_offset Requested left margin. |
| * offset_first_line Add margin to the first printed line. |
| */ |
| static void formatted_text_print(const struct shell *shell, const char *str, |
| size_t terminal_offset, bool offset_first_line) |
| { |
| size_t offset = 0; |
| size_t length; |
| |
| if (str == NULL) { |
| return; |
| } |
| |
| if (offset_first_line) { |
| shell_op_cursor_horiz_move(shell, terminal_offset); |
| } |
| |
| |
| /* Skipping whitespace. */ |
| while (isspace((int) *(str + offset))) { |
| ++offset; |
| } |
| |
| while (true) { |
| size_t idx = 0; |
| |
| length = shell_strlen(str) - offset; |
| |
| if (length <= |
| shell->ctx->vt100_ctx.cons.terminal_wid - terminal_offset) { |
| for (idx = 0; idx < length; idx++) { |
| if (*(str + offset + idx) == '\n') { |
| transport_buffer_flush(shell); |
| shell_write(shell, str + offset, idx); |
| offset += idx + 1; |
| cursor_next_line_move(shell); |
| shell_op_cursor_horiz_move(shell, |
| terminal_offset); |
| break; |
| } |
| } |
| |
| /* String will fit in one line. */ |
| shell_raw_fprintf(shell->fprintf_ctx, str + offset); |
| |
| break; |
| } |
| |
| /* String is longer than terminal line so text needs to |
| * divide in the way to not divide words. |
| */ |
| length = shell->ctx->vt100_ctx.cons.terminal_wid |
| - terminal_offset; |
| |
| while (true) { |
| /* Determining line break. */ |
| if (isspace((int) (*(str + offset + idx)))) { |
| length = idx; |
| if (*(str + offset + idx) == '\n') { |
| break; |
| } |
| } |
| |
| if ((idx + terminal_offset) >= |
| shell->ctx->vt100_ctx.cons.terminal_wid) { |
| /* End of line reached. */ |
| break; |
| } |
| |
| ++idx; |
| } |
| |
| /* Writing one line, fprintf IO buffer must be flushed |
| * before calling shell_write. |
| */ |
| transport_buffer_flush(shell); |
| shell_write(shell, str + offset, length); |
| offset += length; |
| |
| /* Calculating text offset to ensure that next line will |
| * not begin with a space. |
| */ |
| while (isspace((int) (*(str + offset)))) { |
| ++offset; |
| } |
| |
| cursor_next_line_move(shell); |
| shell_op_cursor_horiz_move(shell, terminal_offset); |
| |
| } |
| cursor_next_line_move(shell); |
| } |
| |
| static void help_cmd_print(const struct shell *shell) |
| { |
| static const char cmd_sep[] = " - "; /* commands separator */ |
| |
| u16_t field_width = shell_strlen(shell->ctx->active_cmd.syntax) + |
| shell_strlen(cmd_sep); |
| |
| shell_fprintf(shell, SHELL_NORMAL, "%s%s", |
| shell->ctx->active_cmd.syntax, cmd_sep); |
| |
| formatted_text_print(shell, shell->ctx->active_cmd.help, |
| field_width, false); |
| } |
| |
| static void help_item_print(const struct shell *shell, const char *item_name, |
| u16_t item_name_width, const char *item_help) |
| { |
| static const u8_t tabulator[] = " "; |
| const u16_t offset = 2 * strlen(tabulator) + item_name_width + 1; |
| |
| if (item_name == NULL) { |
| return; |
| } |
| |
| if (!IS_ENABLED(CONFIG_NEWLIB_LIBC) && !IS_ENABLED(CONFIG_ARCH_POSIX)) { |
| /* print option name */ |
| shell_fprintf(shell, SHELL_NORMAL, "%s%-*s%s:", |
| tabulator, |
| item_name_width, item_name, |
| tabulator); |
| } else { |
| u16_t tmp = item_name_width - strlen(item_name); |
| char space = ' '; |
| |
| shell_fprintf(shell, SHELL_NORMAL, "%s%s", tabulator, |
| item_name); |
| for (u16_t i = 0; i < tmp; i++) { |
| shell_write(shell, &space, 1); |
| } |
| shell_fprintf(shell, SHELL_NORMAL, "%s:", tabulator); |
| } |
| |
| if (item_help == NULL) { |
| cursor_next_line_move(shell); |
| return; |
| } |
| /* print option help */ |
| formatted_text_print(shell, item_help, offset, false); |
| } |
| |
| static void help_options_print(const struct shell *shell, |
| const struct shell_getopt_option *opt, |
| size_t opt_cnt) |
| { |
| static const char opt_sep[] = ", "; /* options separator */ |
| static const char help_opt[] = "-h, --help"; |
| u16_t longest_name = shell_strlen(help_opt); |
| |
| shell_fprintf(shell, SHELL_NORMAL, "Options:\r\n"); |
| |
| if ((opt == NULL) || (opt_cnt == 0)) { |
| help_item_print(shell, help_opt, longest_name, |
| "Show command help."); |
| return; |
| } |
| |
| /* Looking for the longest option string. */ |
| for (size_t i = 0; i < opt_cnt; ++i) { |
| u16_t len = shell_strlen(opt[i].optname_short) + |
| shell_strlen(opt[i].optname) + |
| shell_strlen(opt_sep); |
| |
| longest_name = len > longest_name ? len : longest_name; |
| } |
| |
| /* help option is printed first */ |
| help_item_print(shell, help_opt, longest_name, "Show command help."); |
| |
| /* prepare a buffer to compose a string: |
| * option name short - option separator - option name |
| */ |
| memset(shell->ctx->temp_buff, 0, longest_name + 1); |
| |
| /* Formating and printing all available options (except -h, --help). */ |
| for (size_t i = 0; i < opt_cnt; ++i) { |
| if (opt[i].optname_short) { |
| strcpy(shell->ctx->temp_buff, opt[i].optname_short); |
| } |
| if (opt[i].optname) { |
| if (*shell->ctx->temp_buff) { |
| strcat(shell->ctx->temp_buff, opt_sep); |
| strcat(shell->ctx->temp_buff, opt[i].optname); |
| } else { |
| strcpy(shell->ctx->temp_buff, opt[i].optname); |
| } |
| } |
| help_item_print(shell, shell->ctx->temp_buff, longest_name, |
| opt[i].optname_help); |
| } |
| } |
| |
| /* Function is printing command help, its subcommands name and subcommands |
| * help string. |
| */ |
| static void help_subcmd_print(const struct shell *shell) |
| { |
| const struct shell_static_entry *entry = NULL; |
| struct shell_static_entry static_entry; |
| u16_t longest_syntax = 0; |
| size_t cmd_idx = 0; |
| |
| /* Checking if there are any subcommands available. */ |
| if (!shell->ctx->active_cmd.subcmd) { |
| return; |
| } |
| |
| /* Searching for the longest subcommand to print. */ |
| do { |
| cmd_get(shell->ctx->active_cmd.subcmd, !SHELL_CMD_ROOT_LVL, |
| cmd_idx++, &entry, &static_entry); |
| |
| if (!entry) { |
| break; |
| } |
| |
| u16_t len = shell_strlen(entry->syntax); |
| |
| longest_syntax = longest_syntax > len ? longest_syntax : len; |
| } while (cmd_idx != 0); /* too many commands */ |
| |
| if (cmd_idx == 1) { |
| return; |
| } |
| |
| shell_fprintf(shell, SHELL_NORMAL, "Subcommands:\r\n"); |
| |
| /* Printing subcommands and help string (if exists). */ |
| cmd_idx = 0; |
| |
| while (true) { |
| cmd_get(shell->ctx->active_cmd.subcmd, !SHELL_CMD_ROOT_LVL, |
| cmd_idx++, &entry, &static_entry); |
| |
| if (entry == NULL) { |
| break; |
| } |
| |
| help_item_print(shell, entry->syntax, longest_syntax, |
| entry->help); |
| } |
| } |
| |
| void shell_help_print(const struct shell *shell, |
| const struct shell_getopt_option *opt, size_t opt_len) |
| { |
| __ASSERT_NO_MSG(shell); |
| |
| if (!IS_ENABLED(CONFIG_SHELL_HELP)) { |
| return; |
| } |
| |
| help_cmd_print(shell); |
| help_options_print(shell, opt, opt_len); |
| help_subcmd_print(shell); |
| } |
| |
| int shell_prompt_change(const struct shell *shell, char *prompt) |
| { |
| |
| size_t len = shell_strlen(prompt); |
| |
| __ASSERT_NO_MSG(shell); |
| __ASSERT_NO_MSG(prompt); |
| |
| if (len <= CONFIG_SHELL_PROMPT_LENGTH) { |
| memcpy(shell->prompt, prompt, len + 1); /* +1 for '\0' */ |
| return 0; |
| } |
| return -1; |
| } |
| |
| int shell_cmd_precheck(const struct shell *shell, |
| bool arg_cnt_ok, |
| const struct shell_getopt_option *opt, |
| size_t opt_len) |
| { |
| if (shell_help_requested(shell)) { |
| shell_help_print(shell, opt, opt_len); |
| return 1; /* help printed */ |
| } |
| |
| if (!arg_cnt_ok) { |
| shell_fprintf(shell, SHELL_ERROR, |
| "%s: wrong parameter count\n", |
| shell->ctx->active_cmd.syntax); |
| |
| if (IS_ENABLED(CONFIG_SHELL_HELP_ON_WRONG_ARGUMENT_COUNT)) { |
| shell_help_print(shell, opt, opt_len); |
| } |
| |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int shell_execute_cmd(const struct shell *shell, const char *cmd) |
| { |
| u16_t cmd_len = shell_strlen(cmd); |
| |
| if (cmd == NULL) { |
| return -ENOEXEC; |
| } |
| |
| if (cmd_len > (CONFIG_SHELL_CMD_BUFF_SIZE - 1)) { |
| return -ENOEXEC; |
| } |
| |
| if (shell == NULL) { |
| #if CONFIG_SHELL_BACKEND_DUMMY |
| shell = shell_backend_dummy_get_ptr(); |
| #else |
| return -EINVAL; |
| #endif |
| } |
| |
| strcpy(shell->ctx->cmd_buff, cmd); |
| shell->ctx->cmd_buff_len = cmd_len; |
| shell->ctx->cmd_buff_pos = cmd_len; |
| |
| return shell_execute(shell); |
| } |