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