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