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