]> git.notmuchmail.org Git - notmuch/blob - command-line-arguments.c
cli: refactor boolean argument processing
[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 /*
8   Search the array of keywords for a given argument, assigning the
9   output variable to the corresponding value.  Return FALSE if nothing
10   matches.
11 */
12
13 static notmuch_bool_t
14 _process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
15
16     const notmuch_keyword_t *keywords = arg_desc->keywords;
17
18     if (next == '\0') {
19         /* No keyword given */
20         arg_str = "";
21     }
22
23     while (keywords->name) {
24         if (strcmp (arg_str, keywords->name) == 0) {
25             if (arg_desc->opt_flags)
26                 *arg_desc->opt_flags |= keywords->value;
27             else
28                 *arg_desc->opt_keyword = keywords->value;
29             return TRUE;
30         }
31         keywords++;
32     }
33     if (next != '\0')
34         fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str, arg_desc->name);
35     else
36         fprintf (stderr, "Option \"%s\" needs a keyword argument.\n", arg_desc->name);
37     return FALSE;
38 }
39
40 static notmuch_bool_t
41 _process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
42     notmuch_bool_t value;
43
44     if (next == '\0' || strcmp (arg_str, "true") == 0) {
45         value = TRUE;
46     } else if (strcmp (arg_str, "false") == 0) {
47         value = FALSE;
48     } else {
49         fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
50         return FALSE;
51     }
52
53     *arg_desc->opt_bool = value;
54
55     return TRUE;
56 }
57
58 static notmuch_bool_t
59 _process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
60
61     char *endptr;
62     if (next == '\0' || arg_str[0] == '\0') {
63         fprintf (stderr, "Option \"%s\" needs an integer argument.\n", arg_desc->name);
64         return FALSE;
65     }
66
67     *arg_desc->opt_int = strtol (arg_str, &endptr, 10);
68     if (*endptr == '\0')
69         return TRUE;
70
71     fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n",
72              arg_str, arg_desc->name);
73     return FALSE;
74 }
75
76 static notmuch_bool_t
77 _process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
78
79     if (next == '\0') {
80         fprintf (stderr, "Option \"%s\" needs a string argument.\n", arg_desc->name);
81         return FALSE;
82     }
83     if (arg_str[0] == '\0') {
84         fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name);
85         return FALSE;
86     }
87     *arg_desc->opt_string = arg_str;
88     return TRUE;
89 }
90
91 /* Return number of non-NULL opt_* fields in opt_desc. */
92 static int _opt_set_count (const notmuch_opt_desc_t *opt_desc)
93 {
94     return
95         !!opt_desc->opt_inherit +
96         !!opt_desc->opt_bool +
97         !!opt_desc->opt_int +
98         !!opt_desc->opt_keyword +
99         !!opt_desc->opt_flags +
100         !!opt_desc->opt_string +
101         !!opt_desc->opt_position;
102 }
103
104 /* Return TRUE if opt_desc is valid. */
105 static notmuch_bool_t _opt_valid (const notmuch_opt_desc_t *opt_desc)
106 {
107     int n = _opt_set_count (opt_desc);
108
109     if (n > 1)
110         INTERNAL_ERROR ("more than one non-NULL opt_* field for argument \"%s\"",
111                         opt_desc->name);
112
113     return n > 0;
114 }
115
116 /*
117    Search for the {pos_arg_index}th position argument, return FALSE if
118    that does not exist.
119 */
120
121 notmuch_bool_t
122 parse_position_arg (const char *arg_str, int pos_arg_index,
123                     const notmuch_opt_desc_t *arg_desc) {
124
125     int pos_arg_counter = 0;
126     while (_opt_valid (arg_desc)) {
127         if (arg_desc->opt_position) {
128             if (pos_arg_counter == pos_arg_index) {
129                 *arg_desc->opt_position = arg_str;
130                 if (arg_desc->present)
131                     *arg_desc->present = TRUE;
132                 return TRUE;
133             }
134             pos_arg_counter++;
135         }
136         arg_desc++;
137     }
138     return FALSE;
139 }
140
141 /*
142  * Search for a non-positional (i.e. starting with --) argument matching arg,
143  * parse a possible value, and assign to *output_var
144  */
145
146 int
147 parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index)
148 {
149     assert(argv);
150
151     const char *_arg = argv[opt_index];
152
153     assert(_arg);
154     assert(options);
155
156     const char *arg = _arg + 2; /* _arg starts with -- */
157     const notmuch_opt_desc_t *try;
158
159     const char *next_arg = NULL;
160     if (opt_index < argc - 1  && strncmp (argv[opt_index + 1], "--", 2) != 0)
161         next_arg = argv[opt_index + 1];
162
163     for (try = options; _opt_valid (try); try++) {
164         if (try->opt_inherit) {
165             int new_index = parse_option (argc, argv, try->opt_inherit, opt_index);
166             if (new_index >= 0)
167                 return new_index;
168         }
169
170         if (! try->name)
171             continue;
172
173         if (strncmp (arg, try->name, strlen (try->name)) != 0)
174             continue;
175
176         char next = arg[strlen (try->name)];
177         const char *value = arg + strlen(try->name) + 1;
178
179         /*
180          * If we have not reached the end of the argument (i.e. the
181          * next character is not a space or delimiter) then the
182          * argument could still match a longer option name later in
183          * the option table.
184          */
185         if (next != '=' && next != ':' && next != '\0')
186             continue;
187
188         if (next == '\0' && next_arg != NULL && ! try->opt_bool) {
189             next = ' ';
190             value = next_arg;
191             opt_index ++;
192         }
193
194         notmuch_bool_t opt_status = FALSE;
195         if (try->opt_keyword || try->opt_flags)
196             opt_status = _process_keyword_arg (try, next, value);
197         else if (try->opt_bool)
198             opt_status = _process_boolean_arg (try, next, value);
199         else if (try->opt_int)
200             opt_status = _process_int_arg (try, next, value);
201         else if (try->opt_string)
202             opt_status = _process_string_arg (try, next, value);
203         else
204             INTERNAL_ERROR ("unknown or unhandled option \"%s\"", try->name);
205
206         if (! opt_status)
207             return -1;
208
209         if (try->present)
210             *try->present = TRUE;
211
212         return opt_index+1;
213     }
214     return -1;
215 }
216
217 /* See command-line-arguments.h for description */
218 int
219 parse_arguments (int argc, char **argv,
220                  const notmuch_opt_desc_t *options, int opt_index) {
221
222     int pos_arg_index = 0;
223     notmuch_bool_t more_args = TRUE;
224
225     while (more_args && opt_index < argc) {
226         if (strncmp (argv[opt_index],"--",2) != 0) {
227
228             more_args = parse_position_arg (argv[opt_index], pos_arg_index, options);
229
230             if (more_args) {
231                 pos_arg_index++;
232                 opt_index++;
233             }
234
235         } else {
236             int prev_opt_index = opt_index;
237
238             if (strlen (argv[opt_index]) == 2)
239                 return opt_index+1;
240
241             opt_index = parse_option (argc, argv, options, opt_index);
242             if (opt_index < 0) {
243                 fprintf (stderr, "Unrecognized option: %s\n", argv[prev_opt_index]);
244                 more_args = FALSE;
245             }
246         }
247     }
248
249     return opt_index;
250 }