[cli] Combine the tagged and required fields of the option info struct.
[vsc-common.git] / cli / option.c
1 /*
2  * option.c: Library for interactive commandline interfaces
3  *
4  * Copyright (C) 2009 Matthias Bolte <matthias.bolte@googlemail.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
19  */
20
21 #include <string.h>
22
23 #include <libvscmisc/assert.h>
24 #include <libvscmisc/error.h>
25 #include <libvscmisc/list.h>
26 #include <libvscmisc/memory.h>
27 #include <libvscmisc/string.h>
28
29 #include "../include/libvsccli/option.h"
30 #include "option.h"
31 #include "token.h"
32
33 const char *
34 _vsc_cli_option_syntax (const struct VscCliOptionInfo *option_info,
35                         int synopsis)
36 {
37         const char *type = NULL;
38         static char syntax[512];
39
40         VSC__ASSERT (option_info != NULL);
41
42         if (option_info->flags & VSC_CLI__OPTION_FLAG__TAGGED) {
43                 if (option_info->type == VSC_CLI__OPTION_TYPE__BOOLEAN) {
44                         if (synopsis) {
45                                 snprintf (syntax, 512, "--%s", option_info->long_name);
46                         } else {
47                                 if (option_info->short_name > 0) {
48                                         snprintf (syntax, 512, "--%s, -%c", option_info->long_name,
49                                                   option_info->short_name);
50                                 } else {
51                                         snprintf (syntax, 512, "--%s", option_info->long_name);
52                                 }
53                         }
54                 } else {
55                         switch (option_info->type) {
56                         case VSC_CLI__OPTION_TYPE__BOOLEAN:
57                                 type = "";
58                                 break;
59
60                         case VSC_CLI__OPTION_TYPE__NUMBER:
61                                 type = "<number>";
62                                 break;
63
64                         case VSC_CLI__OPTION_TYPE__STRING:
65                                 type = "<string>";
66                                 break;
67
68                         default:
69                                 type = "<INVALID>";
70                                 break;
71                         }
72
73                         if (synopsis) {
74                                 snprintf (syntax, 512, "--%s %s", option_info->long_name,
75                                           type);
76                         } else {
77                                 if (option_info->short_name > 0) {
78                                         snprintf (syntax, 512, "--%s %s, -%c %s",
79                                                   option_info->long_name, type,
80                                                   option_info->short_name, type);
81                                 } else {
82                                         snprintf (syntax, 512, "--%s %s", option_info->long_name,
83                                                   type);
84                                 }
85                         }
86                 }
87         } else {
88                 snprintf (syntax, 512, "<%s>", option_info->long_name);
89         }
90
91         return syntax;
92 }
93
94 int /* option_parsed */
95 _vsc_cli_option_parse (struct VscError *error,
96                        const struct VscCliOptionInfo *option_infos,
97                        int untagged_option_index, const char *command_name,
98                        const char *input, const char **input_tail,
99                        struct VscCliOption *option)
100 {
101         int option_parsed = TRUE;
102         const struct VscCliOptionInfo *option_info;
103         const char *tail = NULL;
104         char *token = NULL;
105         int num_dashes = 0;
106
107         VSC__ASSERT (error != NULL);
108         VSC__ASSERT (! error->occured);
109         VSC__ASSERT (option_infos != NULL);
110         VSC__ASSERT (input != NULL);
111         VSC__ASSERT (input_tail != NULL);
112         VSC__ASSERT (option != NULL);
113
114         *input_tail = input;
115
116         _vsc_cli_token_parse (error, input, &token, &tail);
117
118         if (error->occured) {
119                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
120                 goto failure;
121         }
122
123         if (token == NULL) {
124                 return FALSE;
125         }
126
127         memset (option, 0, sizeof (struct VscCliOption));
128
129         if (*token == '-') {
130                 ++num_dashes;
131
132                 if (*(token + 1) == '-') {
133                         ++num_dashes;
134                 }
135         }
136
137         if (num_dashes > 0) {
138                 if (num_dashes == 1 && *(token + 2) != '\0') {
139                         VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
140                                      "Invalid option syntax: %s", token);
141                         goto failure;
142                 }
143
144                 option_info = _vsc_cli_option_infos_lookup_tagged
145                                  (option_infos, token + num_dashes, num_dashes > 1);
146
147                 if (option_info == NULL) {
148                         if (command_name != NULL) {
149                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
150                                              "Unknown option %s for command '%s'",
151                                              token, command_name);
152                         } else  {
153                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
154                                              "Unknown option %s", token);
155                         }
156
157                         goto failure;
158                 }
159
160                 option->info = option_info;
161
162                 switch (option_info->type) {
163                 case VSC_CLI__OPTION_TYPE__BOOLEAN:
164                         break;
165
166                 case VSC_CLI__OPTION_TYPE__NUMBER:
167                         vsc_free (&token);
168                         _vsc_cli_token_parse (error, tail, &token, &tail);
169
170                         if (error->occured) {
171                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
172                                 goto failure;
173                         }
174
175                         if (token == NULL) {
176                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
177                                              "Expected option syntax: %s",
178                                              _vsc_cli_option_syntax (option_info, FALSE));
179                                 goto failure;
180                         }
181
182                         option->number = vsc_strtoi (error, token, NULL, 10);
183
184                         if (error->occured) {
185                                 vsc_error_cleanup (error);
186                                 vsc_error_init (error);
187
188                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
189                                              "Expected option syntax: %s",
190                                              _vsc_cli_option_syntax (option_info, FALSE));
191                                 goto failure;
192                         }
193
194                         break;
195
196                 case VSC_CLI__OPTION_TYPE__STRING:
197                         vsc_free (&token);
198                         _vsc_cli_token_parse (error, tail, &token, &tail);
199
200                         if (error->occured) {
201                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
202                                 goto failure;
203                         }
204
205                         if (token == NULL) {
206                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
207                                              "Expected option syntax: %s",
208                                              _vsc_cli_option_syntax (option_info, FALSE));
209                                 goto failure;
210                         }
211
212                         option->string = token;
213                         token = NULL;
214                         break;
215
216                 default:
217                         VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
218                                      "Internal error");
219                         goto failure;
220                 }
221
222                 if (error->occured) {
223                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
224                         goto failure;
225                 }
226         } else if (untagged_option_index >= 0) {
227                 option_info = _vsc_cli_option_infos_lookup_untagged
228                                  (option_infos, untagged_option_index);
229
230                 if (option_info == NULL) {
231                         VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
232                                      "Too many untagged options for command '%s'",
233                                      command_name);
234                         goto failure;
235                 }
236
237                 option->info = option_info;
238                 option->string = token;
239                 token = NULL;
240
241                 if (error->occured) {
242                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
243                         goto failure;
244                 }
245         } else {
246                 option_parsed = FALSE;
247         }
248
249         if (option_parsed) {
250                 *input_tail = tail;
251         }
252
253 cleanup:
254         vsc_free (&token);
255
256         return option_parsed;
257
258 failure:
259         vsc_free (&option->string);
260
261         option_parsed = FALSE;
262
263         goto cleanup;
264 }
265
266 const struct VscCliOption *
267 vsc_cli_option_list_lookup (const struct VscCliOption *option_list,
268                             const char *option_long_name)
269 {
270         const struct VscCliOption *option;
271
272         VSC__ASSERT (option_long_name != NULL);
273
274         for (option = option_list; option != NULL; option = option->next) {
275                 if (strcmp (option->info->long_name, option_long_name) == 0) {
276                         return option;
277                 }
278         }
279
280         return NULL;
281 }
282
283 int
284 vsc_cli_option_list_lookup_boolean (const struct VscCliOption *option_list,
285                                     const char *option_long_name)
286 {
287         return vsc_cli_option_list_lookup (option_list, option_long_name) != NULL;
288 }
289
290 int
291 vsc_cli_option_list_lookup_number (const struct VscCliOption *option_list,
292                                    const char *option_long_name, int *found)
293 {
294         const struct VscCliOption *option;
295
296         VSC__ASSERT (option_long_name != NULL);
297
298         if (found != NULL) {
299                 *found = FALSE;
300         }
301
302         option = vsc_cli_option_list_lookup (option_list, option_long_name);
303
304         if (option == NULL) {
305                 return 0;
306         }
307
308         if (found != NULL) {
309                 *found = TRUE;
310         }
311
312         return option->number;
313 }
314
315 const char *
316 vsc_cli_option_list_lookup_string (const struct VscCliOption *option_list,
317                                    const char *option_long_name)
318 {
319         const struct VscCliOption *option;
320
321         VSC__ASSERT (option_long_name != NULL);
322
323         option = vsc_cli_option_list_lookup (option_list, option_long_name);
324
325         if (option == NULL) {
326                 return NULL;
327         }
328
329         return option->string;
330 }
331
332 const struct VscCliOptionInfo *
333 _vsc_cli_option_infos_lookup_tagged
334    (const struct VscCliOptionInfo *option_infos, const char *option_name,
335     int is_long)
336 {
337         int i;
338
339         VSC__ASSERT (option_infos != NULL);
340         VSC__ASSERT (option_name != NULL);
341
342         for (i = 0; option_infos[i].long_name != NULL; i++) {
343                 if (! (option_infos[i].flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
344                         continue;
345                 }
346
347                 if (is_long) {
348                         if (strcmp (option_name, option_infos[i].long_name) == 0) {
349                                 return &option_infos[i];
350                         }
351                 } else {
352                         if (*option_name == option_infos[i].short_name) {
353                                 return &option_infos[i];
354                         }
355                 }
356         }
357
358         return NULL;
359 }
360
361 const struct VscCliOptionInfo *
362 _vsc_cli_option_infos_lookup_untagged
363    (const struct VscCliOptionInfo *option_infos, int position)
364 {
365         int i;
366
367         VSC__ASSERT (option_infos != NULL);
368         VSC__ASSERT (position >= 0);
369
370         for (i = 0; option_infos[i].long_name != NULL; i++) {
371                 if (! (option_infos[i].flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
372                         if (position > 0) {
373                                 --position;
374                         } else {
375                                 return &option_infos[i];
376                         }
377                 }
378         }
379
380         return NULL;
381 }
382
383 static void
384 _option_free (struct VscCliOption **option)
385 {
386         VSC__ASSERT (option != NULL);
387
388         if (*option != NULL) {
389                 vsc_free (&(*option)->string);
390         }
391
392         vsc_free (option);
393 }
394
395 static struct VscCliOption * /* option_duplicate */
396 _option_duplicate (struct VscError *error, const struct VscCliOption *option)
397 {
398         struct VscCliOption *option_duplicate = NULL;
399
400         VSC__ASSERT (error != NULL);
401         VSC__ASSERT (! error->occured);
402         VSC__ASSERT (option != NULL);
403
404         option_duplicate = vsc_memdup (error, option, sizeof (struct VscCliOption));
405
406         if (error->occured) {
407                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
408                 goto failure;
409         }
410
411         if (option->string != NULL) {
412                 option_duplicate->string = vsc_strdup (error, option->string);
413         }
414
415         if (error->occured) {
416                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
417                 goto failure;
418         }
419
420         option_duplicate->next = NULL;
421
422         return option_duplicate;
423
424 failure:
425         _option_free (&option_duplicate);
426
427         return NULL;
428 }
429
430 static int
431 _option_match (struct VscList *item, void *match_data)
432 {
433         struct VscCliOption *option = (struct VscCliOption *) item;
434         const char *option_long_name = (const char *) match_data;
435
436         VSC__ASSERT (item != NULL);
437
438         return strcmp (option->info->long_name, option_long_name) == 0;
439 }
440
441 void
442 _vsc_cli_option_list_append (struct VscError *error,
443                              struct VscCliOption **option_list,
444                              const struct VscCliOption *option)
445 {
446         VSC__ASSERT (error != NULL);
447         VSC__ASSERT (! error->occured);
448         VSC__ASSERT (option_list != NULL);
449         VSC__ASSERT (option != NULL);
450
451         vsc_list_append
452            (error, (struct VscList **) option_list,
453             (struct VscList *) option,
454             (VscList_DuplicateFunction) _option_duplicate);
455
456         if (error->occured) {
457                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
458                 return;
459         }
460 }
461
462 void
463 vsc_cli_option_list_free (struct VscCliOption **option_list)
464 {
465         VSC__ASSERT (option_list != NULL);
466
467         vsc_list_free ((struct VscList **) option_list,
468                        (VscList_FreeFunction) _option_free);
469 }
470
471 struct VscCliOption * /* option_list_duplicate */
472 _vsc_cli_option_list_duplicate (struct VscError *error,
473                                 const struct VscCliOption *option_list)
474 {
475         struct VscCliOption *option_list_duplicate = NULL;
476
477         VSC__ASSERT (error != NULL);
478         VSC__ASSERT (! error->occured);
479
480         option_list_duplicate = (struct VscCliOption *)
481            vsc_list_duplicate
482               (error, (struct VscList *) option_list,
483                (VscList_DuplicateFunction) _option_duplicate,
484                (VscList_FreeFunction) _option_free);
485
486         if (error->occured) {
487                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
488                 return NULL;
489         }
490
491         return option_list_duplicate;
492 }
493
494 struct VscCliOption *
495 _vsc_cli_option_list_lookup (const struct VscCliOption *option_list,
496                              const char *option_long_name)
497 {
498         VSC__ASSERT (option_long_name != NULL);
499
500         return (struct VscCliOption *)
501                   vsc_list_lookup
502                      ((struct VscList *) option_list,
503                       (VscList_MatchFunction) _option_match,
504                       (void *) option_long_name);
505 }