1 /*
2  * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <sys/param.h>
11 #include "esp_log.h"
12 #include "esp_console.h"
13 #include "esp_system.h"
14 #include "linenoise/linenoise.h"
15 #include "argtable3/argtable3.h"
16 #include "sys/queue.h"
17 
18 #define ANSI_COLOR_DEFAULT      39      /** Default foreground color */
19 
20 typedef struct cmd_item_ {
21     /**
22      * Command name (statically allocated by application)
23      */
24     const char *command;
25     /**
26      * Help text (statically allocated by application), may be NULL.
27      */
28     const char *help;
29     /**
30      * Hint text, usually lists possible arguments, dynamically allocated.
31      * May be NULL.
32      */
33     char *hint;
34     esp_console_cmd_func_t func;    //!< pointer to the command handler
35     void *argtable;                 //!< optional pointer to arg table
36     SLIST_ENTRY(cmd_item_) next;    //!< next command in the list
37 } cmd_item_t;
38 
39 /** linked list of command structures */
40 static SLIST_HEAD(cmd_list_, cmd_item_) s_cmd_list;
41 
42 /** run-time configuration options */
43 static esp_console_config_t s_config;
44 
45 /** temporary buffer used for command line parsing */
46 static char *s_tmp_line_buf;
47 
48 static const cmd_item_t *find_command_by_name(const char *name);
49 
esp_console_init(const esp_console_config_t * config)50 esp_err_t esp_console_init(const esp_console_config_t *config)
51 {
52     if (!config) {
53         return ESP_ERR_INVALID_ARG;
54     }
55     if (s_tmp_line_buf) {
56         return ESP_ERR_INVALID_STATE;
57     }
58     memcpy(&s_config, config, sizeof(s_config));
59     if (s_config.hint_color == 0) {
60         s_config.hint_color = ANSI_COLOR_DEFAULT;
61     }
62     s_tmp_line_buf = calloc(config->max_cmdline_length, 1);
63     if (s_tmp_line_buf == NULL) {
64         return ESP_ERR_NO_MEM;
65     }
66     return ESP_OK;
67 }
68 
esp_console_deinit(void)69 esp_err_t esp_console_deinit(void)
70 {
71     if (!s_tmp_line_buf) {
72         return ESP_ERR_INVALID_STATE;
73     }
74     free(s_tmp_line_buf);
75     s_tmp_line_buf = NULL;
76     cmd_item_t *it, *tmp;
77     SLIST_FOREACH_SAFE(it, &s_cmd_list, next, tmp) {
78         SLIST_REMOVE(&s_cmd_list, it, cmd_item_, next);
79         free(it->hint);
80         free(it);
81     }
82     return ESP_OK;
83 }
84 
esp_console_cmd_register(const esp_console_cmd_t * cmd)85 esp_err_t esp_console_cmd_register(const esp_console_cmd_t *cmd)
86 {
87     cmd_item_t *item = NULL;
88     if (!cmd || cmd->command == NULL) {
89         return ESP_ERR_INVALID_ARG;
90     }
91     if (strchr(cmd->command, ' ') != NULL) {
92         return ESP_ERR_INVALID_ARG;
93     }
94     item = (cmd_item_t *)find_command_by_name(cmd->command);
95     if (!item) {
96         // not registered before
97         item = calloc(1, sizeof(*item));
98         if (item == NULL) {
99             return ESP_ERR_NO_MEM;
100         }
101     } else {
102         // remove from list and free the old hint, because we will alloc new hint for the command
103         SLIST_REMOVE(&s_cmd_list, item, cmd_item_, next);
104         free(item->hint);
105     }
106     item->command = cmd->command;
107     item->help = cmd->help;
108     if (cmd->hint) {
109         /* Prepend a space before the hint. It separates command name and
110          * the hint. arg_print_syntax below adds this space as well.
111          */
112         int unused __attribute__((unused));
113         unused = asprintf(&item->hint, " %s", cmd->hint);
114     } else if (cmd->argtable) {
115         /* Generate hint based on cmd->argtable */
116         char *buf = NULL;
117         size_t buf_size = 0;
118         FILE *f = open_memstream(&buf, &buf_size);
119         if (f != NULL) {
120             arg_print_syntax(f, cmd->argtable, NULL);
121             fclose(f);
122         }
123         item->hint = buf;
124     }
125     item->argtable = cmd->argtable;
126     item->func = cmd->func;
127     cmd_item_t *last = SLIST_FIRST(&s_cmd_list);
128     if (last == NULL) {
129         SLIST_INSERT_HEAD(&s_cmd_list, item, next);
130     } else {
131         cmd_item_t *it;
132         while ((it = SLIST_NEXT(last, next)) != NULL) {
133             last = it;
134         }
135         SLIST_INSERT_AFTER(last, item, next);
136     }
137     return ESP_OK;
138 }
139 
esp_console_get_completion(const char * buf,linenoiseCompletions * lc)140 void esp_console_get_completion(const char *buf, linenoiseCompletions *lc)
141 {
142     size_t len = strlen(buf);
143     if (len == 0) {
144         return;
145     }
146     cmd_item_t *it;
147     SLIST_FOREACH(it, &s_cmd_list, next) {
148         /* Check if command starts with buf */
149         if (strncmp(buf, it->command, len) == 0) {
150             linenoiseAddCompletion(lc, it->command);
151         }
152     }
153 }
154 
esp_console_get_hint(const char * buf,int * color,int * bold)155 const char *esp_console_get_hint(const char *buf, int *color, int *bold)
156 {
157     size_t len = strlen(buf);
158     cmd_item_t *it;
159     SLIST_FOREACH(it, &s_cmd_list, next) {
160         if (strlen(it->command) == len &&
161                 strncmp(buf, it->command, len) == 0) {
162             *color = s_config.hint_color;
163             *bold = s_config.hint_bold;
164             return it->hint;
165         }
166     }
167     return NULL;
168 }
169 
find_command_by_name(const char * name)170 static const cmd_item_t *find_command_by_name(const char *name)
171 {
172     const cmd_item_t *cmd = NULL;
173     cmd_item_t *it;
174     size_t len = strlen(name);
175     SLIST_FOREACH(it, &s_cmd_list, next) {
176         if (strlen(it->command) == len &&
177                 strcmp(name, it->command) == 0) {
178             cmd = it;
179             break;
180         }
181     }
182     return cmd;
183 }
184 
esp_console_run(const char * cmdline,int * cmd_ret)185 esp_err_t esp_console_run(const char *cmdline, int *cmd_ret)
186 {
187     if (s_tmp_line_buf == NULL) {
188         return ESP_ERR_INVALID_STATE;
189     }
190     char **argv = (char **) calloc(s_config.max_cmdline_args, sizeof(char *));
191     if (argv == NULL) {
192         return ESP_ERR_NO_MEM;
193     }
194     strlcpy(s_tmp_line_buf, cmdline, s_config.max_cmdline_length);
195 
196     size_t argc = esp_console_split_argv(s_tmp_line_buf, argv,
197                                          s_config.max_cmdline_args);
198     if (argc == 0) {
199         free(argv);
200         return ESP_ERR_INVALID_ARG;
201     }
202     const cmd_item_t *cmd = find_command_by_name(argv[0]);
203     if (cmd == NULL) {
204         free(argv);
205         return ESP_ERR_NOT_FOUND;
206     }
207     *cmd_ret = (*cmd->func)(argc, argv);
208     free(argv);
209     return ESP_OK;
210 }
211 
help_command(int argc,char ** argv)212 static int help_command(int argc, char **argv)
213 {
214     cmd_item_t *it;
215 
216     /* Print summary of each command */
217     SLIST_FOREACH(it, &s_cmd_list, next) {
218         if (it->help == NULL) {
219             continue;
220         }
221         /* First line: command name and hint
222          * Pad all the hints to the same column
223          */
224         const char *hint = (it->hint) ? it->hint : "";
225         printf("%-s %s\n", it->command, hint);
226         /* Second line: print help.
227          * Argtable has a nice helper function for this which does line
228          * wrapping.
229          */
230         printf("  "); // arg_print_formatted does not indent the first line
231         arg_print_formatted(stdout, 2, 78, it->help);
232         /* Finally, print the list of arguments */
233         if (it->argtable) {
234             arg_print_glossary(stdout, (void **) it->argtable, "  %12s  %s\n");
235         }
236         printf("\n");
237     }
238     return 0;
239 }
240 
esp_console_register_help_command(void)241 esp_err_t esp_console_register_help_command(void)
242 {
243     esp_console_cmd_t command = {
244         .command = "help",
245         .help = "Print the list of registered commands",
246         .func = &help_command
247     };
248     return esp_console_cmd_register(&command);
249 }
250