This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
libcfu/src/cfuopt.c

627 lines
14 KiB
C

/*
* cfuopt.c - This file is part of the libcfu library
*
* Copyright (c) 2005 Don Owens. All rights reserved.
*
* This code is released under the BSD license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* * Neither the name of the author nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cfu.h"
#include "cfuopt.h"
#include "cfuhash.h"
#include "cfulist.h"
#include "cfustring.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
struct cfuopt_struct {
libcfu_type type;
cfulist_t *option_list;
cfuhash_table_t *option_map;
cfulist_t *extra;
char *progname;
};
typedef enum {
cfuopt_arg_invalid = 0,
cfuopt_arg_bool,
cfuopt_arg_string,
cfuopt_arg_int,
cfuopt_arg_float,
cfuopt_arg_string_array
} cfuopt_arg_t;
typedef struct cfuopt_list_entry {
const char *arg_data;
const char *description;
const char *arg_description;
cfuopt_arg_t arg_type;
int required;
cfulist_t *param_names;
} cfuopt_list_entry_t;
cfuopt_t *
cfuopt_new(void) {
cfuopt_t *context = calloc(1, sizeof(cfuopt_t));
context->option_list = cfulist_new();
context->option_map = cfuhash_new();
context->extra = cfulist_new();
return context;
}
#define CFUOPT_SET_TYPE(spec, type_var) \
switch (spec) { \
case 's': \
type_var = cfuopt_arg_string; \
break; \
case 'i': \
type_var = cfuopt_arg_int; \
break; \
case 'f': \
type_var = cfuopt_arg_float; \
break; \
case '!': \
type_var = cfuopt_arg_bool; \
default: \
break; \
}
/*
Parse a string like Perl's Getopt::Long takes, e.g.
"verbose|v!",
"file|f=s",
"scale:f",
"count:i"
! means boolean flag
= means required
: means optional
s means arbitrary string (C string)
f means float (double)
i means integer (long)
*/
static void
parse_opt_str(const char *opt_str, cfulist_t **param_list, cfuopt_arg_t *type, int *is_required) {
cfulist_t *params = cfulist_new();
size_t num_names = 0;
char **opt_names = cfustring_c_str_split(opt_str, &num_names, 0, "|", NULL);
char *last_opt = NULL;
char *pos = NULL;
size_t opt_len = 0;
cfuopt_arg_t arg_type = cfuopt_arg_invalid;
int required = 0;
size_t i = 0;
if (num_names == 0) {
cfulist_destroy(params);
free(opt_names);
return;
}
last_opt = opt_names[num_names - 1];
opt_len = strlen(last_opt);
if ( (pos = memchr((void *)last_opt, '=', opt_len)) ) {
char *tmp = pos;
required = 1;
tmp++;
if (*tmp) {
CFUOPT_SET_TYPE(*tmp, arg_type);
*pos = '\000';
}
else {
arg_type = cfuopt_arg_invalid;
}
}
else if ( (pos = memchr((void *)last_opt, ':', opt_len)) ) {
char *tmp = pos;
required = 0;
tmp++;
if (*tmp) {
CFUOPT_SET_TYPE(*tmp, arg_type);
*pos = '\000';
}
else {
arg_type = cfuopt_arg_invalid;
}
}
else if ( (pos = memchr((void *)last_opt, '!', opt_len)) ) {
required = 0;
arg_type = cfuopt_arg_bool;
*pos = '\000';
}
for (i = 0; i < num_names; i++) {
cfulist_push(params, opt_names[i]);
}
free(opt_names);
*type = arg_type;
*param_list = params;
*is_required = required;
}
static void
_simple_list_free_fn(void *data) {
free(data);
}
struct _add_entry_struct {
cfuopt_t *context;
cfuopt_list_entry_t *entry;
};
static int
_add_to_option_map(void *data, size_t data_size, void *arg) {
struct _add_entry_struct *es = (struct _add_entry_struct *)arg;
data_size = data_size;
cfuhash_put(es->context->option_map, (char *)data, (void *)es->entry);
return 0;
}
void
cfuopt_add_entry(cfuopt_t *context, const char *opt_str, void *arg_data,
const char *description, const char *arg_description) {
cfuopt_list_entry_t *entry = calloc(1, sizeof(cfuopt_list_entry_t));
cfulist_t *param_list = NULL;
cfuopt_arg_t arg_type = cfuopt_arg_invalid;
struct _add_entry_struct entry_struct;
int required = 0;
parse_opt_str(opt_str, &param_list, &arg_type, &required);
entry->arg_data = arg_data;
entry->description = description;
entry->arg_description = arg_description;
entry->arg_type = arg_type;
entry->required = required;
entry->param_names = param_list;
cfulist_push(context->option_list, (void *)entry);
entry_struct.entry = entry;
entry_struct.context = context;
cfulist_foreach(param_list, _add_to_option_map, &entry_struct);
}
static void
check_arg(const char *arg, int *is_long, int *is_short, int *is_data, int *is_end_of_options,
char **parsed_arg, const char **value) {
const char *ptr = NULL;
ptr = arg;
*is_long = 0;
*is_short = 0;
*is_data = 0;
*is_end_of_options = 0;
*parsed_arg = NULL;
*value = NULL;
if (!ptr || !*ptr) return;
if (*ptr != '-') {
*is_data = 1;
*parsed_arg = cfustring_dup_c_str(ptr);
return;
}
ptr++;
if (!*ptr) {
*is_data = 1;
*parsed_arg = cfustring_dup_c_str(ptr - 1);
return;
}
if (*ptr == '-') {
const char *val_ptr = NULL;
ptr++;
if (!*ptr) {
*is_end_of_options = 1;
return;
}
*is_long = 1;
*parsed_arg = cfustring_dup_c_str(ptr);
val_ptr = ptr;
for (val_ptr = ptr; *val_ptr && *val_ptr != '='; val_ptr++);
if (*val_ptr == '=') {
(*parsed_arg)[val_ptr - ptr] = '\000';
val_ptr++;
*value = val_ptr;
}
return;
} else {
*is_short = 1;
*parsed_arg = cfustring_dup_c_str(ptr);
return;
}
}
static void
_set_entry_val(cfuopt_list_entry_t *entry, const char *value) {
switch (entry->arg_type) {
case cfuopt_arg_bool:
if (entry->arg_data) *((int *)entry->arg_data) = 1;
break;
case cfuopt_arg_int:
if (entry->arg_data) *((int *)entry->arg_data) = atol((char *)value);
break;
case cfuopt_arg_float:
if (entry->arg_data) *((double *)entry->arg_data) = atof((char *)value);
break;
case cfuopt_arg_string:
if (entry->arg_data) *((char **)entry->arg_data) = cfustring_dup_c_str((char *)value);
break;
case cfuopt_arg_invalid:
case cfuopt_arg_string_array:
default:
break;
}
}
typedef struct {
int count;
char **argv;
} _update_extra_ds;
static int
_update_extra(void *data, size_t data_size, void *arg) {
_update_extra_ds *ds = (_update_extra_ds *)arg;
data_size = data_size;
ds->argv[ds->count] = (char *)data;
ds->count++;
return 0;
}
void
cfuopt_parse(cfuopt_t *context, int *argc, char ***argv, char **error) {
int i = 0;
char **args = *argv;
int is_long_opt = 0;
int is_short_opt = 0;
int is_data = 0;
int is_end_of_opt = 0;
char *parsed_arg = NULL;
const char *value = NULL;
cfuopt_list_entry_t *entry = NULL;
size_t extra_count = 0;
error = error;
if (!context) return;
if (*argc < 1) return;
context->progname = cfustring_dup_c_str(args[0]);
if (*argc < 2) return;
for (i = 1; i < *argc; i++) {
char *cur_arg = args[i];
entry = NULL;
value = NULL;
if (parsed_arg) free(parsed_arg);
parsed_arg = NULL;
check_arg(cur_arg, &is_long_opt, &is_short_opt, &is_data, &is_end_of_opt, &parsed_arg,
&value);
if (is_long_opt || is_short_opt) {
entry = (cfuopt_list_entry_t *)cfuhash_get(context->option_map, parsed_arg);
if (parsed_arg) free(parsed_arg);
parsed_arg = NULL;
}
else if (is_end_of_opt) {
if (parsed_arg) free(parsed_arg);
parsed_arg = NULL;
i++;
for (; i < *argc; i++) {
cfulist_push(context->extra, args[i]);
}
break;
}
else if (is_data) {
if (parsed_arg) free(parsed_arg);
parsed_arg = NULL;
cfulist_push(context->extra, args[i]);
continue;
}
if (!entry) {
/* FIXME: return error here if need be */
continue;
}
switch (entry->arg_type) {
case cfuopt_arg_bool:
_set_entry_val(entry, "1");
break;
case cfuopt_arg_string:
case cfuopt_arg_int:
case cfuopt_arg_float:
if (value) {
if (entry->arg_data) {
_set_entry_val(entry, value);
}
} else {
i++;
if (i >= *argc) break; /* FIXME: set up error msg here */
check_arg(args[i], &is_long_opt, &is_short_opt, &is_data, &is_end_of_opt,
&parsed_arg, &value);
if (!is_data) {
i--;
break; /* FIXME: set up error msg here */
}
_set_entry_val(entry, parsed_arg);
free(parsed_arg);
parsed_arg = NULL;
}
break;
case cfuopt_arg_string_array:
break;
case cfuopt_arg_invalid:
/* FIXME: really should produce an error msg here */
break;
default:
break;
}
}
extra_count = cfulist_num_entries(context->extra);
*argc = extra_count + 1;
{
_update_extra_ds ds;
size_t update_count = 0;
ds.count = 1;
ds.argv = args;
update_count = cfulist_foreach(context->extra, _update_extra, (void *)&ds);
assert(update_count + 1 == (unsigned)*argc);
}
}
static void
_opt_list_free_fn(void *data) {
cfuopt_list_entry_t *entry = (cfuopt_list_entry_t *)data;
cfulist_destroy_with_free_fn(entry->param_names, _simple_list_free_fn);
free(data);
}
void
cfuopt_destroy(cfuopt_t *context) {
if (!context) return;
cfulist_destroy_with_free_fn(context->option_list, _opt_list_free_fn);
cfuhash_destroy(context->option_map);
cfulist_destroy(context->extra);
free(context->progname);
free(context);
}
#define ARG_TYPE_TO_STR(type, str) \
switch(type) { \
case cfuopt_arg_invalid: \
str = "INVALID"; \
break; \
case cfuopt_arg_bool: \
str = "BOOL"; \
break; \
case cfuopt_arg_string: \
str = "STRING"; \
break; \
case cfuopt_arg_int: \
str = "INT"; \
break; \
case cfuopt_arg_float: \
str = "FLOAT"; \
break; \
case cfuopt_arg_string_array: \
str = "STRING ARRAY"; \
break; \
default: \
str = "INVALID"; \
break; \
}
static int
_cfuopt_pretty_print_foreach(void *key, size_t key_size, void *data, size_t data_size, void *arg) {
char *name = (char *)key;
cfuopt_list_entry_t *list_entry = data;
char *str = NULL;
key_size = key_size;
data_size = data_size;
arg = arg;
ARG_TYPE_TO_STR(list_entry->arg_type, str);
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);
return 0;
}
void
cfuopt_pretty_print(cfuopt_t *context) {
cfuhash_foreach(context->option_map, _cfuopt_pretty_print_foreach, NULL);
}
static void
_list_simple_free_fn(void *data) {
free(data);
}
void *
_param_map_fn(void *data, size_t data_size, void *arg, size_t *new_data_size) {
char *name = (char *)data;
size_t len = 0;
cfuopt_list_entry_t *opt = (cfuopt_list_entry_t *)arg;
data_size = data_size;
if (!name) return NULL;
len = strlen(name);
*new_data_size = -1;
if (len == 0) return cfustring_dup_c_str("");
if (opt->arg_description && strlen(opt->arg_description) > 0) {
if (len == 1) {
return cfustring_sprintf_c_str("-%s %s", name, opt->arg_description);
}
else {
return cfustring_sprintf_c_str("--%s=%s", name, opt->arg_description);
}
}
else {
if (len == 1) {
return cfustring_sprintf_c_str("-%s", name);
}
else {
return cfustring_sprintf_c_str("--%s", name);
}
}
return NULL;
}
typedef struct _find_foreach_struct {
cfulist_t *desc_list;
size_t max_size;
} _find_foreach_ds;
typedef struct _cfuopt_desc_struct {
char *desc;
cfuopt_list_entry_t *entry;
} _cfuopt_desc;
static int
_find_foreach_fn(void *data, size_t data_size, void *arg) {
cfuopt_list_entry_t *opt = (cfuopt_list_entry_t *)data;
_find_foreach_ds *ds = (_find_foreach_ds *)arg;
size_t this_size = 0;
cfulist_t *param_full_list = NULL;
char *desc = NULL;
_cfuopt_desc *desc_ds = calloc(1, sizeof(_cfuopt_desc));
if (!data) return 0;
param_full_list = cfulist_map(opt->param_names, _param_map_fn, opt);
desc = cfulist_join(param_full_list, ", ");
data_size = data_size;
cfulist_destroy_with_free_fn(param_full_list, _simple_list_free_fn);
desc_ds->desc = desc;
desc_ds->entry = opt;
cfulist_push(ds->desc_list, desc_ds);
this_size = strlen(desc);
if (this_size > ds->max_size) ds->max_size = this_size;
return 0;
}
static size_t
_find_longest_help_desc(cfuopt_t *context, cfulist_t **desc_list) {
_find_foreach_ds ds;
ds.max_size = 0;
ds.desc_list = cfulist_new();
cfulist_foreach(context->option_list, _find_foreach_fn, (void *)&ds);
*desc_list = ds.desc_list;
return ds.max_size;
}
typedef struct {
char *fmt;
cfulist_t *list;
cfulist_t *desc_list;
} _help_lines_ds;
static int
_get_help_lines(void *data, size_t data_size, void *arg) {
_cfuopt_desc *desc_ds = (_cfuopt_desc *)data;
_help_lines_ds *ds = (_help_lines_ds *)arg;
char *left_col = NULL;
const char *desc = NULL;
char *line = NULL;
data_size = data_size;
left_col = desc_ds->desc;
if (!left_col) return 0;
desc = desc_ds->entry->description;
if (!desc) desc = "test desc";
line = cfustring_sprintf_c_str(ds->fmt, left_col, desc);
cfulist_push(ds->list, (void *)line);
return 0;
}
static void
_desc_list_free(void *data) {
_cfuopt_desc *desc_ds = (_cfuopt_desc *)data;
free(desc_ds->desc);
free(data);
}
char *
cfuopt_get_help_str(cfuopt_t *context) {
cfulist_t *desc_list = NULL;
size_t max_width = 0;
char *fmt = NULL;
_help_lines_ds ds;
char *help_str = NULL;
max_width = _find_longest_help_desc(context, &desc_list);
fmt = cfustring_sprintf_c_str(" %%-%us %%s\n", max_width);
ds.fmt = fmt;
ds.list = cfulist_new();
ds.desc_list = desc_list;
cfulist_foreach(desc_list, _get_help_lines, (void *)&ds);
help_str = cfulist_join(ds.list, "\n");
cfulist_destroy_with_free_fn(ds.list, _list_simple_free_fn);
cfulist_destroy_with_free_fn(desc_list, _desc_list_free);
free(fmt);
return help_str;
}