1#include <stdio.h>
2
3#include "cmdopt.h"
4
5#ifdef __cplusplus
6extern "C" {
7#endif  // __cplusplus
8
9// Moves `optind' to the end and shifts other arguments.
10static void cmdopt_shift(cmdopt_t *h) {
11  int   i;
12  char *tmp;
13
14  tmp = h->argv[h->optind];
15  for (i = h->optind; i < h->argc - 1; i++) {
16    h->argv[i] = h->argv[i + 1];
17  }
18  h->argv[i] = tmp;
19
20  h->nextchar = NULL;
21  h->optnum--;
22}
23
24// Moves to the next argument.
25static void cmdopt_next(cmdopt_t *h) {
26  h->optind++;
27  h->nextchar = NULL;
28}
29
30// Checks if the current argument is an option or not.
31static int cmdopt_check(cmdopt_t *h) {
32  int         ret = 1;
33  const char *arg = h->argv[h->optind];
34
35  if (*arg++ != '-') {
36    return 0;
37  }
38
39  if (*arg == '-') {
40    arg++;
41    ret++;
42  }
43
44  return ret - (*arg == '\0');
45}
46
47// Gets an argument of the current option.
48static void cmdopt_getopt(cmdopt_t *h) {
49  // Moves to the next argument if the current argument has no more characters.
50  if (*h->nextchar == '\0') {
51    cmdopt_next(h);
52    h->nextchar = h->argv[h->optind];
53  }
54
55  // Checks whether the current option has an argument or not.
56  if (h->optind < h->optnum) {
57    h->optarg = h->nextchar;
58    cmdopt_next(h);
59  } else {
60    h->optarg = NULL;
61  }
62}
63
64// Searches an option.
65static int cmdopt_search(cmdopt_t *h) {
66  const char *ptr;
67
68  // Updates an option character.
69  h->optopt = *h->nextchar++;
70
71  for (ptr = h->optstring; *ptr != '\0'; ptr++) {
72    if (*ptr == h->optopt) {
73      // Gets an option argument if required.
74      if (ptr[1] == ':') {
75        cmdopt_getopt(h);
76
77        // Returns ':' if there is no argument.
78        if (h->optarg == NULL && ptr[2] != ':') {
79          return ':';
80        }
81      }
82      return h->optopt;
83    }
84  }
85
86  if (h->optopt == '-') {
87    cmdopt_next(h);
88    while (h->optind < h->optnum) {
89      cmdopt_shift(h);
90    }
91    return -1;
92  }
93
94  // Returns '?' if the option character is undefined.
95  return '?';
96}
97
98// Compares a long option with an argument and returns the length of the
99// matched prefix.
100static int cmdopt_match_len(const char *opt, const char *arg) {
101  int len = 0;
102
103  // Returns 0 if there is a mismatch.
104  while ((*arg != '\0') && (*arg != '=')) {
105    if (*arg++ != *opt++) {
106      return 0;
107    }
108    len++;
109  }
110
111  // Returns a negative value in case of a perfect match.
112  if ((*arg == '\0') || (*arg == '=')) {
113    return -len;
114  }
115
116  return len;
117}
118
119// Checks long options.
120static int cmdopt_match(cmdopt_t *h) {
121  int i, len;
122  int max = 0, max_optind = -1;
123
124  // Returns -1 if there are no long options.
125  if (h->longopts == NULL) {
126    return max_optind;
127  }
128
129  for (i = 0; h->longopts[i].name != NULL; i++) {
130    len = cmdopt_match_len(h->longopts[i].name, h->nextchar);
131    if (len < 0) {
132      // In case of a perfect match.
133      h->nextchar -= len;
134      return i;
135    } else if (len > max) {
136      // In case of a prefix match.
137      max = len;
138      max_optind = i;
139    } else if (len == max) {
140      // There are other candidates.
141      max_optind = -1;
142    }
143  }
144
145  // If there is no perfect match, adopts the longest one.
146  h->nextchar += max;
147  return max_optind;
148}
149
150// Gets an argument of a long option.
151static void cmdopt_getopt_long(cmdopt_t *h) {
152  if (*h->nextchar == '=') {
153    h->optarg = h->nextchar + 1;
154    cmdopt_next(h);
155  } else {
156    cmdopt_next(h);
157
158    // Checks whether there are more options or not.
159    if (h->optind < h->optnum) {
160      h->optarg = h->argv[h->optind];
161      cmdopt_next(h);
162    } else {
163      h->optarg = NULL;
164    }
165  }
166}
167
168// Searches long options.
169static int cmdopt_search_long(cmdopt_t *h) {
170  const cmdopt_option *option;
171
172  // Keeps the long option.
173  h->optlong = h->argv[h->optind];
174
175  // Gets the next option.
176  h->longindex = cmdopt_match(h);
177  if (h->longindex  < 0) {
178    cmdopt_next(h);
179    return '?';
180  }
181
182  // Gets an argument if required.
183  option = h->longopts + h->longindex;
184  if (option->has_arg) {
185    cmdopt_getopt_long(h);
186
187    // Return ':' if there are no more arguments.
188    if (h->optarg == NULL) {
189      return ':';
190    }
191  } else if (*h->nextchar == '=') {
192    // Returns '?' for an extra option argument.
193    cmdopt_getopt_long(h);
194    return '?';
195  }
196
197  // Overwrites a variable if specified in settings.
198  if (option->flag != NULL) {
199    *option->flag = option->val;
200    return 0;
201  }
202
203  return option->val;
204}
205
206// Analyze command line option.
207static int cmdopt_main(cmdopt_t *h) {
208  int type;
209
210  // Initializes the internal state.
211  h->optopt = 0;
212  h->optlong = NULL;
213  h->optarg = NULL;
214  h->longindex = 0;
215
216  while (h->optind < h->optnum) {
217    if (h->nextchar == NULL) {
218      // Checks whether the next argument is an option or not.
219      type = cmdopt_check(h);
220      if (type == 0) {
221        cmdopt_shift(h);
222      } else {
223        h->nextchar = h->argv[h->optind] + type;
224        if (type == 2) {
225          return cmdopt_search_long(h);
226        }
227      }
228    } else {
229      if (*h->nextchar == '\0') {
230        cmdopt_next(h);
231        continue;
232      }
233      // Searches an option string.
234      return cmdopt_search(h);
235    }
236  }
237
238  return -1;
239}
240
241// cmdopt_init() initializes a cmdopt_t for successive cmdopt_get()s.
242void cmdopt_init(cmdopt_t *h, int argc, char **argv,
243    const char *optstring, const cmdopt_option *longopts) {
244  static const char empty_optstring[] = "";
245
246  h->argc = argc;
247  h->argv = argv;
248  h->optnum = h->argc;
249
250  h->longopts = longopts;
251  h->optstring = (optstring != NULL) ? optstring : empty_optstring;
252
253  h->optind = 1;
254  h->nextchar = NULL;
255  h->optarg = NULL;
256  h->optopt = 0;
257  h->optlong = NULL;
258  h->opterr = 1;
259  h->longindex = 0;
260}
261
262// cmdopt_get() analyzes command line arguments and gets the next option.
263int cmdopt_get(cmdopt_t *h) {
264  int value = cmdopt_main(h);
265
266  // Prints a warning to the standard error stream if enabled.
267  if (h->opterr) {
268    if (value == ':') {
269      // Warning for a lack of an option argument.
270      if (h->optlong == NULL) {
271        fprintf(stderr, "option requires an argument -- %c\n", h->optopt);
272      } else {
273        fprintf(stderr, "option `--%s' requires an argument\n",
274            h->longopts[h->longindex].name);
275      }
276    } else if (value == '?') {
277      // Warning for an invalid option.
278      if (h->optlong == NULL) {
279        fprintf(stderr, "invalid option -- %c\n", h->optopt);
280      } else {
281        fprintf(stderr, "unrecognized option `%s'\n", h->optlong);
282      }
283    } else if ((value != -1) && (h->opterr == 2)) {
284      // Actually this is not for warning, but for debugging.
285      if (h->optlong == NULL) {
286        fprintf(stderr, "option with `%s' -- %c\n", h->optarg, h->optopt);
287      } else {
288        fprintf(stderr, "option `--%s' with `%s'\n",
289            h->longopts[h->longindex].name, h->optarg);
290      }
291    }
292  }
293  return value;
294}
295
296#ifdef __cplusplus
297}  // extern "C"
298#endif  // __cplusplus
299