• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright JS Foundation and other contributors, http://js.foundation
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 
16 #include <stdio.h>
17 #include <stdlib.h>
18 
19 #include "cli.h"
20 
21 /*
22  * Fixed layout settings
23  */
24 
25 /**
26  * Wrap lines at:
27  */
28 #define CLI_LINE_LENGTH 80
29 
30 /**
31  * Indent various lines with:
32  */
33 #define CLI_LINE_INDENT 2
34 
35 /**
36  * Tab stop (for multi-column display) at:
37  */
38 #define CLI_LINE_TAB 24
39 
40 /**
41  * Declare a char VLA and concatenate a program name and a sub-command name
42  * (separated by a single space) into the new array. Useful for printing command
43  * line option usage summary for sub-commands.
44  *
45  * @param CMDNAME name of the new array variable.
46  * @param PROGNAME string containing the name of the program.
47  * @param CMD string continaing the name of the sub-command.
48  */
49 #define CLI_CMD_NAME(CMDNAME, PROGNAME, CMD) \
50   char CMDNAME[strlen ((PROGNAME)) + strlen ((CMD)) + 2]; \
51   strncpy (CMDNAME, (PROGNAME), strlen ((PROGNAME))); \
52   CMDNAME[strlen ((PROGNAME))] = ' '; \
53   strncpy (CMDNAME + strlen ((PROGNAME)) + 1, (CMD), strlen ((CMD)) + 1)
54 
55 /*
56  * Command line option handling
57  */
58 
59 /**
60  * Initialize a command line option processor.
61  *
62  * @return the state that should be passed to other cli_ functions.
63  */
64 cli_state_t
cli_init(const cli_opt_t * options_p,int argc,char ** argv)65 cli_init (const cli_opt_t *options_p, /**< array of option definitions, terminated by CLI_OPT_DEFAULT */
66           int argc, /**< number of command line arguments */
67           char **argv) /**< array of command line arguments */
68 {
69   return (cli_state_t)
70   {
71     .error = NULL,
72     .arg = NULL,
73     .argc = argc,
74     .argv = argv,
75     .opts = options_p
76   };
77 } /* cli_init */
78 
79 /**
80  * Use another option list.
81  */
82 void
cli_change_opts(cli_state_t * state_p,const cli_opt_t * options_p)83 cli_change_opts (cli_state_t *state_p, /**< state of the command line option processor */
84                  const cli_opt_t *options_p) /**< array of option definitions, terminated by CLI_OPT_DEFAULT */
85 {
86   state_p->opts = options_p;
87 } /* cli_change_opts */
88 
89 /**
90  * Checks whether the current argument is an option.
91  *
92  * Note:
93  *   The state_p->error is not NULL on error and it contains the error message.
94  *
95  * @return the ID of the option that was found or a CLI_OPT_ constant otherwise.
96  */
97 int
cli_consume_option(cli_state_t * state_p)98 cli_consume_option (cli_state_t *state_p) /**< state of the command line option processor */
99 {
100   if (state_p->error != NULL)
101   {
102     return CLI_OPT_END;
103   }
104 
105   if (state_p->argc <= 0)
106   {
107     state_p->arg = NULL;
108     return CLI_OPT_END;
109   }
110 
111   const char *arg = state_p->argv[0];
112 
113   state_p->arg = arg;
114 
115   if (arg[0] != '-' || arg[1] == '\0')
116   {
117     return CLI_OPT_DEFAULT;
118   }
119 
120   if (arg[1] == '-')
121   {
122     arg += 2;
123 
124     for (const cli_opt_t *opt = state_p->opts; opt->id != CLI_OPT_DEFAULT; opt++)
125     {
126       if (opt->longopt != NULL && strcmp (arg, opt->longopt) == 0)
127       {
128         state_p->argc--;
129         state_p->argv++;
130         return opt->id;
131       }
132     }
133 
134     state_p->error = "Unknown long option";
135     return CLI_OPT_END;
136   }
137 
138   arg++;
139 
140   for (const cli_opt_t *opt = state_p->opts; opt->id != CLI_OPT_DEFAULT; opt++)
141   {
142     if (opt->opt != NULL && strcmp (arg, opt->opt) == 0)
143     {
144       state_p->argc--;
145       state_p->argv++;
146       return opt->id;
147     }
148   }
149 
150   state_p->error = "Unknown option";
151   return CLI_OPT_END;
152 } /* cli_consume_option */
153 
154 /**
155  * Returns the next argument as string.
156  *
157  * Note:
158  *   The state_p->error is not NULL on error and it contains the error message.
159  *
160  * @return argument string
161  */
162 const char *
cli_consume_string(cli_state_t * state_p)163 cli_consume_string (cli_state_t *state_p) /**< state of the command line option processor */
164 {
165   if (state_p->error != NULL)
166   {
167     return NULL;
168   }
169 
170   if (state_p->argc <= 0)
171   {
172     state_p->error = "Expected string argument";
173     state_p->arg = NULL;
174     return NULL;
175   }
176 
177   state_p->arg = state_p->argv[0];
178 
179   state_p->argc--;
180   state_p->argv++;
181   return state_p->arg;
182 } /* cli_consume_string */
183 
184 /**
185  * Returns the next argument as integer.
186  *
187  * Note:
188  *   The state_p->error is not NULL on error and it contains the error message.
189  *
190  * @return argument integer
191  */
192 int
cli_consume_int(cli_state_t * state_p)193 cli_consume_int (cli_state_t *state_p) /**< state of the command line option processor */
194 {
195   if (state_p->error != NULL)
196   {
197     return 0;
198   }
199 
200   state_p->error = "Expected integer argument";
201 
202   if (state_p->argc <= 0)
203   {
204     state_p->arg = NULL;
205     return 0;
206   }
207 
208   state_p->arg = state_p->argv[0];
209 
210   char *endptr;
211   long int value = strtol (state_p->arg, &endptr, 10);
212 
213   if (*endptr != '\0')
214   {
215     return 0;
216   }
217 
218   state_p->error = NULL;
219   state_p->argc--;
220   state_p->argv++;
221   return (int) value;
222 } /* cli_consume_int */
223 
224 /*
225  * Print helper functions
226  */
227 
228 /**
229  * Pad with spaces.
230  */
231 static void
cli_print_pad(int cnt)232 cli_print_pad (int cnt) /**< number of spaces to print */
233 {
234   for (int i = 0; i < cnt; i++)
235   {
236     printf (" ");
237   }
238 } /* cli_print_pad */
239 
240 /**
241  * Print the prefix of a string.
242  */
243 static void
cli_print_prefix(const char * str,int len)244 cli_print_prefix (const char *str, /**< string to print */
245                   int len) /**< length of the prefix to print */
246 {
247   for (int i = 0; i < len; i++)
248   {
249     printf ("%c", *str++);
250   }
251 } /* cli_print_prefix */
252 
253 /**
254  * Print usage summary of options.
255  */
256 static void
cli_opt_usage(const char * prog_name_p,const char * command_name_p,const cli_opt_t * opts_p)257 cli_opt_usage (const char *prog_name_p, /**< program name, typically argv[0] */
258                const char *command_name_p, /**< command name if available */
259                const cli_opt_t *opts_p) /**< array of command line option definitions, terminated by CLI_OPT_DEFAULT */
260 {
261   int length = (int) strlen (prog_name_p);
262   const cli_opt_t *current_opt_p = opts_p;
263 
264   printf ("%s", prog_name_p);
265 
266   if (command_name_p != NULL)
267   {
268     int command_length = (int) strlen (command_name_p);
269 
270     if (length + 1 + command_length > CLI_LINE_LENGTH)
271     {
272       length = CLI_LINE_INDENT - 1;
273       printf ("\n");
274       cli_print_pad (length);
275     }
276 
277     printf (" %s", command_name_p);
278   }
279 
280   while (current_opt_p->id != CLI_OPT_DEFAULT)
281   {
282     const char *opt_p = current_opt_p->opt;
283     int opt_length = 2 + 1;
284 
285     if (opt_p == NULL)
286     {
287       opt_p = current_opt_p->longopt;
288       opt_length++;
289     }
290 
291     opt_length += (int) strlen (opt_p);
292 
293     if (length + 1 + opt_length >= CLI_LINE_LENGTH)
294     {
295       length = CLI_LINE_INDENT - 1;
296       printf ("\n");
297       cli_print_pad (length);
298     }
299     length += opt_length;
300 
301     printf (" [");
302 
303     if (current_opt_p->opt != NULL)
304     {
305       printf ("-%s", opt_p);
306     }
307     else
308     {
309       printf ("--%s", opt_p);
310     }
311 
312     if (current_opt_p->meta != NULL)
313     {
314       printf (" %s", current_opt_p->meta);
315     }
316 
317     printf ("]");
318 
319     current_opt_p++;
320   }
321 
322   if (current_opt_p->meta != NULL)
323   {
324     const char *opt_p = current_opt_p->meta;
325     int opt_length = (int) (2 + strlen (opt_p));
326 
327     if (length + 1 + opt_length >= CLI_LINE_LENGTH)
328     {
329       length = CLI_LINE_INDENT - 1;
330       printf ("\n");
331       cli_print_pad (length);
332     }
333 
334     printf (" [%s]", opt_p);
335   }
336 
337   printf ("\n\n");
338 } /* cli_opt_usage */
339 
340 /**
341  * Print a help message wrapped into the second column.
342  */
343 static void
cli_print_help(const char * help)344 cli_print_help (const char *help) /**< the help message to print */
345 {
346   while (help != NULL && *help != 0)
347   {
348     int length = -1;
349     int i = 0;
350     for (; i < CLI_LINE_LENGTH - CLI_LINE_TAB && help[i] != 0; i++)
351     {
352       if (help[i] == ' ')
353       {
354         length = i;
355       }
356     }
357     if (length < 0 || i < CLI_LINE_LENGTH - CLI_LINE_TAB)
358     {
359       length = i;
360     }
361 
362     cli_print_prefix (help, length);
363 
364     help += length;
365     while (*help == ' ')
366     {
367       help++;
368     }
369 
370     if (*help != 0)
371     {
372       printf ("\n");
373       cli_print_pad (CLI_LINE_TAB);
374     }
375   }
376 } /* cli_print_help */
377 
378 /**
379  * Print detailed help for options.
380  */
381 void
cli_help(const char * prog_name_p,const char * command_name_p,const cli_opt_t * options_p)382 cli_help (const char *prog_name_p, /**< program name, typically argv[0] */
383           const char *command_name_p, /**< command name if available */
384           const cli_opt_t *options_p) /**< array of command line option definitions, terminated by CLI_OPT_DEFAULT */
385 {
386   cli_opt_usage (prog_name_p, command_name_p, options_p);
387 
388   const cli_opt_t *opt_p = options_p;
389 
390   while (opt_p->id != CLI_OPT_DEFAULT)
391   {
392     int length = CLI_LINE_INDENT;
393     cli_print_pad (CLI_LINE_INDENT);
394 
395     if (opt_p->opt != NULL)
396     {
397       printf ("-%s", opt_p->opt);
398       length += (int) (strlen (opt_p->opt) + 1);
399     }
400 
401     if (opt_p->opt != NULL && opt_p->longopt != NULL)
402     {
403       printf (", ");
404       length += 2;
405     }
406 
407     if (opt_p->longopt != NULL)
408     {
409       printf ("--%s", opt_p->longopt);
410       length += (int) (strlen (opt_p->longopt) + 2);
411     }
412 
413     if (opt_p->meta != NULL)
414     {
415       printf (" %s", opt_p->meta);
416       length += 1 + (int) strlen (opt_p->meta);
417     }
418 
419     if (opt_p->help != NULL)
420     {
421       if (length >= CLI_LINE_TAB)
422       {
423         printf ("\n");
424         length = 0;
425       }
426       cli_print_pad (CLI_LINE_TAB - length);
427       length = CLI_LINE_TAB;
428 
429       cli_print_help (opt_p->help);
430     }
431 
432     printf ("\n");
433     opt_p++;
434   }
435 
436   if (opt_p->help != NULL)
437   {
438     int length = 0;
439 
440     if (opt_p->meta != NULL)
441     {
442       length = (int) (CLI_LINE_INDENT + strlen (opt_p->meta));
443 
444       cli_print_pad (CLI_LINE_INDENT);
445       printf ("%s", opt_p->meta);
446     }
447 
448     if (length >= CLI_LINE_TAB)
449     {
450       printf ("\n");
451       length = 0;
452     }
453 
454     cli_print_pad (CLI_LINE_TAB - length);
455 
456     cli_print_help (opt_p->help);
457     printf ("\n");
458   }
459 } /* cli_help */
460