cmdline.c 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /*
  2. * C Reusables
  3. * <http://github.com/mity/c-reusables>
  4. *
  5. * Copyright (c) 2017-2020 Martin Mitas
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining a
  8. * copy of this software and associated documentation files (the "Software"),
  9. * to deal in the Software without restriction, including without limitation
  10. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  11. * and/or sell copies of the Software, and to permit persons to whom the
  12. * Software is furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in
  15. * all copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  18. * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  23. * IN THE SOFTWARE.
  24. */
  25. #include "cmdline.h"
  26. #include <stdio.h>
  27. #include <string.h>
  28. #ifdef _WIN32
  29. #define snprintf _snprintf
  30. #endif
  31. #define CMDLINE_AUXBUF_SIZE 32
  32. static int
  33. cmdline_handle_short_opt_group(const CMDLINE_OPTION* options, const char* arggroup,
  34. int (*callback)(int /*optval*/, const char* /*arg*/, void* /*userdata*/),
  35. void* userdata)
  36. {
  37. const CMDLINE_OPTION* opt;
  38. int i;
  39. int ret = 0;
  40. for(i = 0; arggroup[i] != '\0'; i++) {
  41. for(opt = options; opt->id != 0; opt++) {
  42. if(arggroup[i] == opt->shortname)
  43. break;
  44. }
  45. if(opt->id != 0 && !(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG)) {
  46. ret = callback(opt->id, NULL, userdata);
  47. } else {
  48. /* Unknown option. */
  49. char badoptname[3];
  50. badoptname[0] = '-';
  51. badoptname[1] = arggroup[i];
  52. badoptname[2] = '\0';
  53. ret = callback((opt->id != 0 ? CMDLINE_OPTID_MISSINGARG : CMDLINE_OPTID_UNKNOWN),
  54. badoptname, userdata);
  55. }
  56. if(ret != 0)
  57. break;
  58. }
  59. return ret;
  60. }
  61. int
  62. cmdline_read(const CMDLINE_OPTION* options, int argc, char** argv,
  63. int (*callback)(int /*optval*/, const char* /*arg*/, void* /*userdata*/),
  64. void* userdata)
  65. {
  66. const CMDLINE_OPTION* opt;
  67. char auxbuf[CMDLINE_AUXBUF_SIZE+1];
  68. int fast_optarg_decision = 1;
  69. int after_doubledash = 0;
  70. int i = 1;
  71. int ret = 0;
  72. auxbuf[CMDLINE_AUXBUF_SIZE] = '\0';
  73. /* Check whether there is any CMDLINE_OPTFLAG_COMPILERLIKE option with
  74. * a name not starting with '-'. That would imply we can to check for
  75. * non-option arguments only after refusing all such options. */
  76. for(opt = options; opt->id != 0; opt++) {
  77. if((opt->flags & CMDLINE_OPTFLAG_COMPILERLIKE) && opt->longname[0] != '-')
  78. fast_optarg_decision = 0;
  79. }
  80. while(i < argc) {
  81. if(after_doubledash || strcmp(argv[i], "-") == 0) {
  82. /* Non-option argument.
  83. * Standalone "-" usually means "read from stdin" or "write to
  84. * stdout" so treat it always as a non-option. */
  85. ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata);
  86. } else if(strcmp(argv[i], "--") == 0) {
  87. /* End of options. All the remaining tokens are non-options
  88. * even if they start with a dash. */
  89. after_doubledash = 1;
  90. } else if(fast_optarg_decision && argv[i][0] != '-') {
  91. /* Non-option argument. */
  92. ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata);
  93. } else {
  94. for(opt = options; opt->id != 0; opt++) {
  95. if(opt->flags & CMDLINE_OPTFLAG_COMPILERLIKE) {
  96. size_t len = strlen(opt->longname);
  97. if(strncmp(argv[i], opt->longname, len) == 0) {
  98. /* Compiler-like option. */
  99. if(argv[i][len] != '\0')
  100. ret = callback(opt->id, argv[i] + len, userdata);
  101. else if(i+1 < argc)
  102. ret = callback(opt->id, argv[++i], userdata);
  103. else
  104. ret = callback(CMDLINE_OPTID_MISSINGARG, opt->longname, userdata);
  105. break;
  106. }
  107. } else if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) {
  108. size_t len = strlen(opt->longname);
  109. if(strncmp(argv[i]+2, opt->longname, len) == 0) {
  110. /* Regular long option. */
  111. if(argv[i][2+len] == '\0') {
  112. /* with no argument provided. */
  113. if(!(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG))
  114. ret = callback(opt->id, NULL, userdata);
  115. else
  116. ret = callback(CMDLINE_OPTID_MISSINGARG, argv[i], userdata);
  117. break;
  118. } else if(argv[i][2+len] == '=') {
  119. /* with an argument provided. */
  120. if(opt->flags & (CMDLINE_OPTFLAG_OPTIONALARG | CMDLINE_OPTFLAG_REQUIREDARG)) {
  121. ret = callback(opt->id, argv[i]+2+len+1, userdata);
  122. } else {
  123. snprintf(auxbuf, CMDLINE_AUXBUF_SIZE, "--%s", opt->longname);
  124. ret = callback(CMDLINE_OPTID_BOGUSARG, auxbuf, userdata);
  125. }
  126. break;
  127. } else {
  128. continue;
  129. }
  130. }
  131. } else if(opt->shortname != '\0' && argv[i][0] == '-') {
  132. if(argv[i][1] == opt->shortname) {
  133. /* Regular short option. */
  134. if(opt->flags & CMDLINE_OPTFLAG_REQUIREDARG) {
  135. if(argv[i][2] != '\0')
  136. ret = callback(opt->id, argv[i]+2, userdata);
  137. else if(i+1 < argc)
  138. ret = callback(opt->id, argv[++i], userdata);
  139. else
  140. ret = callback(CMDLINE_OPTID_MISSINGARG, argv[i], userdata);
  141. break;
  142. } else {
  143. ret = callback(opt->id, NULL, userdata);
  144. /* There might be more (argument-less) short options
  145. * grouped together. */
  146. if(ret == 0 && argv[i][2] != '\0')
  147. ret = cmdline_handle_short_opt_group(options, argv[i]+2, callback, userdata);
  148. break;
  149. }
  150. }
  151. }
  152. }
  153. if(opt->id == 0) { /* still not handled? */
  154. if(argv[i][0] != '-') {
  155. /* Non-option argument. */
  156. ret = callback(CMDLINE_OPTID_NONE, argv[i], userdata);
  157. } else {
  158. /* Unknown option. */
  159. char* badoptname = argv[i];
  160. if(strncmp(badoptname, "--", 2) == 0) {
  161. /* Strip any argument from the long option. */
  162. char* assignment = strchr(badoptname, '=');
  163. if(assignment != NULL) {
  164. size_t len = assignment - badoptname;
  165. if(len > CMDLINE_AUXBUF_SIZE)
  166. len = CMDLINE_AUXBUF_SIZE;
  167. strncpy(auxbuf, badoptname, len);
  168. auxbuf[len] = '\0';
  169. badoptname = auxbuf;
  170. }
  171. }
  172. ret = callback(CMDLINE_OPTID_UNKNOWN, badoptname, userdata);
  173. }
  174. }
  175. }
  176. if(ret != 0)
  177. return ret;
  178. i++;
  179. }
  180. return ret;
  181. }