[misc] Add error code 'GENERIC_BADNESS'
[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__APPEND_ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
187                                                     "Expected option syntax: %s",
188                                                     _vsc_cli_option_syntax (option->info, FALSE));
189                                 goto failure;
190                         }
191
192                         break;
193
194                 case VSC_CLI__OPTION_TYPE__STRING:
195                         vsc_free (&token);
196                         _vsc_cli_token_parse (error, tail, &token, &tail);
197
198                         if (error->occured) {
199                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
200                                 goto failure;
201                         }
202
203                         if (token == NULL) {
204                                 VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
205                                              "Expected option syntax: %s",
206                                              _vsc_cli_option_syntax (option->info, FALSE));
207                                 goto failure;
208                         }
209
210                         option->string = token;
211                         token = NULL;
212                         break;
213
214                 default:
215                         VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
216                                      "Internal error");
217                         goto failure;
218                 }
219
220                 if (error->occured) {
221                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
222                         goto failure;
223                 }
224         } else if (untagged_option_index >= 0) {
225                 option->info = _vsc_cli_option_infos_lookup_untagged
226                                   (option_infos, untagged_option_index);
227
228                 if (option->info == NULL) {
229                         VSC__ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
230                                      "Too many untagged options for command '%s'",
231                                      command_name);
232                         goto failure;
233                 }
234
235                 switch (option->info->type) {
236                 case VSC_CLI__OPTION_TYPE__BOOLEAN:
237                         VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_ARGUMENT,
238                                      "Boolean options can not untagged");
239
240                         goto failure;
241
242                 case VSC_CLI__OPTION_TYPE__NUMBER:
243                         option->number = vsc_strtoi (error, token, NULL, 10);
244
245                         if (error->occured) {
246                                 VSC__APPEND_ERROR2 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
247                                                     "Expected option syntax: %s",
248                                                     _vsc_cli_option_syntax (option->info, FALSE));
249                                 goto failure;
250                         }
251
252                         break;
253
254                 case VSC_CLI__OPTION_TYPE__STRING:
255                         option->string = token;
256                         token = NULL;
257                         break;
258
259                 default:
260                         VSC__ERROR1 (error, VSC__ERROR_CODE__INTERNAL_ERROR,
261                                      "Internal error");
262                         goto failure;
263                 }
264
265                 if (error->occured) {
266                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
267                         goto failure;
268                 }
269         } else {
270                 option_parsed = FALSE;
271         }
272
273         if (option_parsed) {
274                 *input_tail = tail;
275         }
276
277 cleanup:
278         vsc_free (&token);
279
280         return option_parsed;
281
282 failure:
283         vsc_free (&option->string);
284         memset (option, 0, sizeof (struct VscCliOption));
285
286         option_parsed = FALSE;
287
288         goto cleanup;
289 }
290
291 const struct VscCliOption *
292 vsc_cli_option_list_lookup (const struct VscCliOption *option_list,
293                             const char *option_long_name)
294 {
295         const struct VscCliOption *option;
296
297         VSC__ASSERT (option_long_name != NULL);
298
299         for (option = option_list; option != NULL; option = option->next) {
300                 if (strcmp (option->info->long_name, option_long_name) == 0) {
301                         return option;
302                 }
303         }
304
305         return NULL;
306 }
307
308 int
309 vsc_cli_option_list_lookup_boolean (const struct VscCliOption *option_list,
310                                     const char *option_long_name)
311 {
312         const struct VscCliOption *option;
313
314         option = vsc_cli_option_list_lookup (option_list, option_long_name);
315
316         if (option != NULL) {
317                 VSC__ASSERT (option->info->type == VSC_CLI__OPTION_TYPE__BOOLEAN);
318
319                 return TRUE;
320         } else {
321                 return FALSE;
322         }
323 }
324
325 int
326 vsc_cli_option_list_lookup_number (const struct VscCliOption *option_list,
327                                    const char *option_long_name, int *found)
328 {
329         const struct VscCliOption *option;
330
331         VSC__ASSERT (option_long_name != NULL);
332
333         if (found != NULL) {
334                 *found = FALSE;
335         }
336
337         option = vsc_cli_option_list_lookup (option_list, option_long_name);
338
339         if (option == NULL) {
340                 return 0;
341         }
342
343         VSC__ASSERT (option->info->type == VSC_CLI__OPTION_TYPE__NUMBER);
344
345         if (found != NULL) {
346                 *found = TRUE;
347         }
348
349         return option->number;
350 }
351
352 const char *
353 vsc_cli_option_list_lookup_string (const struct VscCliOption *option_list,
354                                    const char *option_long_name)
355 {
356         const struct VscCliOption *option;
357
358         VSC__ASSERT (option_long_name != NULL);
359
360         option = vsc_cli_option_list_lookup (option_list, option_long_name);
361
362         if (option == NULL) {
363                 return NULL;
364         }
365
366         VSC__ASSERT (option->info->type == VSC_CLI__OPTION_TYPE__STRING);
367
368         return option->string;
369 }
370
371 const struct VscCliOptionInfo *
372 _vsc_cli_option_infos_lookup_tagged
373    (const struct VscCliOptionInfo *option_infos, const char *option_name,
374     int is_long)
375 {
376         int i;
377
378         VSC__ASSERT (option_infos != NULL);
379         VSC__ASSERT (option_name != NULL);
380
381         for (i = 0; option_infos[i].long_name != NULL; i++) {
382                 if (! (option_infos[i].flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
383                         continue;
384                 }
385
386                 if (is_long) {
387                         if (strcmp (option_name, option_infos[i].long_name) == 0) {
388                                 return &option_infos[i];
389                         }
390                 } else {
391                         if (*option_name == option_infos[i].short_name) {
392                                 return &option_infos[i];
393                         }
394                 }
395         }
396
397         return NULL;
398 }
399
400 const struct VscCliOptionInfo *
401 _vsc_cli_option_infos_lookup_untagged
402    (const struct VscCliOptionInfo *option_infos, int position)
403 {
404         int i;
405
406         VSC__ASSERT (option_infos != NULL);
407         VSC__ASSERT (position >= 0);
408
409         for (i = 0; option_infos[i].long_name != NULL; i++) {
410                 if (! (option_infos[i].flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
411                         if (position > 0) {
412                                 --position;
413                         } else {
414                                 return &option_infos[i];
415                         }
416                 }
417         }
418
419         return NULL;
420 }
421
422 static void
423 _option_free (struct VscCliOption **option)
424 {
425         VSC__ASSERT (option != NULL);
426
427         if (*option != NULL) {
428                 vsc_free (&(*option)->string);
429         }
430
431         vsc_free (option);
432 }
433
434 static struct VscCliOption * /* option_clone */
435 _option_clone (struct VscError *error, const struct VscCliOption *option)
436 {
437         struct VscCliOption *option_clone = NULL;
438
439         VSC__ASSERT (error != NULL);
440         VSC__ASSERT (! error->occured);
441         VSC__ASSERT (option != NULL);
442
443         option_clone = vsc_memdup (error, option, sizeof (struct VscCliOption));
444
445         if (error->occured) {
446                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
447                 goto failure;
448         }
449
450         option_clone->next = NULL;
451
452         if (option->string != NULL) {
453                 option_clone->string = vsc_strdup (error, option->string);
454         }
455
456         if (error->occured) {
457                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
458                 goto failure;
459         }
460
461         return option_clone;
462
463 failure:
464         _option_free (&option_clone);
465
466         return NULL;
467 }
468
469 static int
470 _option_match (struct VscList *item, void *match_data)
471 {
472         struct VscCliOption *option = (struct VscCliOption *) item;
473         const char *option_long_name = (const char *) match_data;
474
475         VSC__ASSERT (item != NULL);
476
477         return strcmp (option->info->long_name, option_long_name) == 0;
478 }
479
480 void
481 _vsc_cli_option_list_append_clone (struct VscError *error,
482                                    struct VscCliOption **option_list,
483                                    const struct VscCliOption *option)
484 {
485         VSC__ASSERT (error != NULL);
486         VSC__ASSERT (! error->occured);
487         VSC__ASSERT (option_list != NULL);
488         VSC__ASSERT (option != NULL);
489
490         vsc_list_append_clone
491            (error, (struct VscList **) option_list,
492             (struct VscList *) option, (VscList_CloneFunction) _option_clone);
493
494         if (error->occured) {
495                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
496                 return;
497         }
498 }
499
500 void
501 vsc_cli_option_list_free (struct VscCliOption **option_list)
502 {
503         VSC__ASSERT (option_list != NULL);
504
505         vsc_list_free ((struct VscList **) option_list,
506                        (VscList_FreeFunction) _option_free);
507 }
508
509 struct VscCliOption * /* option_list_clone */
510 _vsc_cli_option_list_clone (struct VscError *error,
511                             const struct VscCliOption *option_list)
512 {
513         struct VscCliOption *option_list_clone = NULL;
514
515         VSC__ASSERT (error != NULL);
516         VSC__ASSERT (! error->occured);
517
518         option_list_clone = (struct VscCliOption *)
519            vsc_list_clone
520               (error, (struct VscList *) option_list,
521                (VscList_CloneFunction) _option_clone,
522                (VscList_FreeFunction) _option_free);
523
524         if (error->occured) {
525                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
526                 return NULL;
527         }
528
529         return option_list_clone;
530 }
531
532 struct VscCliOption *
533 _vsc_cli_option_list_lookup (const struct VscCliOption *option_list,
534                              const char *option_long_name)
535 {
536         VSC__ASSERT (option_long_name != NULL);
537
538         return (struct VscCliOption *)
539                   vsc_list_lookup
540                      ((struct VscList *) option_list,
541                       (VscList_MatchFunction) _option_match,
542                       (void *) option_long_name);
543 }