getopt.c 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /*
  2. Copyright (c) 2012, Kim Gräsman
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are met:
  6. * Redistributions of source code must retain the above copyright
  7. notice, this list of conditions and the following disclaimer.
  8. * Redistributions in binary form must reproduce the above copyright
  9. notice, this list of conditions and the following disclaimer in the
  10. documentation and/or other materials provided with the distribution.
  11. * Neither the name of Kim Gräsman nor the
  12. names of contributors may be used to endorse or promote products
  13. derived from this software without specific prior written permission.
  14. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  15. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  16. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17. DISCLAIMED. IN NO EVENT SHALL KIM GRÄSMAN BE LIABLE FOR ANY
  18. DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  19. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  20. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  21. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  23. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. #include "lub/getopt.h"
  26. #include <stddef.h>
  27. #include <string.h>
  28. const int no_argument = 0;
  29. const int required_argument = 1;
  30. const int optional_argument = 2;
  31. char* optarg;
  32. int optopt;
  33. /* The variable optind [...] shall be initialized to 1 by the system. */
  34. int optind = 1;
  35. int opterr;
  36. static char* optcursor = NULL;
  37. /* Implemented based on [1] and [2] for optional arguments.
  38. optopt is handled FreeBSD-style, per [3].
  39. Other GNU and FreeBSD extensions are purely accidental.
  40. [1] http://pubs.opengroup.org/onlinepubs/000095399/functions/getopt.html
  41. [2] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html
  42. [3] http://www.freebsd.org/cgi/man.cgi?query=getopt&sektion=3&manpath=FreeBSD+9.0-RELEASE
  43. */
  44. int getopt(int argc, char* const argv[], const char* optstring) {
  45. int optchar = -1;
  46. const char* optdecl = NULL;
  47. optarg = NULL;
  48. opterr = 0;
  49. optopt = 0;
  50. /* Unspecified, but we need it to avoid overrunning the argv bounds. */
  51. if (optind >= argc)
  52. goto no_more_optchars;
  53. /* If, when getopt() is called argv[optind] is a null pointer, getopt()
  54. shall return -1 without changing optind. */
  55. if (argv[optind] == NULL)
  56. goto no_more_optchars;
  57. /* If, when getopt() is called *argv[optind] is not the character '-',
  58. getopt() shall return -1 without changing optind. */
  59. if (*argv[optind] != '-')
  60. goto no_more_optchars;
  61. /* If, when getopt() is called argv[optind] points to the string "-",
  62. getopt() shall return -1 without changing optind. */
  63. if (strcmp(argv[optind], "-") == 0)
  64. goto no_more_optchars;
  65. /* If, when getopt() is called argv[optind] points to the string "--",
  66. getopt() shall return -1 after incrementing optind. */
  67. if (strcmp(argv[optind], "--") == 0) {
  68. ++optind;
  69. goto no_more_optchars;
  70. }
  71. if (optcursor == NULL || *optcursor == '\0')
  72. optcursor = argv[optind] + 1;
  73. optchar = *optcursor;
  74. /* FreeBSD: The variable optopt saves the last known option character
  75. returned by getopt(). */
  76. optopt = optchar;
  77. /* The getopt() function shall return the next option character (if one is
  78. found) from argv that matches a character in optstring, if there is
  79. one that matches. */
  80. optdecl = strchr(optstring, optchar);
  81. if (optdecl) {
  82. /* [I]f a character is followed by a colon, the option takes an
  83. argument. */
  84. if (optdecl[1] == ':') {
  85. optarg = ++optcursor;
  86. if (*optarg == '\0') {
  87. /* GNU extension: Two colons mean an option takes an
  88. optional arg; if there is text in the current argv-element
  89. (i.e., in the same word as the option name itself, for example,
  90. "-oarg"), then it is returned in optarg, otherwise optarg is set
  91. to zero. */
  92. if (optdecl[2] != ':') {
  93. /* If the option was the last character in the string pointed to by
  94. an element of argv, then optarg shall contain the next element
  95. of argv, and optind shall be incremented by 2. If the resulting
  96. value of optind is greater than argc, this indicates a missing
  97. option-argument, and getopt() shall return an error indication.
  98. Otherwise, optarg shall point to the string following the
  99. option character in that element of argv, and optind shall be
  100. incremented by 1.
  101. */
  102. if (++optind < argc) {
  103. optarg = argv[optind];
  104. } else {
  105. /* If it detects a missing option-argument, it shall return the
  106. colon character ( ':' ) if the first character of optstring
  107. was a colon, or a question-mark character ( '?' ) otherwise.
  108. */
  109. optarg = NULL;
  110. optchar = (optstring[0] == ':') ? ':' : '?';
  111. }
  112. } else {
  113. optarg = NULL;
  114. }
  115. }
  116. optcursor = NULL;
  117. }
  118. } else {
  119. /* If getopt() encounters an option character that is not contained in
  120. optstring, it shall return the question-mark ( '?' ) character. */
  121. optchar = '?';
  122. }
  123. if (optcursor == NULL || *++optcursor == '\0')
  124. ++optind;
  125. return optchar;
  126. no_more_optchars:
  127. optcursor = NULL;
  128. return -1;
  129. }
  130. /* Implementation based on [1].
  131. [1] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html
  132. */
  133. int getopt_long(int argc, char* const argv[], const char* optstring,
  134. const struct option* longopts, int* longindex) {
  135. const struct option* o = longopts;
  136. const struct option* match = NULL;
  137. int num_matches = 0;
  138. size_t argument_name_length = 0;
  139. const char* current_argument = NULL;
  140. int retval = -1;
  141. optarg = NULL;
  142. optopt = 0;
  143. if (optind >= argc)
  144. return -1;
  145. if (strlen(argv[optind]) < 3 || strncmp(argv[optind], "--", 2) != 0)
  146. return getopt(argc, argv, optstring);
  147. /* It's an option; starts with -- and is longer than two chars. */
  148. current_argument = argv[optind] + 2;
  149. argument_name_length = strcspn(current_argument, "=");
  150. for (; o->name; ++o) {
  151. if (strncmp(o->name, current_argument, argument_name_length) == 0) {
  152. match = o;
  153. ++num_matches;
  154. }
  155. }
  156. if (num_matches == 1) {
  157. /* If longindex is not NULL, it points to a variable which is set to the
  158. index of the long option relative to longopts. */
  159. if (longindex)
  160. *longindex = (match - longopts);
  161. /* If flag is NULL, then getopt_long() shall return val.
  162. Otherwise, getopt_long() returns 0, and flag shall point to a variable
  163. which shall be set to val if the option is found, but left unchanged if
  164. the option is not found. */
  165. if (match->flag)
  166. *(match->flag) = match->val;
  167. retval = match->flag ? 0 : match->val;
  168. if (match->has_arg != no_argument) {
  169. optarg = strchr(argv[optind], '=');
  170. if (optarg != NULL)
  171. ++optarg;
  172. if (match->has_arg == required_argument) {
  173. /* Only scan the next argv for required arguments. Behavior is not
  174. specified, but has been observed with Ubuntu and Mac OSX. */
  175. if (optarg == NULL && ++optind < argc) {
  176. optarg = argv[optind];
  177. }
  178. if (optarg == NULL)
  179. retval = ':';
  180. }
  181. } else if (strchr(argv[optind], '=')) {
  182. /* An argument was provided to a non-argument option.
  183. I haven't seen this specified explicitly, but both GNU and BSD-based
  184. implementations show this behavior.
  185. */
  186. retval = '?';
  187. }
  188. } else {
  189. /* Unknown option or ambiguous match. */
  190. retval = '?';
  191. }
  192. ++optind;
  193. return retval;
  194. }