/ components / console / commands.c
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  }