cli: command line parsing: allow default for keyword options
[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->output_var) {
26                 *((int *)arg_desc->output_var) = keywords->value;
27             }
28             return TRUE;
29         }
30         keywords++;
31     }
32     if (next != 0)
33         fprintf (stderr, "unknown keyword: %s\n", arg_str);
34     else
35         fprintf (stderr, "option %s needs a keyword\n", arg_desc->name);
36     return FALSE;
37 }
38
39 static notmuch_bool_t
40 _process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
41
42     if (next == 0) {
43         *((notmuch_bool_t *)arg_desc->output_var) = TRUE;
44         return TRUE;
45     }
46     if (strcmp (arg_str, "false") == 0) {
47         *((notmuch_bool_t *)arg_desc->output_var) = FALSE;
48         return TRUE;
49     }
50     if (strcmp (arg_str, "true") == 0) {
51         *((notmuch_bool_t *)arg_desc->output_var) = TRUE;
52         return TRUE;
53     }
54     return FALSE;
55 }
56
57 /*
58    Search for the {pos_arg_index}th position argument, return FALSE if
59    that does not exist.
60 */
61
62 notmuch_bool_t
63 parse_position_arg (const char *arg_str, int pos_arg_index,
64                     const notmuch_opt_desc_t *arg_desc) {
65
66     int pos_arg_counter = 0;
67     while (arg_desc->opt_type != NOTMUCH_OPT_END){
68         if (arg_desc->opt_type == NOTMUCH_OPT_POSITION) {
69             if (pos_arg_counter == pos_arg_index) {
70                 if (arg_desc->output_var) {
71                     *((const char **)arg_desc->output_var) = arg_str;
72                 }
73                 return TRUE;
74             }
75             pos_arg_counter++;
76         }
77         arg_desc++;
78     }
79     return FALSE;
80 }
81
82 /*
83  * Search for a non-positional (i.e. starting with --) argument matching arg,
84  * parse a possible value, and assign to *output_var
85  */
86
87 notmuch_bool_t
88 parse_option (const char *arg,
89               const notmuch_opt_desc_t *options) {
90
91     assert(arg);
92     assert(options);
93
94     arg += 2;
95
96     const notmuch_opt_desc_t *try = options;
97     while (try->opt_type != NOTMUCH_OPT_END) {
98         if (try->name && strncmp (arg, try->name, strlen (try->name)) == 0) {
99             char next = arg[strlen (try->name)];
100             const char *value= arg+strlen(try->name)+1;
101
102             char *endptr;
103
104             /* Everything but boolean arguments (switches) needs a
105              * delimiter, and a non-zero length value. Boolean
106              * arguments may take an optional =true or =false value.
107              */
108             if (next != '=' && next != ':' && next != 0) return FALSE;
109             if (next == 0) {
110                 if (try->opt_type != NOTMUCH_OPT_BOOLEAN &&
111                     try->opt_type != NOTMUCH_OPT_KEYWORD)
112                     return FALSE;
113             } else {
114                 if (value[0] == 0) return FALSE;
115             }
116
117             if (try->output_var == NULL)
118                 INTERNAL_ERROR ("output pointer NULL for option %s", try->name);
119
120             switch (try->opt_type) {
121             case NOTMUCH_OPT_KEYWORD:
122                 return _process_keyword_arg (try, next, value);
123                 break;
124             case NOTMUCH_OPT_BOOLEAN:
125                 return _process_boolean_arg (try, next, value);
126                 break;
127             case NOTMUCH_OPT_INT:
128                 *((int *)try->output_var) = strtol (value, &endptr, 10);
129                 return (*endptr == 0);
130                 break;
131             case NOTMUCH_OPT_STRING:
132                 *((const char **)try->output_var) = value;
133                 return TRUE;
134                 break;
135             case NOTMUCH_OPT_POSITION:
136             case NOTMUCH_OPT_END:
137             default:
138                 INTERNAL_ERROR ("unknown or unhandled option type %d", try->opt_type);
139                 /*UNREACHED*/
140             }
141         }
142         try++;
143     }
144     fprintf (stderr, "Unrecognized option: --%s\n", arg);
145     return FALSE;
146 }
147
148 /* See command-line-arguments.h for description */
149 int
150 parse_arguments (int argc, char **argv,
151                  const notmuch_opt_desc_t *options, int opt_index) {
152
153     int pos_arg_index = 0;
154     notmuch_bool_t more_args = TRUE;
155
156     while (more_args && opt_index < argc) {
157         if (strncmp (argv[opt_index],"--",2) != 0) {
158
159             more_args = parse_position_arg (argv[opt_index], pos_arg_index, options);
160
161             if (more_args) {
162                 pos_arg_index++;
163                 opt_index++;
164             }
165
166         } else {
167
168             if (strlen (argv[opt_index]) == 2)
169                 return opt_index+1;
170
171             more_args = parse_option (argv[opt_index], options);
172             if (more_args) {
173                 opt_index++;
174             } else {
175                 opt_index = -1;
176             }
177
178         }
179     }
180
181     return opt_index;
182 }