6699c5217b6d8abb182dfa207a15dbb21682ba49
[notmuch] / command-line-arguments.c
1 #include <assert.h>
2 #include <string.h>
3 #include <stdio.h>
4 #include "error_util.h"
5 #include "command-line-arguments.h"
6
7 typedef enum {
8     OPT_FAILED, /* false */
9     OPT_OK, /* good */
10     OPT_GIVEBACK, /* pop one of the arguments you thought you were getting off the stack */
11 } opt_handled;
12
13 /*
14   Search the array of keywords for a given argument, assigning the
15   output variable to the corresponding value.  Return false if nothing
16   matches.
17 */
18
19 static opt_handled
20 _process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next,
21                       const char *arg_str, bool negate)
22 {
23     const notmuch_keyword_t *keywords;
24
25     if (next == '\0') {
26         /* No keyword given */
27         arg_str = "";
28     }
29
30     for (keywords = arg_desc->keywords; keywords->name; keywords++) {
31         if (strcmp (arg_str, keywords->name) != 0)
32             continue;
33
34         if (arg_desc->opt_flags && negate)
35             *arg_desc->opt_flags &= ~keywords->value;
36         else if (arg_desc->opt_flags)
37             *arg_desc->opt_flags |= keywords->value;
38         else
39             *arg_desc->opt_keyword = keywords->value;
40
41         return OPT_OK;
42     }
43
44     if (arg_desc->opt_keyword && arg_desc->keyword_no_arg_value && next != ':' && next != '=') {
45         for (keywords = arg_desc->keywords; keywords->name; keywords++) {
46             if (strcmp (arg_desc->keyword_no_arg_value, keywords->name) != 0)
47                 continue;
48
49             *arg_desc->opt_keyword = keywords->value;
50             fprintf (stderr, "Warning: No known keyword option given for \"%s\", choosing value \"%s\"."
51                      "  Please specify the argument explicitly!\n", arg_desc->name, arg_desc->keyword_no_arg_value);
52
53             return OPT_GIVEBACK;
54         }
55         fprintf (stderr, "No matching keyword for option \"%s\" and default value \"%s\" is invalid.\n", arg_str, arg_desc->name);
56         return OPT_FAILED;
57     }
58
59     if (next != '\0')
60         fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str, arg_desc->name);
61     else
62         fprintf (stderr, "Option \"%s\" needs a keyword argument.\n", arg_desc->name);
63     return OPT_FAILED;
64 }
65
66 static opt_handled
67 _process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next,
68                       const char *arg_str, bool negate)
69 {
70     bool value;
71
72     if (next == '\0' || strcmp (arg_str, "true") == 0) {
73         value = true;
74     } else if (strcmp (arg_str, "false") == 0) {
75         value = false;
76     } else {
77         fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
78         return OPT_FAILED;
79     }
80
81     *arg_desc->opt_bool = negate ? (! value) : value;
82
83     return OPT_OK;
84 }
85
86 static opt_handled
87 _process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
88
89     char *endptr;
90     if (next == '\0' || arg_str[0] == '\0') {
91         fprintf (stderr, "Option \"%s\" needs an integer argument.\n", arg_desc->name);
92         return OPT_FAILED;
93     }
94
95     *arg_desc->opt_int = strtol (arg_str, &endptr, 10);
96     if (*endptr == '\0')
97         return OPT_OK;
98
99     fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n",
100              arg_str, arg_desc->name);
101     return OPT_FAILED;
102 }
103
104 static opt_handled
105 _process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
106
107     if (next == '\0') {
108         fprintf (stderr, "Option \"%s\" needs a string argument.\n", arg_desc->name);
109         return OPT_FAILED;
110     }
111     if (arg_str[0] == '\0' && ! arg_desc->allow_empty) {
112         fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name);
113         return OPT_FAILED;
114     }
115     *arg_desc->opt_string = arg_str;
116     return OPT_OK;
117 }
118
119 /* Return number of non-NULL opt_* fields in opt_desc. */
120 static int _opt_set_count (const notmuch_opt_desc_t *opt_desc)
121 {
122     return
123         (bool) opt_desc->opt_inherit +
124         (bool) opt_desc->opt_bool +
125         (bool) opt_desc->opt_int +
126         (bool) opt_desc->opt_keyword +
127         (bool) opt_desc->opt_flags +
128         (bool) opt_desc->opt_string +
129         (bool) opt_desc->opt_position;
130 }
131
132 /* Return true if opt_desc is valid. */
133 static bool _opt_valid (const notmuch_opt_desc_t *opt_desc)
134 {
135     int n = _opt_set_count (opt_desc);
136
137     if (n > 1)
138         INTERNAL_ERROR ("more than one non-NULL opt_* field for argument \"%s\"",
139                         opt_desc->name);
140
141     return n > 0;
142 }
143
144 /*
145    Search for the {pos_arg_index}th position argument, return false if
146    that does not exist.
147 */
148
149 bool
150 parse_position_arg (const char *arg_str, int pos_arg_index,
151                     const notmuch_opt_desc_t *arg_desc) {
152
153     int pos_arg_counter = 0;
154     while (_opt_valid (arg_desc)) {
155         if (arg_desc->opt_position) {
156             if (pos_arg_counter == pos_arg_index) {
157                 *arg_desc->opt_position = arg_str;
158                 if (arg_desc->present)
159                     *arg_desc->present = true;
160                 return true;
161             }
162             pos_arg_counter++;
163         }
164         arg_desc++;
165     }
166     return false;
167 }
168
169 #define NEGATIVE_PREFIX "no-"
170
171 /*
172  * Search for a non-positional (i.e. starting with --) argument matching arg,
173  * parse a possible value, and assign to *output_var
174  */
175
176 int
177 parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index)
178 {
179     assert(argv);
180
181     const char *_arg = argv[opt_index];
182
183     assert(_arg);
184     assert(options);
185
186     const char *arg = _arg + 2; /* _arg starts with -- */
187     const char *negative_arg = NULL;
188
189     /* See if this is a --no-argument */
190     if (strlen (arg) > strlen (NEGATIVE_PREFIX) &&
191         strncmp (arg, NEGATIVE_PREFIX, strlen (NEGATIVE_PREFIX)) == 0) {
192         negative_arg = arg + strlen (NEGATIVE_PREFIX);
193     }
194
195     const notmuch_opt_desc_t *try;
196
197     const char *next_arg = NULL;
198     if (opt_index < argc - 1  && strncmp (argv[opt_index + 1], "--", 2) != 0)
199         next_arg = argv[opt_index + 1];
200
201     for (try = options; _opt_valid (try); try++) {
202         if (try->opt_inherit) {
203             int new_index = parse_option (argc, argv, try->opt_inherit, opt_index);
204             if (new_index >= 0)
205                 return new_index;
206         }
207
208         if (! try->name)
209             continue;
210
211         char next;
212         const char *value;
213         bool negate = false;
214
215         if (strncmp (arg, try->name, strlen (try->name)) == 0) {
216             next = arg[strlen (try->name)];
217             value = arg + strlen (try->name) + 1;
218         } else if (negative_arg && (try->opt_bool || try->opt_flags) &&
219                    strncmp (negative_arg, try->name, strlen (try->name)) == 0) {
220             next = negative_arg[strlen (try->name)];
221             value = negative_arg + strlen (try->name) + 1;
222             /* The argument part of --no-argument matches, negate the result. */
223             negate = true;
224         } else {
225             continue;
226         }
227
228         /*
229          * If we have not reached the end of the argument (i.e. the
230          * next character is not a space or delimiter) then the
231          * argument could still match a longer option name later in
232          * the option table.
233          */
234         if (next != '=' && next != ':' && next != '\0')
235             continue;
236
237         bool lookahead = (next == '\0' && next_arg != NULL && ! try->opt_bool);
238
239         if (lookahead) {
240             next = ' ';
241             value = next_arg;
242             opt_index ++;
243         }
244
245         opt_handled opt_status = OPT_FAILED;
246         if (try->opt_keyword || try->opt_flags)
247             opt_status = _process_keyword_arg (try, next, value, negate);
248         else if (try->opt_bool)
249             opt_status = _process_boolean_arg (try, next, value, negate);
250         else if (try->opt_int)
251             opt_status = _process_int_arg (try, next, value);
252         else if (try->opt_string)
253             opt_status = _process_string_arg (try, next, value);
254         else
255             INTERNAL_ERROR ("unknown or unhandled option \"%s\"", try->name);
256
257         if (opt_status == OPT_FAILED)
258             return -1;
259
260         if (lookahead && opt_status == OPT_GIVEBACK)
261             opt_index --;
262
263         if (try->present)
264             *try->present = true;
265
266         return opt_index+1;
267     }
268     return -1;
269 }
270
271 /* See command-line-arguments.h for description */
272 int
273 parse_arguments (int argc, char **argv,
274                  const notmuch_opt_desc_t *options, int opt_index) {
275
276     int pos_arg_index = 0;
277     bool more_args = true;
278
279     while (more_args && opt_index < argc) {
280         if (strncmp (argv[opt_index],"--",2) != 0) {
281
282             more_args = parse_position_arg (argv[opt_index], pos_arg_index, options);
283
284             if (more_args) {
285                 pos_arg_index++;
286                 opt_index++;
287             }
288
289         } else {
290             int prev_opt_index = opt_index;
291
292             if (strlen (argv[opt_index]) == 2)
293                 return opt_index+1;
294
295             opt_index = parse_option (argc, argv, options, opt_index);
296             if (opt_index < 0) {
297                 fprintf (stderr, "Unrecognized option: %s\n", argv[prev_opt_index]);
298                 more_args = false;
299             }
300         }
301     }
302
303     return opt_index;
304 }