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