summaryrefslogtreecommitdiff
path: root/lib/console/commands.c
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-06-27 16:12:18 +1000
committerjacqueline <me@jacqueline.id.au>2024-06-27 16:12:18 +1000
commit1036f1b00efe2bbd2467cbfa3a4a97ab7f56591e (patch)
treecc3aa8683daa30e36f3777699e32aaf93e211f95 /lib/console/commands.c
parent1daa1e9b0fe737ea5dac99f42fdf3c217873443f (diff)
downloadtangara-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.c302
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);
+}