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