1 #include <stdio.h>
2 
3 #include "cmdopt.h"
4 
5 #ifdef __cplusplus
6 extern "C" {
7 #endif  // __cplusplus
8 
9 // Moves `optind' to the end and shifts other arguments.
cmdopt_shift(cmdopt_t * h)10 static 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.
cmdopt_next(cmdopt_t * h)25 static 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.
cmdopt_check(cmdopt_t * h)31 static 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.
cmdopt_getopt(cmdopt_t * h)48 static 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.
cmdopt_search(cmdopt_t * h)65 static 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.
cmdopt_match_len(const char * opt,const char * arg)100 static 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.
cmdopt_match(cmdopt_t * h)120 static 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.
cmdopt_getopt_long(cmdopt_t * h)151 static 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.
cmdopt_search_long(cmdopt_t * h)169 static 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.
cmdopt_main(cmdopt_t * h)207 static 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.
cmdopt_init(cmdopt_t * h,int argc,char ** argv,const char * optstring,const cmdopt_option * longopts)242 void 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.
cmdopt_get(cmdopt_t * h)263 int 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