[misc] Add missing initializer in _error_default struct instance.
[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 /*!
22  * @file
23  */
24
25 #include <string.h>
26
27 #include <libvscmisc/assert.h>
28 #include <libvscmisc/error.h>
29 #include <libvscmisc/list.h>
30 #include <libvscmisc/memory.h>
31 #include <libvscmisc/string.h>
32
33 #include "../include/libvsccli/option.h"
34 #include "option.h"
35 #include "token.h"
36
37 const char *
38 _vsc_cli_option_syntax (const struct VscCliOptionInfo *option_info,
39                         int synopsis)
40 {
41         const char *type = NULL;
42         static char syntax[512];
43
44         VSC__ASSERT (option_info != NULL);
45
46         if (option_info->flags & VSC_CLI__OPTION_FLAG__TAGGED) {
47                 if (option_info->type == VSC_CLI__OPTION_TYPE__BOOLEAN) {
48                         if (synopsis) {
49                                 snprintf (syntax, 512, "--%s", option_info->long_name);
50                         } else {
51                                 if (option_info->short_name > 0) {
52                                         snprintf (syntax, 512, "--%s, -%c", option_info->long_name,
53                                                   option_info->short_name);
54                                 } else {
55                                         snprintf (syntax, 512, "--%s", option_info->long_name);
56                                 }
57                         }
58                 } else {
59                         switch (option_info->type) {
60                         case VSC_CLI__OPTION_TYPE__BOOLEAN:
61                                 type = "";
62                                 break;
63
64                         case VSC_CLI__OPTION_TYPE__NUMBER:
65                                 type = "<number>";
66                                 break;
67
68                         case VSC_CLI__OPTION_TYPE__STRING:
69                                 type = "<string>";
70                                 break;
71
72                         default:
73                                 type = "<INVALID>";
74                                 break;
75                         }
76
77                         if (synopsis) {
78                                 snprintf (syntax, 512, "--%s %s", option_info->long_name,
79                                           type);
80                         } else {
81                                 if (option_info->short_name > 0) {
82                                         snprintf (syntax, 512, "--%s %s, -%c %s",
83                                                   option_info->long_name, type,
84                                                   option_info->short_name, type);
85                                 } else {
86                                         snprintf (syntax, 512, "--%s %s", option_info->long_name,
87                                                   type);
88                                 }
89                         }
90                 }
91         } else {
92                 snprintf (syntax, 512, "<%s>", option_info->long_name);
93         }
94
95         return syntax;
96 }
97
98 int /* option_parsed */
99 _vsc_cli_option_parse (struct VscError *error,
100                        const struct VscCliOptionInfo *option_infos,
101                        int untagged_option_index, const char *command_name,
102                        const char *input, const char **input_tail,
103                        struct VscCliOption *option)
104 {
105         int option_parsed = TRUE;
106         const char *tail = NULL;
107         char *token = NULL;
108         int num_dashes = 0;
109
110         VSC__ASSERT (error != NULL);
111         VSC__ASSERT (! error->occured);
112         VSC__ASSERT (option_infos != NULL);
113         VSC__ASSERT (input != NULL);
114         VSC__ASSERT (input_tail != NULL);
115         VSC__ASSERT (option != NULL);
116
117         *input_tail = input;
118
119         _vsc_cli_token_parse (error, input, &token, &tail);
120
121         if (error->occured) {
122                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
123                 goto failure;
124         }
125
126         if (token == NULL) {
127                 return FALSE;
128         }
129
130         memset (option, 0, sizeof (struct VscCliOption));
131
132         if (*token == '-') {
133                 ++num_dashes;
134
135                 if (*(token + 1) == '-') {
136                         ++num_dashes;
137                 }
138         }
139
140         if (num_dashes > 0) {
141                 if (num_dashes == 1 && *(token + 2) != '\0') {
142                         VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
143                                      "Invalid option syntax: %s", token);
144                         goto failure;
145                 }
146
147                 option->info = _vsc_cli_option_infos_lookup_tagged
148                                   (option_infos, token + num_dashes, num_dashes > 1);
149
150                 if (option->info == NULL) {
151                         if (command_name != NULL) {
152                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
153                                              "Unknown option %s for command '%s'",
154                                              token, command_name);
155                         } else  {
156                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
157                                              "Unknown option %s", token);
158                         }
159
160                         goto failure;
161                 }
162
163                 switch (option->info->type) {
164                 case VSC_CLI__OPTION_TYPE__BOOLEAN:
165                         break;
166
167                 case VSC_CLI__OPTION_TYPE__NUMBER:
168                         vsc_free (&token);
169                         _vsc_cli_token_parse (error, tail, &token, &tail);
170
171                         if (error->occured) {
172                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
173                                 goto failure;
174                         }
175
176                         if (token == NULL) {
177                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
178                                              "Expected option syntax: %s",
179                                              _vsc_cli_option_syntax (option->info, FALSE));
180                                 goto failure;
181                         }
182
183                         option->number = vsc_strtoi (error, token, NULL, 10);
184
185                         if (error->occured) {
186                                 vsc_error_cleanup (error);
187                                 vsc_error_init (error);
188
189                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
190                                              "Expected option syntax: %s",
191                                              _vsc_cli_option_syntax (option->info, FALSE));
192                                 goto failure;
193                         }
194
195                         break;
196
197                 case VSC_CLI__OPTION_TYPE__STRING:
198                         vsc_free (&token);
199                         _vsc_cli_token_parse (error, tail, &token, &tail);
200
201                         if (error->occured) {
202                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
203                                 goto failure;
204                         }
205
206                         if (token == NULL) {
207                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
208                                              "Expected option syntax: %s",
209                                              _vsc_cli_option_syntax (option->info, FALSE));
210                                 goto failure;
211                         }
212
213                         option->string = token;
214                         token = NULL;
215                         break;
216
217                 default:
218                         VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
219                                      "Internal error");
220                         goto failure;
221                 }
222
223                 if (error->occured) {
224                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
225                         goto failure;
226                 }
227         } else if (untagged_option_index >= 0) {
228                 option->info = _vsc_cli_option_infos_lookup_untagged
229                                   (option_infos, untagged_option_index);
230
231                 if (option->info == NULL) {
232                         VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
233                                      "Too many untagged options for command '%s'",
234                                      command_name);
235                         goto failure;
236                 }
237
238                 switch (option->info->type) {
239                 case VSC_CLI__OPTION_TYPE__BOOLEAN:
240                         VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_ARGUMENT,
241                                      "Boolean options can not untagged");
242
243                         goto failure;
244
245                 case VSC_CLI__OPTION_TYPE__NUMBER:
246                         option->number = vsc_strtoi (error, token, NULL, 10);
247
248                         if (error->occured) {
249                                 vsc_error_cleanup (error);
250                                 vsc_error_init (error);
251
252                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
253                                              "Expected option syntax: %s",
254                                              _vsc_cli_option_syntax (option->info, FALSE));
255                                 goto failure;
256                         }
257
258                         break;
259
260                 case VSC_CLI__OPTION_TYPE__STRING:
261                         option->string = token;
262                         token = NULL;
263                         break;
264
265                 default:
266                         VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
267                                      "Internal error");
268                         goto failure;
269                 }
270
271                 if (error->occured) {
272                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
273                         goto failure;
274                 }
275         } else {
276                 option_parsed = FALSE;
277         }
278
279         if (option_parsed) {
280                 *input_tail = tail;
281         }
282
283 cleanup:
284         vsc_free (&token);
285
286         return option_parsed;
287
288 failure:
289         vsc_free (&option->string);
290         memset (option, 0, sizeof (struct VscCliOption));
291
292         option_parsed = FALSE;
293
294         goto cleanup;
295 }
296
297 const struct VscCliOption *
298 vsc_cli_option_list_lookup (const struct VscCliOption *option_list,
299                             const char *option_long_name)
300 {
301         const struct VscCliOption *option;
302
303         VSC__ASSERT (option_long_name != NULL);
304
305         for (option = option_list; option != NULL; option = option->next) {
306                 if (strcmp (option->info->long_name, option_long_name) == 0) {
307                         return option;
308                 }
309         }
310
311         return NULL;
312 }
313
314 int
315 vsc_cli_option_list_lookup_boolean (const struct VscCliOption *option_list,
316                                     const char *option_long_name)
317 {
318         const struct VscCliOption *option;
319
320         option = vsc_cli_option_list_lookup (option_list, option_long_name);
321
322         if (option != NULL) {
323                 VSC__ASSERT (option->info->type == VSC_CLI__OPTION_TYPE__BOOLEAN);
324
325                 return TRUE;
326         } else {
327                 return FALSE;
328         }
329 }
330
331 int
332 vsc_cli_option_list_lookup_number (const struct VscCliOption *option_list,
333                                    const char *option_long_name, int *found)
334 {
335         const struct VscCliOption *option;
336
337         VSC__ASSERT (option_long_name != NULL);
338
339         if (found != NULL) {
340                 *found = FALSE;
341         }
342
343         option = vsc_cli_option_list_lookup (option_list, option_long_name);
344
345         if (option == NULL) {
346                 return 0;
347         }
348
349         VSC__ASSERT (option->info->type == VSC_CLI__OPTION_TYPE__NUMBER);
350
351         if (found != NULL) {
352                 *found = TRUE;
353         }
354
355         return option->number;
356 }
357
358 const char *
359 vsc_cli_option_list_lookup_string (const struct VscCliOption *option_list,
360                                    const char *option_long_name)
361 {
362         const struct VscCliOption *option;
363
364         VSC__ASSERT (option_long_name != NULL);
365
366         option = vsc_cli_option_list_lookup (option_list, option_long_name);
367
368         if (option == NULL) {
369                 return NULL;
370         }
371
372         VSC__ASSERT (option->info->type == VSC_CLI__OPTION_TYPE__STRING);
373
374         return option->string;
375 }
376
377 const struct VscCliOptionInfo *
378 _vsc_cli_option_infos_lookup_tagged
379    (const struct VscCliOptionInfo *option_infos, const char *option_name,
380     int is_long)
381 {
382         int i;
383
384         VSC__ASSERT (option_infos != NULL);
385         VSC__ASSERT (option_name != NULL);
386
387         for (i = 0; option_infos[i].long_name != NULL; i++) {
388                 if (! (option_infos[i].flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
389                         continue;
390                 }
391
392                 if (is_long) {
393                         if (strcmp (option_name, option_infos[i].long_name) == 0) {
394                                 return &option_infos[i];
395                         }
396                 } else {
397                         if (*option_name == option_infos[i].short_name) {
398                                 return &option_infos[i];
399                         }
400                 }
401         }
402
403         return NULL;
404 }
405
406 const struct VscCliOptionInfo *
407 _vsc_cli_option_infos_lookup_untagged
408    (const struct VscCliOptionInfo *option_infos, int position)
409 {
410         int i;
411
412         VSC__ASSERT (option_infos != NULL);
413         VSC__ASSERT (position >= 0);
414
415         for (i = 0; option_infos[i].long_name != NULL; i++) {
416                 if (! (option_infos[i].flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
417                         if (position > 0) {
418                                 --position;
419                         } else {
420                                 return &option_infos[i];
421                         }
422                 }
423         }
424
425         return NULL;
426 }
427
428 static void
429 _option_free (struct VscCliOption **option)
430 {
431         VSC__ASSERT (option != NULL);
432
433         if (*option != NULL) {
434                 vsc_free (&(*option)->string);
435         }
436
437         vsc_free (option);
438 }
439
440 static struct VscCliOption * /* option_duplicate */
441 _option_duplicate (struct VscError *error, const struct VscCliOption *option)
442 {
443         struct VscCliOption *option_duplicate = NULL;
444
445         VSC__ASSERT (error != NULL);
446         VSC__ASSERT (! error->occured);
447         VSC__ASSERT (option != NULL);
448
449         option_duplicate = vsc_memdup (error, option, sizeof (struct VscCliOption));
450
451         if (error->occured) {
452                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
453                 goto failure;
454         }
455
456         if (option->string != NULL) {
457                 option_duplicate->string = vsc_strdup (error, option->string);
458         }
459
460         if (error->occured) {
461                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
462                 goto failure;
463         }
464
465         option_duplicate->next = NULL;
466
467         return option_duplicate;
468
469 failure:
470         _option_free (&option_duplicate);
471
472         return NULL;
473 }
474
475 static int
476 _option_match (struct VscList *item, void *match_data)
477 {
478         struct VscCliOption *option = (struct VscCliOption *) item;
479         const char *option_long_name = (const char *) match_data;
480
481         VSC__ASSERT (item != NULL);
482
483         return strcmp (option->info->long_name, option_long_name) == 0;
484 }
485
486 void
487 _vsc_cli_option_list_append (struct VscError *error,
488                              struct VscCliOption **option_list,
489                              const struct VscCliOption *option)
490 {
491         VSC__ASSERT (error != NULL);
492         VSC__ASSERT (! error->occured);
493         VSC__ASSERT (option_list != NULL);
494         VSC__ASSERT (option != NULL);
495
496         vsc_list_append
497            (error, (struct VscList **) option_list,
498             (struct VscList *) option,
499             (VscList_DuplicateFunction) _option_duplicate);
500
501         if (error->occured) {
502                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
503                 return;
504         }
505 }
506
507 void
508 vsc_cli_option_list_free (struct VscCliOption **option_list)
509 {
510         VSC__ASSERT (option_list != NULL);
511
512         vsc_list_free ((struct VscList **) option_list,
513                        (VscList_FreeFunction) _option_free);
514 }
515
516 struct VscCliOption * /* option_list_duplicate */
517 _vsc_cli_option_list_duplicate (struct VscError *error,
518                                 const struct VscCliOption *option_list)
519 {
520         struct VscCliOption *option_list_duplicate = NULL;
521
522         VSC__ASSERT (error != NULL);
523         VSC__ASSERT (! error->occured);
524
525         option_list_duplicate = (struct VscCliOption *)
526            vsc_list_duplicate
527               (error, (struct VscList *) option_list,
528                (VscList_DuplicateFunction) _option_duplicate,
529                (VscList_FreeFunction) _option_free);
530
531         if (error->occured) {
532                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
533                 return NULL;
534         }
535
536         return option_list_duplicate;
537 }
538
539 struct VscCliOption *
540 _vsc_cli_option_list_lookup (const struct VscCliOption *option_list,
541                              const char *option_long_name)
542 {
543         VSC__ASSERT (option_long_name != NULL);
544
545         return (struct VscCliOption *)
546                   vsc_list_lookup
547                      ((struct VscList *) option_list,
548                       (VscList_MatchFunction) _option_match,
549                       (void *) option_long_name);
550 }