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 */
64cli_state_t
65cli_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 .index = 1,
74 .argc = argc,
75 .argv = argv,
76 .opts = options_p
77 };
78} /* cli_init */
79
80/**
81 * Use another option list.
82 */
83void
84cli_change_opts (cli_state_t *state_p, /**< state of the command line option processor */
85 const cli_opt_t *options_p) /**< array of option definitions, terminated by CLI_OPT_DEFAULT */
86{
87 state_p->opts = options_p;
88} /* cli_change_opts */
89
90/**
91 * Checks whether the current argument is an option.
92 *
93 * Note:
94 * The state_p->error is not NULL on error and it contains the error message.
95 *
96 * @return the ID of the option that was found or a CLI_OPT_ constant otherwise.
97 */
98int
99cli_consume_option (cli_state_t *state_p) /**< state of the command line option processor */
100{
101 if (state_p->error != NULL)
102 {
103 return CLI_OPT_END;
104 }
105
106 if (state_p->index >= state_p->argc)
107 {
108 state_p->arg = NULL;
109 return CLI_OPT_END;
110 }
111
112 const char *arg = state_p->argv[state_p->index];
113
114 state_p->arg = arg;
115
116 if (arg[0] != '-')
117 {
118 return CLI_OPT_DEFAULT;
119 }
120
121 if (arg[1] == '-')
122 {
123 arg += 2;
124
125 for (const cli_opt_t *opt = state_p->opts; opt->id != CLI_OPT_DEFAULT; opt++)
126 {
127 if (opt->longopt != NULL && strcmp (arg, opt->longopt) == 0)
128 {
129 state_p->index++;
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->index++;
145 return opt->id;
146 }
147 }
148
149 state_p->error = "Unknown option";
150 return CLI_OPT_END;
151} /* cli_consume_option */
152
153/**
154 * Returns the next argument as string.
155 *
156 * Note:
157 * The state_p->error is not NULL on error and it contains the error message.
158 *
159 * @return argument string
160 */
161const char *
162cli_consume_string (cli_state_t *state_p) /**< state of the command line option processor */
163{
164 if (state_p->error != NULL)
165 {
166 return NULL;
167 }
168
169 if (state_p->index >= state_p->argc)
170 {
171 state_p->error = "Expected string argument";
172 state_p->arg = NULL;
173 return NULL;
174 }
175
176 state_p->arg = state_p->argv[state_p->index];
177
178 state_p->index++;
179 return state_p->arg;
180} /* cli_consume_string */
181
182/**
183 * Returns the next argument as integer.
184 *
185 * Note:
186 * The state_p->error is not NULL on error and it contains the error message.
187 *
188 * @return argument integer
189 */
190int
191cli_consume_int (cli_state_t *state_p) /**< state of the command line option processor */
192{
193 if (state_p->error != NULL)
194 {
195 return 0;
196 }
197
198 state_p->error = "Expected integer argument";
199
200 if (state_p->index >= state_p->argc)
201 {
202 state_p->arg = NULL;
203 return 0;
204 }
205
206 state_p->arg = state_p->argv[state_p->index];
207
208 char *endptr;
209 long int value = strtol (state_p->arg, &endptr, 10);
210
211 if (*endptr != '\0')
212 {
213 return 0;
214 }
215
216 state_p->error = NULL;
217 state_p->index++;
218 return (int) value;
219} /* cli_consume_int */
220
221/**
222 * Return next agument as path index.
223 *
224 * @return path index
225 */
226uint32_t
227cli_consume_path (cli_state_t *state_p) /**< state of the command line option processor */
228{
229 if (state_p->error != NULL)
230 {
231 return 0;
232 }
233
234 uint32_t path_index = (uint32_t) state_p->index;
235 cli_consume_string (state_p);
236
237 return path_index;
238} /* cli_consume_path */
239
240/*
241 * Print helper functions
242 */
243
244/**
245 * Pad with spaces.
246 */
247static void
248cli_print_pad (int cnt) /**< number of spaces to print */
249{
250 for (int i = 0; i < cnt; i++)
251 {
252 printf (" ");
253 }
254} /* cli_print_pad */
255
256/**
257 * Print the prefix of a string.
258 */
259static void
260cli_print_prefix (const char *str, /**< string to print */
261 int len) /**< length of the prefix to print */
262{
263 for (int i = 0; i < len; i++)
264 {
265 printf ("%c", *str++);
266 }
267} /* cli_print_prefix */
268
269/**
270 * Print usage summary of options.
271 */
272static void
273cli_opt_usage (const char *prog_name_p, /**< program name, typically argv[0] */
274 const char *command_name_p, /**< command name if available */
275 const cli_opt_t *opts_p) /**< array of command line option definitions, terminated by CLI_OPT_DEFAULT */
276{
277 int length = (int) strlen (prog_name_p);
278 const cli_opt_t *current_opt_p = opts_p;
279
280 printf ("%s", prog_name_p);
281
282 if (command_name_p != NULL)
283 {
284 int command_length = (int) strlen (command_name_p);
285
286 if (length + 1 + command_length > CLI_LINE_LENGTH)
287 {
288 length = CLI_LINE_INDENT - 1;
289 printf ("\n");
290 cli_print_pad (length);
291 }
292
293 printf (" %s", command_name_p);
294 }
295
296 while (current_opt_p->id != CLI_OPT_DEFAULT)
297 {
298 const char *opt_p = current_opt_p->opt;
299 int opt_length = 2 + 1;
300
301 if (opt_p == NULL)
302 {
303 opt_p = current_opt_p->longopt;
304 opt_length++;
305 }
306
307 opt_length += (int) strlen (opt_p);
308
309 if (length + 1 + opt_length >= CLI_LINE_LENGTH)
310 {
311 length = CLI_LINE_INDENT - 1;
312 printf ("\n");
313 cli_print_pad (length);
314 }
315 length += opt_length;
316
317 printf (" [");
318
319 if (current_opt_p->opt != NULL)
320 {
321 printf ("-%s", opt_p);
322 }
323 else
324 {
325 printf ("--%s", opt_p);
326 }
327
328 if (current_opt_p->meta != NULL)
329 {
330 printf (" %s", current_opt_p->meta);
331 }
332
333 printf ("]");
334
335 current_opt_p++;
336 }
337
338 if (current_opt_p->meta != NULL)
339 {
340 const char *opt_p = current_opt_p->meta;
341 int opt_length = (int) (2 + strlen (opt_p));
342
343 if (length + 1 + opt_length >= CLI_LINE_LENGTH)
344 {
345 length = CLI_LINE_INDENT - 1;
346 printf ("\n");
347 cli_print_pad (length);
348 }
349
350 printf (" [%s]", opt_p);
351 }
352
353 printf ("\n\n");
354} /* cli_opt_usage */
355
356/**
357 * Print a help message wrapped into the second column.
358 */
359static void
360cli_print_help (const char *help) /**< the help message to print */
361{
362 while (help != NULL && *help != 0)
363 {
364 int length = -1;
365 int i = 0;
366 for (; i < CLI_LINE_LENGTH - CLI_LINE_TAB && help[i] != 0; i++)
367 {
368 if (help[i] == ' ')
369 {
370 length = i;
371 }
372 }
373 if (length < 0 || i < CLI_LINE_LENGTH - CLI_LINE_TAB)
374 {
375 length = i;
376 }
377
378 cli_print_prefix (help, length);
379
380 help += length;
381 while (*help == ' ')
382 {
383 help++;
384 }
385
386 if (*help != 0)
387 {
388 printf ("\n");
389 cli_print_pad (CLI_LINE_TAB);
390 }
391 }
392} /* cli_print_help */
393
394/**
395 * Print detailed help for options.
396 */
397void
398cli_help (const char *prog_name_p, /**< program name, typically argv[0] */
399 const char *command_name_p, /**< command name if available */
400 const cli_opt_t *options_p) /**< array of command line option definitions, terminated by CLI_OPT_DEFAULT */
401{
402 cli_opt_usage (prog_name_p, command_name_p, options_p);
403
404 const cli_opt_t *opt_p = options_p;
405
406 while (opt_p->id != CLI_OPT_DEFAULT)
407 {
408 int length = CLI_LINE_INDENT;
409 cli_print_pad (CLI_LINE_INDENT);
410
411 if (opt_p->opt != NULL)
412 {
413 printf ("-%s", opt_p->opt);
414 length += (int) (strlen (opt_p->opt) + 1);
415 }
416
417 if (opt_p->opt != NULL && opt_p->longopt != NULL)
418 {
419 printf (", ");
420 length += 2;
421 }
422
423 if (opt_p->longopt != NULL)
424 {
425 printf ("--%s", opt_p->longopt);
426 length += (int) (strlen (opt_p->longopt) + 2);
427 }
428
429 if (opt_p->meta != NULL)
430 {
431 printf (" %s", opt_p->meta);
432 length += 1 + (int) strlen (opt_p->meta);
433 }
434
435 if (opt_p->help != NULL)
436 {
437 if (length >= CLI_LINE_TAB)
438 {
439 printf ("\n");
440 length = 0;
441 }
442 cli_print_pad (CLI_LINE_TAB - length);
443 length = CLI_LINE_TAB;
444
445 cli_print_help (opt_p->help);
446 }
447
448 printf ("\n");
449 opt_p++;
450 }
451
452 if (opt_p->help != NULL)
453 {
454 int length = 0;
455
456 if (opt_p->meta != NULL)
457 {
458 length = (int) (CLI_LINE_INDENT + strlen (opt_p->meta));
459
460 cli_print_pad (CLI_LINE_INDENT);
461 printf ("%s", opt_p->meta);
462 }
463
464 if (length >= CLI_LINE_TAB)
465 {
466 printf ("\n");
467 length = 0;
468 }
469
470 cli_print_pad (CLI_LINE_TAB - length);
471
472 cli_print_help (opt_p->help);
473 printf ("\n");
474 }
475} /* cli_help */
476