C cli option parsing in a single header file
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

918 lines
22 KiB

  1. #ifndef SIMPLE_OPT_H
  2. #define SIMPLE_OPT_H
  3. #include <stdlib.h>
  4. #include <stdbool.h>
  5. #include <string.h>
  6. #include <stdio.h>
  7. #include <stdint.h>
  8. #include <ctype.h>
  9. #include <errno.h>
  10. /* the maximum number of options that can be passed on the cli */
  11. #ifndef SIMPLE_OPT_MAX_ARGC
  12. #define SIMPLE_OPT_MAX_ARGC 1024
  13. #endif
  14. /* the maximum allowed width for an option passed on the cli */
  15. #ifndef SIMPLE_OPT_OPT_MAX_WIDTH
  16. #define SIMPLE_OPT_OPT_MAX_WIDTH 512
  17. #endif
  18. /* the maximum allowed width for an option's argument passed on the cli */
  19. #ifndef SIMPLE_OPT_OPT_ARG_MAX_WIDTH
  20. #define SIMPLE_OPT_OPT_ARG_MAX_WIDTH 2048
  21. #endif
  22. /* an internal print buffer width for usage printing. you shouldn't have to
  23. * worry about this if you're sane */
  24. #ifndef SIMPLE_OPT_PRINT_BUFFER_WIDTH
  25. #define SIMPLE_OPT_PRINT_BUFFER_WIDTH 2048
  26. #endif
  27. enum simple_opt_type {
  28. SIMPLE_OPT_FLAG,
  29. SIMPLE_OPT_BOOL,
  30. SIMPLE_OPT_INT,
  31. SIMPLE_OPT_UNSIGNED,
  32. SIMPLE_OPT_DOUBLE,
  33. SIMPLE_OPT_CHAR,
  34. SIMPLE_OPT_STRING,
  35. SIMPLE_OPT_STRING_SET,
  36. SIMPLE_OPT_END,
  37. };
  38. struct simple_opt {
  39. enum simple_opt_type type;
  40. const char short_name;
  41. const char *long_name;
  42. bool arg_is_required;
  43. /* optional, a description of the option used for usage printing */
  44. const char *description;
  45. /* optional, a custom string describing the arg, used for usage printing */
  46. const char *custom_arg_string;
  47. /* required for type SIMPLE_OPT_STRING_SET, a NULL-terminated array of
  48. * string possibilities against which an option's argument is matched */
  49. const char **string_set;
  50. /* values assigned upon successful option parse */
  51. bool was_seen;
  52. bool arg_is_stored;
  53. union {
  54. bool v_bool;
  55. long v_int;
  56. unsigned long v_unsigned;
  57. double v_double;
  58. char v_char;
  59. char v_string[SIMPLE_OPT_OPT_ARG_MAX_WIDTH];
  60. int v_string_set_idx;
  61. } val;
  62. };
  63. enum simple_opt_result_type {
  64. SIMPLE_OPT_RESULT_SUCCESS,
  65. SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION,
  66. SIMPLE_OPT_RESULT_BAD_ARG,
  67. SIMPLE_OPT_RESULT_MISSING_ARG,
  68. SIMPLE_OPT_RESULT_OPT_ARG_TOO_LONG,
  69. SIMPLE_OPT_RESULT_TOO_MANY_ARGS,
  70. SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT,
  71. };
  72. struct simple_opt_result {
  73. enum simple_opt_result_type result_type;
  74. enum simple_opt_type option_type;
  75. char option_string[SIMPLE_OPT_OPT_MAX_WIDTH];
  76. char argument_string[SIMPLE_OPT_OPT_ARG_MAX_WIDTH];
  77. struct simple_opt *option;
  78. int argc;
  79. char *argv[SIMPLE_OPT_MAX_ARGC];
  80. };
  81. static struct simple_opt_result simple_opt_parse(int argc, char **argv,
  82. struct simple_opt *options);
  83. static void simple_opt_print_usage(FILE *f, unsigned width,
  84. char *command_name, char *command_options, char *command_summary,
  85. struct simple_opt *options);
  86. static void simple_opt_print_error(FILE *f, unsigned width, char *command_name,
  87. struct simple_opt_result result);
  88. /*
  89. * internal definitions
  90. *
  91. */
  92. static bool sub_simple_opt_parse(struct simple_opt *o, char *s)
  93. {
  94. unsigned i, j;
  95. char *str, *cp;
  96. bool match;
  97. switch (o->type) {
  98. case SIMPLE_OPT_BOOL:
  99. goto loop;
  100. strmatch:
  101. for (j = 0; j < strlen(str); j++) {
  102. if (s[j] == '\0' || tolower(s[j]) != str[j]) {
  103. match = false;
  104. goto strmatch_out;
  105. }
  106. }
  107. match = true;
  108. goto strmatch_out;
  109. loop:
  110. for (i = 0; i < 6; i++) {
  111. switch (i) {
  112. case 0:
  113. str = "true";
  114. goto strmatch;
  115. case 1:
  116. str = "yes";
  117. goto strmatch;
  118. case 2:
  119. str = "on";
  120. goto strmatch;
  121. case 3:
  122. str = "false";
  123. goto strmatch;
  124. case 4:
  125. str = "no";
  126. goto strmatch;
  127. case 5:
  128. str = "off";
  129. goto strmatch;
  130. }
  131. strmatch_out:
  132. if (match) {
  133. if (i < 3)
  134. o->val.v_bool = true;
  135. else
  136. o->val.v_bool = false;
  137. return true;
  138. }
  139. }
  140. return false;
  141. case SIMPLE_OPT_INT:
  142. errno = 0;
  143. o->val.v_int = strtol(s, &cp, 0);
  144. if (cp == s || *cp != '\0' || errno)
  145. return false;
  146. return true;
  147. case SIMPLE_OPT_UNSIGNED:
  148. if (s[0] == '-' || s[0] == '+')
  149. return false;
  150. errno = 0;
  151. o->val.v_unsigned = strtoul(s, &cp, 0);
  152. if (cp == s || *cp != '\0' || errno)
  153. return false;
  154. return true;
  155. case SIMPLE_OPT_DOUBLE:
  156. errno = 0;
  157. o->val.v_double = strtod(s, &cp);
  158. if (cp == s || *cp != '\0' || errno)
  159. return false;
  160. return true;
  161. case SIMPLE_OPT_CHAR:
  162. if (strlen(s) == 2 && s[0] == '\\') {
  163. switch (s[1]) {
  164. case '0':
  165. o->val.v_char = '\0';
  166. break;
  167. case 'a':
  168. o->val.v_char = '\a';
  169. break;
  170. case 'b':
  171. o->val.v_char = '\b';
  172. break;
  173. case 't':
  174. o->val.v_char = '\t';
  175. break;
  176. case 'n':
  177. o->val.v_char = '\n';
  178. break;
  179. case 'v':
  180. o->val.v_char = '\v';
  181. break;
  182. case 'f':
  183. o->val.v_char = '\f';
  184. break;
  185. case 'r':
  186. o->val.v_char = '\r';
  187. break;
  188. case '\\':
  189. o->val.v_char = '\\';
  190. break;
  191. default:
  192. return false;
  193. }
  194. return true;
  195. } else if (strlen(s) != 1) {
  196. return false;
  197. }
  198. o->val.v_char = s[0];
  199. return true;
  200. case SIMPLE_OPT_STRING:
  201. if (strlen(s) + 1 >= SIMPLE_OPT_OPT_ARG_MAX_WIDTH)
  202. return false;
  203. strcpy(o->val.v_string, s);
  204. return true;
  205. case SIMPLE_OPT_STRING_SET:
  206. for (i = 0; o->string_set[i] != NULL; i++) {
  207. if (!strcmp(s, o->string_set[i])) {
  208. o->val.v_string_set_idx = i;
  209. return true;
  210. }
  211. }
  212. return false;
  213. default:
  214. return false;
  215. }
  216. }
  217. static int sub_simple_opt_id(char *s, struct simple_opt *o)
  218. {
  219. int i;
  220. char c;
  221. if (strlen(s) < 2)
  222. return -1;
  223. if (s[1] != '-') {
  224. if (strlen(s) > 2)
  225. return -1;
  226. for (i = 0; o[i].type != SIMPLE_OPT_END; i++) {
  227. if (s[1] == o[i].short_name)
  228. return i;
  229. }
  230. return -1;
  231. }
  232. for (i = 0; o[i].type != SIMPLE_OPT_END; i++) {
  233. if ( o[i].long_name != NULL && !strncmp(s + 2, o[i].long_name, strlen(o[i].long_name)) ) {
  234. c = s[2 + strlen(o[i].long_name)];
  235. if (c == '\0' || c == '=')
  236. return i;
  237. }
  238. }
  239. return -1;
  240. }
  241. static struct simple_opt_result simple_opt_parse(int argc, char **argv,
  242. struct simple_opt *options)
  243. {
  244. int i, j, opt_i;
  245. int arg_end;
  246. char c;
  247. char *s;
  248. struct simple_opt_result r;
  249. /* check for malformed options */
  250. for (i = 0; options[i].type != SIMPLE_OPT_END; i++) {
  251. if ( (options[i].short_name == '\0' && options[i].long_name == NULL)
  252. || (options[i].type == SIMPLE_OPT_FLAG &&
  253. options[i].arg_is_required)
  254. || (options[i].type == SIMPLE_OPT_STRING_SET &&
  255. options[i].string_set == NULL) ) {
  256. r.result_type = SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT;
  257. goto end;
  258. }
  259. }
  260. /* check for duplicate options. can't modify anything so this is going to
  261. * be pretty not-optimised, but ah well */
  262. for (i = 0; options[i].type != SIMPLE_OPT_END; i++) {
  263. for (j = 0; options[j].type != SIMPLE_OPT_END; j++) {
  264. if (i != j && (
  265. ( options[i].short_name != '\0'
  266. && options[j].short_name != '\0'
  267. && options[i].short_name == options[j].short_name)
  268. || ( options[i].long_name != NULL
  269. && options[j].long_name != NULL
  270. && !strcmp(options[i].long_name, options[j].long_name))
  271. )
  272. ) {
  273. r.result_type = SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT;
  274. goto end;
  275. }
  276. }
  277. }
  278. r.argc = 0;
  279. r.result_type = SIMPLE_OPT_RESULT_SUCCESS;
  280. for (i = 1; i < argc; i++) {
  281. /* "following are non-opts" marker */
  282. if ( !strcmp(argv[i], "--") ) {
  283. i++;
  284. break;
  285. }
  286. /* if not an opt, add to r.argv */
  287. if (argv[i][0] != '-') {
  288. if (r.argc + 1 > SIMPLE_OPT_MAX_ARGC) {
  289. r.result_type = SIMPLE_OPT_RESULT_TOO_MANY_ARGS;
  290. goto end;
  291. }
  292. r.argv[r.argc] = argv[i];
  293. r.argc++;
  294. continue;
  295. }
  296. /* unrecognised argument */
  297. if (strlen(argv[i]) < 2) {
  298. r.result_type = SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION;
  299. goto opt_copy_and_return;
  300. }
  301. /* identify this option */
  302. opt_i = sub_simple_opt_id(argv[i], options);
  303. if (opt_i == -1) {
  304. r.result_type = SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION;
  305. goto opt_copy_and_return;
  306. }
  307. options[opt_i].was_seen = true;
  308. if (options[opt_i].type == SIMPLE_OPT_FLAG)
  309. continue;
  310. /* if there's an arg, is it a separate element in argv? or is it passed
  311. * as "--X=arg"? */
  312. if (argv[i][1] == '-')
  313. c = argv[i][2 + strlen(options[opt_i].long_name)];
  314. else
  315. c = '\0';
  316. /* if this option doesn't require an arg and none is to be found,
  317. * just continue */
  318. if (!options[opt_i].arg_is_required && c == '\0') {
  319. if (i + 1 >= argc)
  320. continue;
  321. if (!strcmp(argv[i+1], "--"))
  322. continue;
  323. if (sub_simple_opt_id(argv[i+1], options) != -1)
  324. continue;
  325. }
  326. if (c == '\0') {
  327. if (i + 1 >= argc) {
  328. r.result_type = SIMPLE_OPT_RESULT_MISSING_ARG;
  329. r.option_type = options[opt_i].type;
  330. r.option = options + opt_i;
  331. goto opt_copy_and_return;
  332. }
  333. s = argv[i+1];
  334. } else {
  335. if (argv[i][3 + strlen(options[opt_i].long_name)] == '\0') {
  336. r.result_type = SIMPLE_OPT_RESULT_MISSING_ARG;
  337. r.option_type = options[opt_i].type;
  338. r.option = options + opt_i;
  339. goto opt_copy_and_return;
  340. }
  341. s = argv[i] + 3 + strlen(options[opt_i].long_name);
  342. }
  343. /* is there space for the arg (if this opt wants a string)? */
  344. if (options[opt_i].type == SIMPLE_OPT_STRING
  345. && strlen(s) + 1 >= SIMPLE_OPT_OPT_ARG_MAX_WIDTH) {
  346. r.result_type = SIMPLE_OPT_RESULT_OPT_ARG_TOO_LONG;
  347. r.option_type = options[opt_i].type;
  348. r.option = options + opt_i;
  349. goto opt_copy_and_return;
  350. }
  351. /* try to actually parse the thing */
  352. if (sub_simple_opt_parse(&(options[opt_i]), s) ) {
  353. options[opt_i].arg_is_stored = true;
  354. /* skip forwards in argv if this wasn't an "="-type argument
  355. * passing */
  356. if (i + 1 < argc && s == argv[i+1])
  357. i++;
  358. } else {
  359. r.result_type = SIMPLE_OPT_RESULT_BAD_ARG;
  360. r.option_type = options[opt_i].type;
  361. strncpy(r.argument_string, s, SIMPLE_OPT_OPT_ARG_MAX_WIDTH - 1);
  362. r.argument_string[SIMPLE_OPT_OPT_ARG_MAX_WIDTH - 1] = '\0';
  363. r.option = options + opt_i;
  364. goto opt_copy_and_return;
  365. }
  366. continue;
  367. }
  368. /* copy anything that follows -- into r.argv */
  369. for (; i < argc; i++, r.argc++)
  370. r.argv[r.argc] = argv[i];
  371. end:
  372. return r;
  373. opt_copy_and_return:
  374. for(arg_end = 0; argv[i][arg_end] != '=' && argv[i][arg_end] != '\0';
  375. arg_end++);
  376. strncpy(r.option_string, argv[i], SIMPLE_OPT_OPT_MAX_WIDTH - 1 < arg_end ?
  377. SIMPLE_OPT_OPT_MAX_WIDTH - 1 : arg_end);
  378. r.option_string[SIMPLE_OPT_OPT_MAX_WIDTH - 1 < arg_end ?
  379. SIMPLE_OPT_OPT_MAX_WIDTH -1 : arg_end] = '\0';
  380. goto end;
  381. }
  382. static int sub_simple_opt_wrap_print(FILE *f, unsigned width, unsigned col,
  383. unsigned line_start, const char *s)
  384. {
  385. bool add_newline = false, first_word = true, first_line = true;
  386. unsigned i, j, word_start, word_end;
  387. if (width != 0 && line_start >= width) {
  388. line_start = 0;
  389. add_newline = true;
  390. }
  391. if (width != 0 && col >= width)
  392. add_newline = true;
  393. if (add_newline) {
  394. fprintf(f, "\n");
  395. col = 0;
  396. first_line = false;
  397. }
  398. /* print out the message, trying to wrap at words */
  399. word_start = 0;
  400. while (1) {
  401. /* find the next word */
  402. while ( isspace(s[word_start]) )
  403. word_start++;
  404. /* null appeared before any non-spaces */
  405. if (s[word_start] == '\0')
  406. return col;
  407. word_end = word_start;
  408. while ( (s[word_end] != '\0') && !isspace(s[word_end]) )
  409. word_end++;
  410. /* buffer up to line_start with spaces */
  411. while (col < line_start + 2 * (!first_line && width > 40
  412. && line_start > 5)) {
  413. fprintf(f, " ");
  414. col++;
  415. }
  416. /* if too little space left, wrap */
  417. if (width != 0 && col + (word_end - word_start) + (first_word ? 0 : 1)
  418. > width && first_word == false) {
  419. fprintf(f, "\n");
  420. first_line = false;
  421. /* buffer up to line_start with spaces */
  422. col = 0;
  423. while (col < line_start + 2 * (!first_line && width > 40
  424. && line_start > 5)) {
  425. fprintf(f, " ");
  426. col++;
  427. }
  428. first_word = true;
  429. }
  430. if (first_word == false) {
  431. fprintf(f, " ");
  432. col++;
  433. }
  434. /* if too long, print piecemeal */
  435. if (width != 0 && (line_start + (word_end - word_start) > width
  436. || (first_word && col + (word_end - word_start) > width)) ) {
  437. j = word_start;
  438. while (1) {
  439. for (i = 0; col < width && j < word_end; i++, j++) {
  440. fprintf(f, "%c", s[j]);
  441. col++;
  442. }
  443. if (j == word_end)
  444. break;
  445. col = 0;
  446. fprintf(f, "\n");
  447. first_line = false;
  448. while (col < line_start + 2 * (!first_line && width > 40
  449. && line_start > 5)) {
  450. fprintf(f, " ");
  451. col++;
  452. }
  453. }
  454. /* else just print and move to the next word */
  455. } else {
  456. for (i = 0; i < word_end - word_start; i++)
  457. fprintf(f, "%c", s[word_start + i]);
  458. col += i;
  459. }
  460. word_start = word_end;
  461. first_word = false;
  462. }
  463. return col;
  464. }
  465. static void simple_opt_print_usage(FILE *f, unsigned width,
  466. char *command_name, char *command_options, char *command_summary,
  467. struct simple_opt *options)
  468. {
  469. char print_buffer[SIMPLE_OPT_PRINT_BUFFER_WIDTH];
  470. unsigned i, j, col, print_buffer_offset, desc_line_start;
  471. /* calculate the required line_start for printing descriptions (leaving
  472. * space for the widest existing long-option) */
  473. /* check for space for column 1 (short_name) */
  474. if (5 >= SIMPLE_OPT_PRINT_BUFFER_WIDTH) {
  475. fprintf(f, "simple-opt internal err: print buffer too small\n");
  476. return;
  477. }
  478. /* 4 to start with, leaving space for " -X " */
  479. desc_line_start = 5;
  480. /* check for space for column 2 (long_name / arg type) */
  481. for (i = 0; options[i].type != SIMPLE_OPT_END; i++) {
  482. j = 0;
  483. /* 3 for "--" and "=" */
  484. if (options[i].long_name != NULL)
  485. j += 3 + strlen(options[i].long_name);
  486. /* 2 for optional args, where arg is wrapped in [] */
  487. if (!options[i].arg_is_required && options[i].type != SIMPLE_OPT_FLAG)
  488. j += 2;
  489. /* the width of the arg type string (BOOL, INT, UNSIGNED, STRING etc)
  490. * FLAGs don't take an argument, so 0 */
  491. if (options[i].type != SIMPLE_OPT_FLAG) {
  492. /* if there's a custom string, use that width. else, use one of the
  493. * default widths */
  494. if (options[i].custom_arg_string != NULL) {
  495. j += strlen(options[i].custom_arg_string);
  496. } else {
  497. switch (options[i].type) {
  498. case SIMPLE_OPT_BOOL:
  499. j += 4;
  500. break;
  501. case SIMPLE_OPT_INT:
  502. j += 3;
  503. break;
  504. case SIMPLE_OPT_UNSIGNED:
  505. j += 8;
  506. break;
  507. case SIMPLE_OPT_DOUBLE:
  508. j += 6;
  509. break;
  510. case SIMPLE_OPT_CHAR:
  511. j += 4;
  512. break;
  513. case SIMPLE_OPT_STRING:
  514. case SIMPLE_OPT_STRING_SET:
  515. j += 6;
  516. break;
  517. default:
  518. break;
  519. }
  520. }
  521. }
  522. /* 5 for leading " -X ", 2 for trailing " " */
  523. if (desc_line_start < j + 5 + 2)
  524. desc_line_start = j + 5 + 2;
  525. }
  526. /* check for space for long_name printing */
  527. if (desc_line_start - 5 - 2 >= SIMPLE_OPT_PRINT_BUFFER_WIDTH) {
  528. fprintf(f, "simple-opt internal err: usage print buffer too small\n");
  529. return;
  530. }
  531. /* if the desc_line_start is so far over it threatens readability, move it
  532. * back a bit and just let the offending longer args be offset */
  533. if (desc_line_start > (width / 2 < 30 ? width / 2 : 30))
  534. desc_line_start = (width / 2 < 30 ? width / 2 : 30);
  535. /*
  536. * printing
  537. *
  538. */
  539. /* print "Usage: <exec> <options> */
  540. if (command_name != NULL && command_options != NULL) {
  541. col = sub_simple_opt_wrap_print(f, width, 0, 0, "Usage:");
  542. col = sub_simple_opt_wrap_print(f, width, col, 7, command_name);
  543. if (command_options != NULL)
  544. sub_simple_opt_wrap_print(f, width, col,
  545. 7 + strlen(command_name) + 1, command_options);
  546. fprintf(f, "\n\n");
  547. }
  548. /* print summary line */
  549. if (command_summary != NULL) {
  550. sub_simple_opt_wrap_print(f, width, 0, 2, command_summary);
  551. fprintf(f, "\n\n");
  552. }
  553. /* print option list */
  554. for (i = 0; options[i].type != SIMPLE_OPT_END; i++) {
  555. /* print column 1 (short name) */
  556. if (options[i].short_name != '\0') {
  557. if (sprintf(print_buffer, "-%c", options[i].short_name) < 0) {
  558. fprintf(f, "\nsimple-opt internal err: encoding error printing"
  559. "option %i\n", i);
  560. return;
  561. }
  562. } else {
  563. sprintf(print_buffer, "%c", '\0');
  564. }
  565. col = sub_simple_opt_wrap_print(f, width, 0, 2, print_buffer);
  566. /* print column 2 (long_name and type) */
  567. sprintf(print_buffer, "%c", '\0');
  568. print_buffer_offset = 0;
  569. if (options[i].long_name != NULL) {
  570. sprintf(print_buffer, "--");
  571. print_buffer_offset = 2;
  572. if (sprintf(print_buffer + print_buffer_offset, "%s",
  573. options[i].long_name) < 0) {
  574. fprintf(f, "\nsimple-opt internal err: encoding error printing"
  575. "option %i\n", i);
  576. return;
  577. }
  578. print_buffer_offset += strlen(options[i].long_name);
  579. }
  580. if (!options[i].arg_is_required && options[i].type !=
  581. SIMPLE_OPT_FLAG) {
  582. sprintf(print_buffer + print_buffer_offset, "[");
  583. print_buffer_offset++;
  584. }
  585. if (options[i].long_name != NULL && options[i].type != SIMPLE_OPT_FLAG) {
  586. sprintf(print_buffer + print_buffer_offset, "=");
  587. print_buffer_offset++;
  588. }
  589. if (options[i].type != SIMPLE_OPT_FLAG) {
  590. if (options[i].custom_arg_string != NULL) {
  591. sprintf(print_buffer + print_buffer_offset,
  592. "%s", options[i].custom_arg_string);
  593. print_buffer_offset += strlen(options[i].custom_arg_string);
  594. } else {
  595. switch (options[i].type) {
  596. case SIMPLE_OPT_BOOL:
  597. sprintf(print_buffer + print_buffer_offset, "BOOL");
  598. print_buffer_offset += 4;
  599. break;
  600. case SIMPLE_OPT_INT:
  601. sprintf(print_buffer + print_buffer_offset, "INT");
  602. print_buffer_offset += 3;
  603. break;
  604. case SIMPLE_OPT_UNSIGNED:
  605. sprintf(print_buffer + print_buffer_offset, "UNSIGNED");
  606. print_buffer_offset += 8;
  607. break;
  608. case SIMPLE_OPT_DOUBLE:
  609. sprintf(print_buffer + print_buffer_offset, "DOUBLE");
  610. print_buffer_offset += 6;
  611. break;
  612. case SIMPLE_OPT_CHAR:
  613. sprintf(print_buffer + print_buffer_offset, "CHAR");
  614. print_buffer_offset += 4;
  615. break;
  616. case SIMPLE_OPT_STRING:
  617. case SIMPLE_OPT_STRING_SET:
  618. sprintf(print_buffer + print_buffer_offset, "STRING");
  619. print_buffer_offset += 6;
  620. break;
  621. default:
  622. break;
  623. }
  624. }
  625. }
  626. if (!options[i].arg_is_required && options[i].type !=
  627. SIMPLE_OPT_FLAG)
  628. sprintf(print_buffer + print_buffer_offset, "]");
  629. /* 5 for " -X --" */
  630. col = sub_simple_opt_wrap_print(f, width, col, 5, print_buffer);
  631. /* print option description */
  632. if (options[i].description != NULL) {
  633. if (col < width) {
  634. fprintf(f, " ");
  635. col++;
  636. }
  637. if (col < width) {
  638. fprintf(f, " ");
  639. col++;
  640. }
  641. sub_simple_opt_wrap_print(f, width, col, desc_line_start,
  642. options[i].description);
  643. }
  644. /* end of this option */
  645. fprintf(f, "\n");
  646. }
  647. }
  648. static void simple_opt_print_error(FILE *f, unsigned width, char *command_name,
  649. struct simple_opt_result result)
  650. {
  651. char print_buffer[SIMPLE_OPT_PRINT_BUFFER_WIDTH];
  652. unsigned i, line_start, col;
  653. int rval;
  654. /* just easier to write */
  655. const size_t size = SIMPLE_OPT_PRINT_BUFFER_WIDTH;
  656. if (result.result_type == SIMPLE_OPT_RESULT_SUCCESS)
  657. return;
  658. if (command_name != NULL)
  659. rval = snprintf(print_buffer, size, "%s:", command_name);
  660. else
  661. rval = snprintf(print_buffer, size, "err:");
  662. if (rval < 0 || (unsigned)rval >= size) {
  663. fprintf(f, "simple-opt internal err: print buffer too small\n");
  664. return;
  665. }
  666. col = sub_simple_opt_wrap_print(f, width, 0, 0, print_buffer);
  667. line_start = strlen(print_buffer) + 1;
  668. switch (result.result_type) {
  669. case SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION:
  670. rval = snprintf(print_buffer, size, "unrecognised option `%s`",
  671. result.option_string);
  672. break;
  673. case SIMPLE_OPT_RESULT_BAD_ARG:
  674. rval = snprintf(print_buffer, size,
  675. "bad argument `%s` passed to option `%s`",
  676. result.argument_string, result.option_string);
  677. if (rval < 0 || (unsigned)rval >= size) {
  678. fprintf(f, "simple-opt internal err: print buffer too small\n");
  679. return;
  680. }
  681. sub_simple_opt_wrap_print(f, width, col, line_start,
  682. print_buffer);
  683. fprintf(f, "\n");
  684. col = 0;
  685. switch (result.option_type) {
  686. case SIMPLE_OPT_BOOL:
  687. rval = snprintf(print_buffer, size,
  688. "expected a boolean: (yes|true|on) or (no|false|off)");
  689. break;
  690. case SIMPLE_OPT_INT:
  691. rval = snprintf(print_buffer, size,"expected integer value");
  692. break;
  693. case SIMPLE_OPT_UNSIGNED:
  694. rval = snprintf(print_buffer, size,
  695. "expected unsigned integer value");
  696. break;
  697. case SIMPLE_OPT_DOUBLE:
  698. rval = snprintf(print_buffer, size,
  699. "expected floating-point value");
  700. break;
  701. case SIMPLE_OPT_CHAR:
  702. rval = snprintf(print_buffer, size,
  703. "expected single character");
  704. break;
  705. case SIMPLE_OPT_STRING:
  706. rval = snprintf(print_buffer, size,
  707. "expected a string");
  708. break;
  709. case SIMPLE_OPT_STRING_SET:
  710. for (i = 0; result.option->string_set[i] != NULL; i++);
  711. if (i == 1) {
  712. rval = snprintf(print_buffer, size, "expected \"%s\"",
  713. result.option->string_set[0]);
  714. } else if (i == 2) {
  715. rval = snprintf(print_buffer, size,
  716. "expected \"%s\" or \"%s\"",
  717. result.option->string_set[0],
  718. result.option->string_set[1]);
  719. } else if (i == 3) {
  720. rval = snprintf(print_buffer, size,
  721. "expected \"%s\", \"%s\" or \"%s\"",
  722. result.option->string_set[0],
  723. result.option->string_set[1],
  724. result.option->string_set[2]);
  725. } else if (i == 4) {
  726. rval = snprintf(print_buffer, size,
  727. "expected \"%s\", \"%s\", \"%s\", or \"%s\"",
  728. result.option->string_set[0],
  729. result.option->string_set[1],
  730. result.option->string_set[2],
  731. result.option->string_set[3]);
  732. } else {
  733. rval = snprintf(print_buffer, size,
  734. "expected one of %u possibile strings", i);
  735. }
  736. default:
  737. break;
  738. }
  739. break;
  740. case SIMPLE_OPT_RESULT_MISSING_ARG:
  741. rval = snprintf(print_buffer, size,
  742. "argument expected for option `%s`",
  743. result.option_string);
  744. break;
  745. case SIMPLE_OPT_RESULT_OPT_ARG_TOO_LONG:
  746. rval = snprintf(print_buffer, size,
  747. "argument passed to option `%s` is too long",
  748. result.option_string);
  749. break;
  750. case SIMPLE_OPT_RESULT_TOO_MANY_ARGS:
  751. rval = snprintf(print_buffer, size,
  752. "too many cli arguments received");
  753. break;
  754. case SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT:
  755. rval = snprintf(print_buffer, size,
  756. "malformed option struct (internal err)");
  757. break;
  758. default:
  759. break;
  760. }
  761. if (rval < 0 || (unsigned)rval >= size) {
  762. fprintf(f, "simple-opt internal err: print buffer too small");
  763. return;
  764. }
  765. sub_simple_opt_wrap_print(f, width, col, line_start, print_buffer);
  766. fprintf(f, "\n");
  767. }
  768. #endif