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 .argc = argc, 74 .argv = argv, 75 .opts = options_p 76 }; 77} /* cli_init */ 78 79/** 80 * Use another option list. 81 */ 82void 83cli_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 */ 97int 98cli_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 */ 162const char * 163cli_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 */ 192int 193cli_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 */ 231static void 232cli_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 */ 243static void 244cli_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 */ 256static void 257cli_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 */ 343static void 344cli_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 */ 381void 382cli_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