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