1 |
/* Creation date: 2005-09-04 12:57:25
|
2 |
* Authors: Don
|
3 |
* Change log:
|
4 |
*/
|
5 |
|
6 |
#include "cfu.h"
|
7 |
#include "cfuopt.h"
|
8 |
#include "cfuhash.h"
|
9 |
#include "cfulist.h"
|
10 |
#include "cfustring.h"
|
11 |
|
12 |
#include <stdlib.h>
|
13 |
|
14 |
#ifdef CFU_DEBUG
|
15 |
#ifdef NDEBUG
|
16 |
#undef NDEBUG
|
17 |
#endif
|
18 |
#else
|
19 |
#ifndef NDEBUG
|
20 |
#define NDEBUG 1
|
21 |
#endif
|
22 |
#endif
|
23 |
#include <assert.h>
|
24 |
|
25 |
struct cfuopt_struct {
|
26 |
libcfu_type type;
|
27 |
cfulist_t *option_list;
|
28 |
cfuhash_table_t *option_map;
|
29 |
cfulist_t *extra;
|
30 |
char *progname;
|
31 |
};
|
32 |
|
33 |
typedef enum {
|
34 |
cfuopt_arg_invalid = 0,
|
35 |
cfuopt_arg_bool,
|
36 |
cfuopt_arg_string,
|
37 |
cfuopt_arg_int,
|
38 |
cfuopt_arg_float,
|
39 |
cfuopt_arg_string_array
|
40 |
} cfuopt_arg_t;
|
41 |
|
42 |
typedef struct cfuopt_list_entry {
|
43 |
const char *arg_data;
|
44 |
const char *description;
|
45 |
const char *arg_description;
|
46 |
cfuopt_arg_t arg_type;
|
47 |
int required;
|
48 |
cfulist_t *param_names;
|
49 |
} cfuopt_list_entry_t;
|
50 |
|
51 |
#define CFU_OPT_ALLOC(type, count) (type *)calloc(count, sizeof(type));
|
52 |
|
53 |
extern cfuopt_t *
|
54 |
cfuopt_new() {
|
55 |
cfuopt_t *context = (cfuopt_t *)calloc(1, sizeof(cfuopt_t));
|
56 |
context->option_list = cfulist_new();
|
57 |
context->option_map = cfuhash_new();
|
58 |
context->extra = cfulist_new();
|
59 |
return context;
|
60 |
}
|
61 |
|
62 |
#define CFUOPT_SET_TYPE(spec, type_var) \
|
63 |
switch (spec) { \
|
64 |
case 's': \
|
65 |
type_var = cfuopt_arg_string; \
|
66 |
break; \
|
67 |
case 'i': \
|
68 |
type_var = cfuopt_arg_int; \
|
69 |
break; \
|
70 |
case 'f': \
|
71 |
type_var = cfuopt_arg_float; \
|
72 |
break; \
|
73 |
case '!': \
|
74 |
type_var = cfuopt_arg_bool; \
|
75 |
default: \
|
76 |
break; \
|
77 |
}
|
78 |
|
79 |
|
80 |
/*
|
81 |
Parse a string like Perl's Getopt::Long takes, e.g.
|
82 |
|
83 |
"verbose|v!",
|
84 |
"file|f=s",
|
85 |
"scale:f",
|
86 |
"count:i"
|
87 |
|
88 |
! means boolean flag
|
89 |
= means required
|
90 |
: means optional
|
91 |
s means arbitrary string (C string)
|
92 |
f means float (double)
|
93 |
i means integer (long)
|
94 |
|
95 |
*/
|
96 |
static void
|
97 |
parse_opt_str(const char *opt_str, cfulist_t **param_list, cfuopt_arg_t *type, int *is_required) {
|
98 |
cfulist_t *params = cfulist_new();
|
99 |
size_t num_names = 0;
|
100 |
char **opt_names = cfustring_c_str_split(opt_str, &num_names, 0, "|", NULL);
|
101 |
char *last_opt = NULL;
|
102 |
char *pos = NULL;
|
103 |
size_t opt_len = 0;
|
104 |
cfuopt_arg_t arg_type = cfuopt_arg_invalid;
|
105 |
int required = 0;
|
106 |
size_t i = 0;
|
107 |
|
108 |
if (num_names == 0) {
|
109 |
cfulist_destroy(params);
|
110 |
free(opt_names);
|
111 |
return;
|
112 |
}
|
113 |
|
114 |
last_opt = opt_names[num_names - 1];
|
115 |
opt_len = strlen(last_opt);
|
116 |
if ( (pos = memchr((void *)last_opt, '=', opt_len)) ) {
|
117 |
char *tmp = pos;
|
118 |
required = 1;
|
119 |
tmp++;
|
120 |
if (*tmp) {
|
121 |
CFUOPT_SET_TYPE(*tmp, arg_type);
|
122 |
*pos = '\000';
|
123 |
}
|
124 |
else {
|
125 |
arg_type = cfuopt_arg_invalid;
|
126 |
}
|
127 |
}
|
128 |
else if ( (pos = memchr((void *)last_opt, ':', opt_len)) ) {
|
129 |
char *tmp = pos;
|
130 |
required = 0;
|
131 |
tmp++;
|
132 |
if (*tmp) {
|
133 |
CFUOPT_SET_TYPE(*tmp, arg_type);
|
134 |
*pos = '\000';
|
135 |
}
|
136 |
else {
|
137 |
arg_type = cfuopt_arg_invalid;
|
138 |
}
|
139 |
}
|
140 |
else if ( (pos = memchr((void *)last_opt, '!', opt_len)) ) {
|
141 |
required = 0;
|
142 |
arg_type = cfuopt_arg_bool;
|
143 |
*pos = '\000';
|
144 |
}
|
145 |
|
146 |
for (i = 0; i < num_names; i++) {
|
147 |
cfulist_push(params, opt_names[i]);
|
148 |
}
|
149 |
|
150 |
free(opt_names);
|
151 |
*type = arg_type;
|
152 |
*param_list = params;
|
153 |
*is_required = required;
|
154 |
}
|
155 |
|
156 |
static void
|
157 |
_simple_list_free_fn(void *data) {
|
158 |
free(data);
|
159 |
}
|
160 |
|
161 |
struct _add_entry_struct {
|
162 |
cfuopt_t *context;
|
163 |
cfuopt_list_entry_t *entry;
|
164 |
};
|
165 |
|
166 |
static int
|
167 |
_add_to_option_map(void *data, size_t data_size, void *arg) {
|
168 |
struct _add_entry_struct *es = (struct _add_entry_struct *)arg;
|
169 |
data_size = data_size;
|
170 |
|
171 |
cfuhash_put(es->context->option_map, (char *)data, (void *)es->entry);
|
172 |
|
173 |
return 0;
|
174 |
}
|
175 |
|
176 |
extern void
|
177 |
cfuopt_add_entry(cfuopt_t *context, const char *opt_str, void *arg_data,
|
178 |
const char *description, const char *arg_description) {
|
179 |
cfuopt_list_entry_t *entry = CFU_OPT_ALLOC(cfuopt_list_entry_t, 1);
|
180 |
cfulist_t *param_list = NULL;
|
181 |
cfuopt_arg_t arg_type = cfuopt_arg_invalid;
|
182 |
struct _add_entry_struct entry_struct;
|
183 |
int required = 0;
|
184 |
|
185 |
parse_opt_str(opt_str, ¶m_list, &arg_type, &required);
|
186 |
|
187 |
entry->arg_data = arg_data;
|
188 |
entry->description = description;
|
189 |
entry->arg_description = arg_description;
|
190 |
entry->arg_type = arg_type;
|
191 |
entry->required = required;
|
192 |
entry->param_names = param_list;
|
193 |
|
194 |
cfulist_push(context->option_list, (void *)entry);
|
195 |
|
196 |
entry_struct.entry = entry;
|
197 |
entry_struct.context = context;
|
198 |
|
199 |
cfulist_foreach(param_list, _add_to_option_map, &entry_struct);
|
200 |
}
|
201 |
|
202 |
static void
|
203 |
check_arg(const char *arg, int *is_long, int *is_short, int *is_data, int *is_end_of_options,
|
204 |
char **parsed_arg, const char **value) {
|
205 |
const char *ptr = NULL;
|
206 |
|
207 |
ptr = arg;
|
208 |
*is_long = 0;
|
209 |
*is_short = 0;
|
210 |
*is_data = 0;
|
211 |
*is_end_of_options = 0;
|
212 |
*parsed_arg = NULL;
|
213 |
*value = NULL;
|
214 |
|
215 |
if (!ptr || !*ptr) return;
|
216 |
|
217 |
if (*ptr != '-') {
|
218 |
*is_data = 1;
|
219 |
*parsed_arg = cfustring_dup_c_str(ptr);
|
220 |
return;
|
221 |
}
|
222 |
ptr++;
|
223 |
if (!*ptr) {
|
224 |
*is_data = 1;
|
225 |
*parsed_arg = cfustring_dup_c_str(ptr - 1);
|
226 |
return;
|
227 |
}
|
228 |
if (*ptr == '-') {
|
229 |
const char *val_ptr = NULL;
|
230 |
ptr++;
|
231 |
if (!*ptr) {
|
232 |
*is_end_of_options = 1;
|
233 |
return;
|
234 |
}
|
235 |
*is_long = 1;
|
236 |
*parsed_arg = cfustring_dup_c_str(ptr);
|
237 |
val_ptr = ptr;
|
238 |
for (val_ptr = ptr; *val_ptr && *val_ptr != '='; val_ptr++);
|
239 |
if (*val_ptr == '=') {
|
240 |
(*parsed_arg)[val_ptr - ptr] = '\000';
|
241 |
val_ptr++;
|
242 |
*value = val_ptr;
|
243 |
}
|
244 |
return;
|
245 |
} else {
|
246 |
*is_short = 1;
|
247 |
*parsed_arg = cfustring_dup_c_str(ptr);
|
248 |
return;
|
249 |
}
|
250 |
}
|
251 |
|
252 |
static void
|
253 |
_set_entry_val(cfuopt_list_entry_t *entry, const char *value) {
|
254 |
switch (entry->arg_type) {
|
255 |
case cfuopt_arg_bool:
|
256 |
if (entry->arg_data) *((int *)entry->arg_data) = 1;
|
257 |
break;
|
258 |
case cfuopt_arg_int:
|
259 |
if (entry->arg_data) *((int *)entry->arg_data) = atol((char *)value);
|
260 |
break;
|
261 |
case cfuopt_arg_float:
|
262 |
if (entry->arg_data) *((double *)entry->arg_data) = atof((char *)value);
|
263 |
break;
|
264 |
case cfuopt_arg_string:
|
265 |
if (entry->arg_data) *((char **)entry->arg_data) = cfustring_dup_c_str((char *)value);
|
266 |
break;
|
267 |
default:
|
268 |
break;
|
269 |
}
|
270 |
|
271 |
}
|
272 |
|
273 |
typedef struct {
|
274 |
int count;
|
275 |
char **argv;
|
276 |
} _update_extra_ds;
|
277 |
|
278 |
static int
|
279 |
_update_extra(void *data, size_t data_size, void *arg) {
|
280 |
_update_extra_ds *ds = (_update_extra_ds *)arg;
|
281 |
data_size = data_size;
|
282 |
ds->argv[ds->count] = (char *)data;
|
283 |
ds->count++;
|
284 |
|
285 |
return 0;
|
286 |
}
|
287 |
|
288 |
extern void
|
289 |
cfuopt_parse(cfuopt_t *context, int *argc, char ***argv, char **error) {
|
290 |
int i = 0;
|
291 |
char **args = *argv;
|
292 |
int is_long_opt = 0;
|
293 |
int is_short_opt = 0;
|
294 |
int is_data = 0;
|
295 |
int is_end_of_opt = 0;
|
296 |
char *parsed_arg = NULL;
|
297 |
const char *value = NULL;
|
298 |
cfuopt_list_entry_t *entry = NULL;
|
299 |
size_t extra_count = 0;
|
300 |
|
301 |
error = error;
|
302 |
|
303 |
if (!context) return;
|
304 |
if (*argc < 1) return;
|
305 |
|
306 |
context->progname = cfustring_dup_c_str(args[0]);
|
307 |
|
308 |
if (*argc < 2) return;
|
309 |
|
310 |
for (i = 1; i < *argc; i++) {
|
311 |
char *cur_arg = args[i];
|
312 |
entry = NULL;
|
313 |
value = NULL;
|
314 |
|
315 |
if (parsed_arg) free(parsed_arg);
|
316 |
parsed_arg = NULL;
|
317 |
|
318 |
check_arg(cur_arg, &is_long_opt, &is_short_opt, &is_data, &is_end_of_opt, &parsed_arg,
|
319 |
&value);
|
320 |
if (is_long_opt || is_short_opt) {
|
321 |
entry = (cfuopt_list_entry_t *)cfuhash_get(context->option_map, parsed_arg);
|
322 |
if (parsed_arg) free(parsed_arg);
|
323 |
parsed_arg = NULL;
|
324 |
}
|
325 |
else if (is_end_of_opt) {
|
326 |
if (parsed_arg) free(parsed_arg);
|
327 |
parsed_arg = NULL;
|
328 |
i++;
|
329 |
for (; i < *argc; i++) {
|
330 |
cfulist_push(context->extra, args[i]);
|
331 |
}
|
332 |
break;
|
333 |
}
|
334 |
else if (is_data) {
|
335 |
if (parsed_arg) free(parsed_arg);
|
336 |
parsed_arg = NULL;
|
337 |
cfulist_push(context->extra, args[i]);
|
338 |
continue;
|
339 |
}
|
340 |
|
341 |
if (!entry) {
|
342 |
/* FIXME: return error here if need be */
|
343 |
continue;
|
344 |
}
|
345 |
|
346 |
switch (entry->arg_type) {
|
347 |
case cfuopt_arg_bool:
|
348 |
_set_entry_val(entry, "1");
|
349 |
break;
|
350 |
case cfuopt_arg_string:
|
351 |
case cfuopt_arg_int:
|
352 |
case cfuopt_arg_float:
|
353 |
if (value) {
|
354 |
if (entry->arg_data) {
|
355 |
_set_entry_val(entry, value);
|
356 |
}
|
357 |
} else {
|
358 |
i++;
|
359 |
if (i >= *argc) break; /* FIXME: set up error msg here */
|
360 |
check_arg(args[i], &is_long_opt, &is_short_opt, &is_data, &is_end_of_opt,
|
361 |
&parsed_arg, &value);
|
362 |
if (!is_data) {
|
363 |
i--;
|
364 |
break; /* FIXME: set up error msg here */
|
365 |
}
|
366 |
_set_entry_val(entry, parsed_arg);
|
367 |
free(parsed_arg);
|
368 |
parsed_arg = NULL;
|
369 |
}
|
370 |
break;
|
371 |
case cfuopt_arg_string_array:
|
372 |
break;
|
373 |
case cfuopt_arg_invalid:
|
374 |
/* FIXME: really should produce an error msg here */
|
375 |
break;
|
376 |
default:
|
377 |
break;
|
378 |
}
|
379 |
|
380 |
}
|
381 |
|
382 |
extra_count = cfulist_num_entries(context->extra);
|
383 |
*argc = extra_count + 1;
|
384 |
{
|
385 |
_update_extra_ds ds;
|
386 |
size_t update_count = 0;
|
387 |
ds.count = 1;
|
388 |
ds.argv = args;
|
389 |
update_count = cfulist_foreach(context->extra, _update_extra, (void *)&ds);
|
390 |
assert(update_count + 1 == (unsigned)*argc);
|
391 |
}
|
392 |
}
|
393 |
|
394 |
static void
|
395 |
_opt_list_free_fn(void *data) {
|
396 |
cfuopt_list_entry_t *entry = (cfuopt_list_entry_t *)data;
|
397 |
cfulist_destroy_with_free_fn(entry->param_names, _simple_list_free_fn);
|
398 |
free(data);
|
399 |
}
|
400 |
|
401 |
extern void
|
402 |
cfuopt_destroy(cfuopt_t *context) {
|
403 |
if (!context) return;
|
404 |
cfulist_destroy_with_free_fn(context->option_list, _opt_list_free_fn);
|
405 |
cfuhash_destroy(context->option_map);
|
406 |
cfulist_destroy(context->extra);
|
407 |
free(context->progname);
|
408 |
free(context);
|
409 |
}
|
410 |
|
411 |
#define ARG_TYPE_TO_STR(type, str) \
|
412 |
switch(type) { \
|
413 |
case cfuopt_arg_invalid: \
|
414 |
str = "INVALID"; \
|
415 |
break; \
|
416 |
case cfuopt_arg_bool: \
|
417 |
str = "BOOL"; \
|
418 |
break; \
|
419 |
case cfuopt_arg_string: \
|
420 |
str = "STRING"; \
|
421 |
break; \
|
422 |
case cfuopt_arg_int: \
|
423 |
str = "INT"; \
|
424 |
break; \
|
425 |
case cfuopt_arg_float: \
|
426 |
str = "FLOAT"; \
|
427 |
break; \
|
428 |
case cfuopt_arg_string_array: \
|
429 |
str = "STRING ARRAY"; \
|
430 |
break; \
|
431 |
default: \
|
432 |
str = "INVALID"; \
|
433 |
break; \
|
434 |
}
|
435 |
|
436 |
static int
|
437 |
_cfuopt_pretty_print_foreach(void *key, size_t key_size, void *data, size_t data_size, void *arg) {
|
438 |
char *name = (char *)key;
|
439 |
cfuopt_list_entry_t *list_entry = data;
|
440 |
char *str = NULL;
|
441 |
|
442 |
key_size = key_size;
|
443 |
data_size = data_size;
|
444 |
arg = arg;
|
445 |
ARG_TYPE_TO_STR(list_entry->arg_type, str);
|
446 |
printf("%s=%p (%s - %s) => %s, \"%s\"\n", name, (void *)list_entry, (list_entry->required ? "required" : "optional"), str, list_entry->description, list_entry->arg_description);
|
447 |
|
448 |
return 0;
|
449 |
}
|
450 |
|
451 |
extern void
|
452 |
cfuopt_pretty_print(cfuopt_t *context) {
|
453 |
cfuhash_foreach(context->option_map, _cfuopt_pretty_print_foreach, NULL);
|
454 |
}
|
455 |
|
456 |
static void
|
457 |
_list_simple_free_fn(void *data) {
|
458 |
free(data);
|
459 |
}
|
460 |
|
461 |
extern void *
|
462 |
_param_map_fn(void *data, size_t data_size, void *arg, size_t *new_data_size) {
|
463 |
char *name = (char *)data;
|
464 |
size_t len = 0;
|
465 |
cfuopt_list_entry_t *opt = (cfuopt_list_entry_t *)arg;
|
466 |
|
467 |
data_size = data_size;
|
468 |
if (!name) return NULL;
|
469 |
len = strlen(name);
|
470 |
*new_data_size = -1;
|
471 |
if (len == 0) return cfustring_dup_c_str("");
|
472 |
|
473 |
if (opt->arg_description && strlen(opt->arg_description) > 0) {
|
474 |
if (len == 1) {
|
475 |
return cfustring_sprintf_c_str("-%s %s", name, opt->arg_description);
|
476 |
}
|
477 |
else {
|
478 |
return cfustring_sprintf_c_str("--%s=%s", name, opt->arg_description);
|
479 |
}
|
480 |
}
|
481 |
else {
|
482 |
if (len == 1) {
|
483 |
return cfustring_sprintf_c_str("-%s", name);
|
484 |
}
|
485 |
else {
|
486 |
return cfustring_sprintf_c_str("--%s", name);
|
487 |
}
|
488 |
}
|
489 |
|
490 |
return NULL;
|
491 |
}
|
492 |
|
493 |
typedef struct _find_foreach_struct {
|
494 |
cfulist_t *desc_list;
|
495 |
size_t max_size;
|
496 |
} _find_foreach_ds;
|
497 |
|
498 |
typedef struct _cfuopt_desc_struct {
|
499 |
char *desc;
|
500 |
cfuopt_list_entry_t *entry;
|
501 |
} _cfuopt_desc;
|
502 |
|
503 |
static int
|
504 |
_find_foreach_fn(void *data, size_t data_size, void *arg) {
|
505 |
cfuopt_list_entry_t *opt = (cfuopt_list_entry_t *)data;
|
506 |
_find_foreach_ds *ds = (_find_foreach_ds *)arg;
|
507 |
size_t this_size = 0;
|
508 |
cfulist_t *param_full_list = NULL;
|
509 |
char *desc = NULL;
|
510 |
_cfuopt_desc *desc_ds = CFU_OPT_ALLOC(_cfuopt_desc, 1);
|
511 |
|
512 |
if (!data) return 0;
|
513 |
|
514 |
param_full_list = cfulist_map(opt->param_names, _param_map_fn, opt);
|
515 |
desc = cfulist_join(param_full_list, ", ");
|
516 |
|
517 |
data_size = data_size;
|
518 |
|
519 |
cfulist_destroy_with_free_fn(param_full_list, _simple_list_free_fn);
|
520 |
|
521 |
desc_ds->desc = desc;
|
522 |
desc_ds->entry = opt;
|
523 |
cfulist_push(ds->desc_list, desc_ds);
|
524 |
|
525 |
this_size = strlen(desc);
|
526 |
if (this_size > ds->max_size) ds->max_size = this_size;
|
527 |
|
528 |
return 0;
|
529 |
}
|
530 |
|
531 |
static size_t
|
532 |
_find_longest_help_desc(cfuopt_t *context, cfulist_t **desc_list) {
|
533 |
_find_foreach_ds ds;
|
534 |
ds.max_size = 0;
|
535 |
ds.desc_list = cfulist_new();
|
536 |
|
537 |
cfulist_foreach(context->option_list, _find_foreach_fn, (void *)&ds);
|
538 |
*desc_list = ds.desc_list;
|
539 |
|
540 |
return ds.max_size;
|
541 |
}
|
542 |
|
543 |
typedef struct {
|
544 |
char *fmt;
|
545 |
cfulist_t *list;
|
546 |
cfulist_t *desc_list;
|
547 |
} _help_lines_ds;
|
548 |
|
549 |
static int
|
550 |
_get_help_lines(void *data, size_t data_size, void *arg) {
|
551 |
_cfuopt_desc *desc_ds = (_cfuopt_desc *)data;
|
552 |
_help_lines_ds *ds = (_help_lines_ds *)arg;
|
553 |
char *left_col = NULL;
|
554 |
const char *desc = NULL;
|
555 |
char *line = NULL;
|
556 |
|
557 |
data_size = data_size;
|
558 |
|
559 |
left_col = desc_ds->desc;
|
560 |
|
561 |
if (!left_col) return 0;
|
562 |
|
563 |
desc = desc_ds->entry->description;
|
564 |
if (!desc) desc = "test desc";
|
565 |
|
566 |
line = cfustring_sprintf_c_str(ds->fmt, left_col, desc);
|
567 |
cfulist_push(ds->list, (void *)line);
|
568 |
|
569 |
return 0;
|
570 |
|
571 |
}
|
572 |
|
573 |
static void
|
574 |
_desc_list_free(void *data) {
|
575 |
_cfuopt_desc *desc_ds = (_cfuopt_desc *)data;
|
576 |
free(desc_ds->desc);
|
577 |
free(data);
|
578 |
}
|
579 |
|
580 |
extern char *
|
581 |
cfuopt_get_help_str(cfuopt_t *context) {
|
582 |
cfulist_t *desc_list = NULL;
|
583 |
size_t max_width = 0;
|
584 |
char *fmt = NULL;
|
585 |
_help_lines_ds ds;
|
586 |
char *help_str = NULL;
|
587 |
|
588 |
max_width = _find_longest_help_desc(context, &desc_list);
|
589 |
|
590 |
fmt = cfustring_sprintf_c_str(" %%-%us %%s\n", max_width);
|
591 |
ds.fmt = fmt;
|
592 |
ds.list = cfulist_new();
|
593 |
ds.desc_list = desc_list;
|
594 |
|
595 |
cfulist_foreach(desc_list, _get_help_lines, (void *)&ds);
|
596 |
|
597 |
help_str = cfulist_join(ds.list, "\n");
|
598 |
|
599 |
cfulist_destroy_with_free_fn(ds.list, _list_simple_free_fn);
|
600 |
cfulist_destroy_with_free_fn(desc_list, _desc_list_free);
|
601 |
free(fmt);
|
602 |
|
603 |
return help_str;
|
604 |
}
|