diff options
| author | jacqueline <me@jacqueline.id.au> | 2024-06-27 16:12:18 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2024-06-27 16:12:18 +1000 |
| commit | 1036f1b00efe2bbd2467cbfa3a4a97ab7f56591e (patch) | |
| tree | cc3aa8683daa30e36f3777699e32aaf93e211f95 /lib/console/commands.c | |
| parent | 1daa1e9b0fe737ea5dac99f42fdf3c217873443f (diff) | |
| download | tangara-fw-1036f1b00efe2bbd2467cbfa3a4a97ab7f56591e.tar.gz | |
fork esp-idf's dev console, in order to add some hooks
Diffstat (limited to 'lib/console/commands.c')
| -rw-r--r-- | lib/console/commands.c | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/lib/console/commands.c b/lib/console/commands.c new file mode 100644 index 00000000..5e63db87 --- /dev/null +++ b/lib/console/commands.c @@ -0,0 +1,302 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/param.h> +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_console.h" +#include "esp_system.h" +#include "linenoise/linenoise.h" +#include "argtable3/argtable3.h" +#include "sys/queue.h" + +#define ANSI_COLOR_DEFAULT 39 /** Default foreground color */ + +typedef struct cmd_item_ { + /** + * Command name (statically allocated by application) + */ + const char *command; + /** + * Help text (statically allocated by application), may be NULL. + */ + const char *help; + /** + * Hint text, usually lists possible arguments, dynamically allocated. + * May be NULL. + */ + char *hint; + esp_console_cmd_func_t func; //!< pointer to the command handler + void *argtable; //!< optional pointer to arg table + SLIST_ENTRY(cmd_item_) next; //!< next command in the list +} cmd_item_t; + +/** linked list of command structures */ +static SLIST_HEAD(cmd_list_, cmd_item_) s_cmd_list; + +/** run-time configuration options */ +static esp_console_config_t s_config = { + .heap_alloc_caps = MALLOC_CAP_DEFAULT +}; + +/** temporary buffer used for command line parsing */ +static char *s_tmp_line_buf; + +static const cmd_item_t *find_command_by_name(const char *name); + +esp_err_t esp_console_init(const esp_console_config_t *config) +{ + if (!config) { + return ESP_ERR_INVALID_ARG; + } + if (s_tmp_line_buf) { + return ESP_ERR_INVALID_STATE; + } + memcpy(&s_config, config, sizeof(s_config)); + if (s_config.hint_color == 0) { + s_config.hint_color = ANSI_COLOR_DEFAULT; + } + if (s_config.heap_alloc_caps == 0) { + s_config.heap_alloc_caps = MALLOC_CAP_DEFAULT; + } + s_tmp_line_buf = heap_caps_calloc(1, config->max_cmdline_length, s_config.heap_alloc_caps); + if (s_tmp_line_buf == NULL) { + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t esp_console_deinit(void) +{ + if (!s_tmp_line_buf) { + return ESP_ERR_INVALID_STATE; + } + free(s_tmp_line_buf); + s_tmp_line_buf = NULL; + cmd_item_t *it, *tmp; + SLIST_FOREACH_SAFE(it, &s_cmd_list, next, tmp) { + SLIST_REMOVE(&s_cmd_list, it, cmd_item_, next); + free(it->hint); + free(it); + } + return ESP_OK; +} + +esp_err_t esp_console_cmd_register(const esp_console_cmd_t *cmd) +{ + cmd_item_t *item = NULL; + if (!cmd || cmd->command == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (strchr(cmd->command, ' ') != NULL) { + return ESP_ERR_INVALID_ARG; + } + item = (cmd_item_t *)find_command_by_name(cmd->command); + if (!item) { + // not registered before + item = heap_caps_calloc(1, sizeof(*item), s_config.heap_alloc_caps); + if (item == NULL) { + return ESP_ERR_NO_MEM; + } + } else { + // remove from list and free the old hint, because we will alloc new hint for the command + SLIST_REMOVE(&s_cmd_list, item, cmd_item_, next); + free(item->hint); + } + item->command = cmd->command; + item->help = cmd->help; + if (cmd->hint) { + /* Prepend a space before the hint. It separates command name and + * the hint. arg_print_syntax below adds this space as well. + */ + int unused __attribute__((unused)); + unused = asprintf(&item->hint, " %s", cmd->hint); + } else if (cmd->argtable) { + /* Generate hint based on cmd->argtable */ + char *buf = NULL; + size_t buf_size = 0; + FILE *f = open_memstream(&buf, &buf_size); + if (f != NULL) { + arg_print_syntax(f, cmd->argtable, NULL); + fclose(f); + } + item->hint = buf; + } + item->argtable = cmd->argtable; + item->func = cmd->func; + cmd_item_t *last = SLIST_FIRST(&s_cmd_list); + if (last == NULL) { + SLIST_INSERT_HEAD(&s_cmd_list, item, next); + } else { + cmd_item_t *it; + while ((it = SLIST_NEXT(last, next)) != NULL) { + last = it; + } + SLIST_INSERT_AFTER(last, item, next); + } + return ESP_OK; +} + +void esp_console_get_completion(const char *buf, linenoiseCompletions *lc) +{ + size_t len = strlen(buf); + if (len == 0) { + return; + } + cmd_item_t *it; + SLIST_FOREACH(it, &s_cmd_list, next) { + /* Check if command starts with buf */ + if (strncmp(buf, it->command, len) == 0) { + linenoiseAddCompletion(lc, it->command); + } + } +} + +const char *esp_console_get_hint(const char *buf, int *color, int *bold) +{ + size_t len = strlen(buf); + cmd_item_t *it; + SLIST_FOREACH(it, &s_cmd_list, next) { + if (strlen(it->command) == len && + strncmp(buf, it->command, len) == 0) { + *color = s_config.hint_color; + *bold = s_config.hint_bold; + return it->hint; + } + } + return NULL; +} + +static const cmd_item_t *find_command_by_name(const char *name) +{ + const cmd_item_t *cmd = NULL; + cmd_item_t *it; + size_t len = strlen(name); + SLIST_FOREACH(it, &s_cmd_list, next) { + if (strlen(it->command) == len && + strcmp(name, it->command) == 0) { + cmd = it; + break; + } + } + return cmd; +} + +esp_err_t esp_console_run(const char *cmdline, int *cmd_ret) +{ + if (s_tmp_line_buf == NULL) { + return ESP_ERR_INVALID_STATE; + } + char **argv = (char **) heap_caps_calloc(s_config.max_cmdline_args, sizeof(char *), s_config.heap_alloc_caps); + if (argv == NULL) { + return ESP_ERR_NO_MEM; + } + strlcpy(s_tmp_line_buf, cmdline, s_config.max_cmdline_length); + + size_t argc = esp_console_split_argv(s_tmp_line_buf, argv, + s_config.max_cmdline_args); + if (argc == 0) { + free(argv); + return ESP_ERR_INVALID_ARG; + } + const cmd_item_t *cmd = find_command_by_name(argv[0]); + if (cmd == NULL) { + free(argv); + return ESP_ERR_NOT_FOUND; + } + *cmd_ret = (*cmd->func)(argc, argv); + free(argv); + return ESP_OK; +} + +static struct { + struct arg_str *help_cmd; + struct arg_end *end; +} help_args; + +static void print_arg_help(cmd_item_t *it) +{ + /* First line: command name and hint + * Pad all the hints to the same column + */ + const char *hint = (it->hint) ? it->hint : ""; + printf("%-s %s\n", it->command, hint); + /* Second line: print help. + * Argtable has a nice helper function for this which does line + * wrapping. + */ + printf(" "); // arg_print_formatted does not indent the first line + arg_print_formatted(stdout, 2, 78, it->help); + /* Finally, print the list of arguments */ + if (it->argtable) { + arg_print_glossary(stdout, (void **) it->argtable, " %12s %s\n"); + } + printf("\n"); +} + +static int help_command(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &help_args); + + if (nerrors != 0) { + arg_print_errors(stderr, help_args.end, argv[0]); + return 1; + } + + cmd_item_t *it; + int ret_value = 1; + + if (help_args.help_cmd->count == 0) { + /* Print summary of each command */ + SLIST_FOREACH(it, &s_cmd_list, next) { + if (it->help == NULL) { + continue; + } + print_arg_help(it); + } + ret_value = 0; + } else { + /* Print summary of given command */ + bool found_command = false; + SLIST_FOREACH(it, &s_cmd_list, next) { + if (it->help == NULL) { + continue; + } + if (strcmp(help_args.help_cmd->sval[0], it->command) == 0) { + print_arg_help(it); + found_command = true; + ret_value = 0; + break; + } + } + + /* If given command has not been found, print error message*/ + if (!found_command) { + printf("help: Unrecognized option '%s'. Please use correct command as argument " + "or type 'help' only to print help for all commands\n", help_args.help_cmd->sval[0]); + } + } + + return ret_value; +} + +esp_err_t esp_console_register_help_command(void) +{ + help_args.help_cmd = arg_str0(NULL, NULL, "<string>", "Name of command"); + help_args.end = arg_end(1); + + esp_console_cmd_t command = { + .command = "help", + .help = "Print the summary of all registered commands if no arguments " + "are given, otherwise print summary of given command.", + .func = &help_command, + .argtable = &help_args + }; + return esp_console_cmd_register(&command); +} |
