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