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 |
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 | .index = 1, |
74 | .argc = argc, |
75 | .argv = argv, |
76 | .opts = options_p |
77 | }; |
78 | } /* cli_init */ |
79 | |
80 | /** |
81 | * Use another option list. |
82 | */ |
83 | void |
84 | cli_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 | */ |
98 | int |
99 | cli_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 | */ |
161 | const char * |
162 | cli_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 | */ |
190 | int |
191 | cli_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 | */ |
226 | uint32_t |
227 | cli_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 | */ |
247 | static void |
248 | cli_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 | */ |
259 | static void |
260 | cli_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 | */ |
272 | static void |
273 | cli_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 | */ |
359 | static void |
360 | cli_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 | */ |
397 | void |
398 | cli_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 | |