if (item->letter) { // will write sth like "-x, --exclusive"
linelen = strlen("-x, --") + strlen(item->name);
- } else { // will write sth like "--exclusive"
+ }
+ else { // will write sth like "--exclusive"
linelen = strlen("--") + strlen(item->name);
}
if (item->flags & OPT_REQUIRED_VALUE) {
linelen += strlen("=value");
- } else if (item->flags & OPT_MAYBE_VALUE) {
+ }
+ else if (item->flags & OPT_MAYBE_VALUE) {
linelen += strlen("(=value)");
}
fprintf(stderr, "\n");
else
fprintf(stderr, "%s %s\n", spaces + strlen(item->help), item->u.help2);
- } else if (item->cls == OPT_CL_SECTION) {
+ }
+ else if (item->cls == OPT_CL_SECTION) {
opt_help_noexit_internal(item->u.section);
- } else if (item->letter) {
+ }
+ else if (item->letter) {
sprintf(buf, "-%c, --%s%s", item->letter, item->name, VAL(item));
fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help);
- } else {
+ }
+ else {
sprintf(buf, "--%s%s", item->name, VAL(item));
fprintf(stderr, "%s%s %s\n", buf, spaces + strlen(buf), item->help);
}
}
}
-void opt_init(struct opt_section * options) {
- for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++) {
- if (item->cls == OPT_CL_SECTION)
- opt_init(item->u.section);
- else if (!(item->flags & OPT_VALUE_FLAGS)) {
- if (item->cls == OPT_CL_CALL || item->cls == OPT_CL_USER) {
- fprintf(stderr, "You MUST specify some of the value flags for the %c/%s item.\n", item->letter, item->name);
- ASSERT(0);
- } else
- item->flags |= opt_default_value_flags[item->cls];
- }
- }
- opt_section_root = options;
+struct opt_precomputed {
+ struct opt_precomputed_option {
+ struct opt_item * item;
+ short flags;
+ short count;
+ } ** opts;
+ struct opt_precomputed_option * shortopt[256];
+ short opt_count;
+};
+
+static struct opt_item * opt_find_item_shortopt(int chr, struct opt_precomputed * opts) {
+ return (opts->shortopt[chr] ? opts->shortopt[chr]->item : NULL);
}
-static struct opt_item * opt_find_item_longopt_section(char * str, struct opt_section * options) {
+static struct opt_item * opt_find_item_longopt(char * str, struct opt_precomputed * pre) {
uns len = strlen(str);
struct opt_item * candidate = NULL;
- for (struct opt_item * item = options->opt; item->cls != OPT_CL_END; item++) {
- if (item->cls == OPT_CL_SECTION) {
- struct opt_item * out = opt_find_item_longopt_section(str, item->u.section);
- if (out) {
- if (candidate)
- opt_failure("Ambiguous prefix %s: Found matching %s and %s.\n", str, candidate->name, item->name);
- else
- candidate = out;
- }
- } else if (!strncmp(item->name, str, len)) {
- if (strlen(item->name) == len)
- return item;
+ for (int i=0; i<pre->opt_count; i++) {
+ if (!strncmp(pre->opts[i]->item->name, str, len)) {
+ if (strlen(pre->opts[i]->item->name) == len) {
+ if (pre->opts[i]->count++ && (pre->opts[i]->flags & OPT_SINGLE))
+ opt_failure("Option %s appeared the second time.\n", pre->opts[i]->item->name);
+ return pre->opts[i]->item;
+ }
if (candidate)
- opt_failure("Ambiguous prefix %s: Found matching %s and %s.\n", str, candidate->name, item->name);
+ opt_failure("Ambiguous prefix %s: Found matching %s and %s.\n", str, candidate->name, pre->opts[i]->item->name);
else
- candidate = item;
+ candidate = pre->opts[i]->item;
}
}
if (candidate)
return candidate;
- else {
- }
-}
-static struct opt_item * opt_find_item_longopt(char * str) {
- struct opt_item * out = opt_find_item_longopt_section(str, opt_section_root);
- if (out == NULL)
- opt_failure("Invalid argument: %s\n", str);
- return out;
+ opt_failure("Invalid option %s.\n", str);
}
#define OPT_NAME (longopt ? stk_printf("--%s", item->name) : stk_printf("-%c", item->letter))
static void opt_parse_value(struct opt_item * item, char * value, int longopt) {
switch (item->cls) {
case OPT_CL_BOOL:
- if (!strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true"))
+ if (!strcasecmp(value, "y") || !strcasecmp(value, "yes") || !strcasecmp(value, "true") || !strcasecmp(value, "1"))
*((int *) item->ptr) = 1;
- else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false"))
+ else if (!strcasecmp(value, "n") || !strcasecmp(value, "no") || !strcasecmp(value, "false") || !strcasecmp(value, "0"))
*((int *) item->ptr) = 0;
else
opt_failure("Boolean argument for %s has a strange value. Supported (case insensitive): y/n, yes/no, true/false.\n", OPT_NAME);
*((int *)item->ptr) = item->u.value;
break;
case OPT_CL_INC:
- if (item->flags | OPT_DECREMENT)
+ if (item->flags & OPT_DECREMENT)
(*((int *)item->ptr))--;
else
(*((int *)item->ptr))++;
case OPT_CL_CALL:
-
+ item->u.call(item, value, item->ptr);
+ break;
case OPT_CL_USER:
{
char * e = NULL;
opt_failure("User defined type value parsing failed for argument %s: %s\n", OPT_NAME, e);
break;
}
+ default:
+ ASSERT(0);
}
}
#undef OPT_NAME
-static int opt_longopt(char ** argv, int index) {
- int eaten;
+static int opt_longopt(char ** argv, int index, struct opt_precomputed * pre) {
+ int eaten = 0;
char * name_in = argv[index] + 2; // skipping the -- on the beginning
uns pos = strchrnul(name_in, '=') - name_in;
- struct opt_item * item = opt_find_item_longopt(strndupa(name_in, pos));
+ struct opt_item * item = opt_find_item_longopt(strndupa(name_in, pos), pre);
char * value = NULL;
- if (item->flags | OPT_REQUIRED_VALUE) {
+ if (item->flags & OPT_REQUIRED_VALUE) {
if (pos < strlen(name_in))
value = name_in + pos + 1;
else {
eaten++;
}
}
- else if (item->flags | OPT_MAYBE_VALUE) {
+ else if (item->flags & OPT_MAYBE_VALUE) {
if (pos < strlen(name_in))
value = name_in + pos + 1;
}
if (pos < strlen(name_in))
opt_failure("Argument %s must not have any value.", item->name);
}
+ opt_parse_value(item, value, 1);
+ return eaten;
}
-void opt_parse(char ** argv, opt_positional * callback) {
+static int opt_shortopt(char ** argv, int index, struct opt_precomputed * pre) {
+ int chr = 0;
+ struct opt_item * item;
+ while (argv[index][++chr] && (item = opt_find_item_shortopt(argv[index][chr], pre))) {
+ if (item->flags & OPT_NO_VALUE) {
+ opt_parse_value(item, NULL, 0);
+ continue;
+ }
+ if (chr == 1 && (item->flags & OPT_REQUIRED_VALUE)) {
+ if (argv[index][2]) {
+ opt_parse_value(item, argv[index] + 2, 0);
+ return 0;
+ }
+ else {
+ opt_parse_value(item, argv[index+1], 0);
+ return 1;
+ }
+ }
+ else if (chr == 1 && (item->flags & OPT_MAYBE_VALUE)) {
+ if (argv[index][2])
+ opt_parse_value(item, argv[index] + 2, 0);
+ else
+ opt_parse_value(item, NULL, 0);
+ }
+ else if (item->flags & (OPT_REQUIRED_VALUE | OPT_MAYBE_VALUE)) {
+ if (argv[index][chr+1] || (item->flags | OPT_MAYBE_VALUE))
+ opt_failure("Option -%c may or must have a value but found inside a bunch of short opts.", item->letter);
+ else {
+ opt_parse_value(item, argv[index+1], 0);
+ return 1;
+ }
+ }
+ }
+
+ if (argv[index][chr])
+ opt_failure("Unknown option -%c.", item->letter);
+
+ return 0;
+}
+
+#define OPT_TRAVERSE_SECTIONS \
+ do { \
+ while (item->cls == OPT_CL_SECTION) { \
+ if (stk->next) \
+ stk = stk->next; \
+ else { \
+ struct opt_stack * new_stk = alloca(sizeof(*new_stk)); \
+ new_stk->prev = stk; \
+ stk->next = new_stk; \
+ stk = new_stk; \
+ } \
+ stk->this = item; \
+ item = item->u.section->opt; \
+ } \
+ if (item->cls == OPT_CL_END) { \
+ if (!stk) break; \
+ item = stk->this; \
+ stk = stk->prev; \
+ continue; \
+ } \
+ } while (0)
+
+void opt_parse(const struct opt_section * options, char ** argv, opt_positional * callback) {
+ struct opt_stack {
+ struct opt_item * this;
+ struct opt_stack * prev;
+ struct opt_stack * next;
+ } * stk = alloca(sizeof(*stk));
+ stk->this = NULL;
+ stk->prev = NULL;
+ stk->next = NULL;
+
+ struct opt_precomputed * pre = alloca(sizeof(*pre));
+
+ int count;
+
+ for (struct opt_item * item = options->opt; ; item++) {
+ OPT_TRAVERSE_SECTIONS;
+ if (item->letter || item->name)
+ count++;
+ }
+
+ pre->opts = xmalloc(sizeof(*pre->opts) * count);
+ pre->opt_count = 0;
+
+ for (struct opt_item * item = options->opt; ; item++) {
+ OPT_TRAVERSE_SECTIONS;
+ if (item->letter || item->name) {
+ struct opt_precomputed_option * opt = xmalloc(sizeof(*opt));
+ opt->item = item;
+ opt->flags = item->flags;
+ opt->count = 0;
+ pre->opts[pre->opt_count++] = opt;
+ if (item->letter)
+ pre->shortopt[(int) item->letter] = opt;
+ if (!(opt->flags & OPT_VALUE_FLAGS) &&
+ (item->cls == OPT_CL_CALL || item->cls == OPT_CL_USER)) {
+ fprintf(stderr, "You MUST specify some of the value flags for the %c/%s item.\n", item->letter, item->name);
+ ASSERT(0);
+ }
+ else
+ opt->flags |= opt_default_value_flags[item->cls];
+ }
+ }
int force_positional = 0;
for (int i=0;argv[i];i++) {
if (argv[i][0] != '-' || force_positional) {
callback(argv[i]);
- } else {
+ }
+ else {
if (argv[i][1] == '-') {
if (argv[i][2] == '\0')
force_positional++;
else
- i += opt_longopt(argv, i);
+ i += opt_longopt(argv, i, pre);
}
else if (argv[i][1])
- i += opt_shortopt(argv, i);
+ i += opt_shortopt(argv, i, pre);
else
callback(argv[i]);
}
#ifdef TEST
#include <ucw/fastbuf.h>
-static int show_version(const char ** param UNUSED) {
+static void show_version(struct opt_item * opt UNUSED, const char * value UNUSED, void * data UNUSED) {
printf("This is a simple tea boiling console v0.1.\n");
exit(EXIT_SUCCESS);
}
OPT_HELP(""),
OPT_HELP("Options:"),
OPT_HELP_OPTION,
- OPT_CALL('V', "version", show_version, OPT_NO_VALUE, "Show the version"),
+ OPT_CALL('V', "version", show_version, NULL, OPT_NO_VALUE, "Show the version"),
OPT_HELP(""),
OPT_BOOL('e', "english-style", english, 0, "English style (with milk)"),
OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "Amount of sugar (in teaspoons)"),
OPT_SWITCH('h', "hands", set, TEAPOT_HANDS, 0, "Use user's hands as a teapot (a bit dangerous)"),
OPT_USER('t', "temperature", temperature, teapot_temperature_t, OPT_REQUIRED_VALUE,
"Wanted final temperature of the tea to be served\n"
- "\t\tSupported scales: \tCelsius [60C], Fahrenheit [140F],"
+ "\t\tSupported scales:\tCelsius [60C], Fahrenheit [140F],"
"\t\t\tKelvin [350K], Rankine [600R] and Reaumur [50Re]"
"\t\tOnly integer values allowed."),
OPT_INC('v', "verbose", verbose, 0, "Verbose (the more -v, the more verbose)"),
}
};
+#define MAX_TEA_COUNT 30
+static char * tea_list[MAX_TEA_COUNT];
+static int tea_num = 0;
+static void add_tea(const char * name) {
+ if (tea_num >= MAX_TEA_COUNT) {
+ fprintf(stderr, "Cannot boil more than %d teas.\n", MAX_TEA_COUNT);
+ exit(OPT_EXIT_BAD_ARGS);
+ }
+ tea_list[tea_num++] = strdup(name);
+}
+
static void boil_tea(const char * name) {
printf("Boiling a tea: %s\n", name);
}
int main(int argc, char ** argv)
{
- char ** teas;
- int teas_num;
-
- opt_init(&help);
- opt_parse(argv, NULL);
+ opt_parse(&help, argv, add_tea);
- for (int i=0; i<teas_num; i++)
- boil_tea(teas[i]);
+ for (int i=0; i<tea_num; i++)
+ boil_tea(tea_list[i]);
printf("Everything OK. Bye.\n");
}
OPT_CL_HELP, // help line
};
-typedef void opt_custom_function(const char ** param);
-
struct opt_section;
struct opt_item {
const char * name; // long-op
struct opt_section * section; // subsection for OPT_SECTION
int value; // value for OPT_SWITCH
const char * help2; // second value for OPT_HELP2
- int (* call)(const char ** param); // function to call for OPT_CALL
+ void (* call)(struct opt_item * opt, const char * value, void * data); // function to call for OPT_CALL
struct cf_user_type * utype; // specification of the user-defined type
} u;
const char letter; // short-op
* Sub-items to be enclosed in OPT_ITEMS { } list
* ----------------------------------------------
*
- * OPT_HELP_OPTION declares --help and prints a line about that
- * OPT_HELP prints a line into help
- * OPT_HELP2 prints two strings onto a line using the same tab structure as the option listing
- * OPT_BOOL declares boolean option with an auto-negation (--sth and --no-sth); may be changed by OPT_BOOL_SET_PREFIXES
- * OPT_STRING, OPT_UNS, OPT_INT declare simple string/uns/int option
- * OPT_SWITCH declares one choice of a switch statement; these have common target and different `value`s; last wins unless OPT_SINGLE is set;
- * parser fails if it matches an OPT_SWITCH with OPT_SINGLE set and also target set.
- * Target must be of signed integer type; it is set to -1 if no switch appears at the command-line.
- * OPT_CALL calls the given function with all the remaining command line, it returns the number of arguments to be skipped.
- * OPT_USER declares a custom type of value; parser is of type opt_custom_parser
- * and returns 1 on success and 0 on failure
- * OPT_INC declares an incremental value like -v/--verbose
- * OPT_SECTION declares a subsection
+ * OPT_HELP_OPTION declares --help and prints a line about that
+ * OPT_HELP prints a line into help
+ * OPT_HELP2 prints two strings onto a line using the same tab structure as the option listing
+ * OPT_BOOL declares boolean option with an auto-negation (--sth and --no-sth). It's also possible to write --sth=y/yes/true/1/n/no/false/0.
+ * OPT_STRING, OPT_UNS, OPT_INT declare simple string/uns/int option
+ * OPT_SWITCH declares one choice of a switch statement; these have common target and different `value`s; last wins unless OPT_SINGLE is set;
+ * parser fails if it matches an OPT_SWITCH with OPT_SINGLE set and also target set.
+ * Target must be of signed integer type; it is set to -1 if no switch appears at the command-line.
+ * OPT_CALL calls the given function with an argument, giving also the opt_item structure and some custom data.
+ * OPT_USER declares a custom type of value defined by the given @cf_user_type in @ttype
+ * OPT_INC declares an incremental value like -v/--verbose
+ * OPT_SECTION declares a subsection
*
***/
-#define OPT_HELP_OPTION OPT_CALL(0, "help", opt_show_help_internal, OPT_NO_VALUE, "Show this help")
+#define OPT_HELP_OPTION OPT_CALL(0, "help", opt_show_help_internal, NULL, OPT_NO_VALUE, "Show this help")
#define OPT_HELP(line) OPT_HELP2(line, NULL)
#define OPT_HELP2(first, second) { .help = first, .cls = OPT_CL_HELP, .u.help2 = second } // FIXME: remove this
#define OPT_BOOL(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .help = desc, .flags = fl, .cls = OPT_CL_BOOL, .type = CT_INT }
#define OPT_DOUBLE(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, double *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_DOUBLE }
#define OPT_IP(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, u32 *), .help = desc, .flags = fl, .cls = OPT_CL_STATIC, .type = CT_IP }
#define OPT_SWITCH(shortopt, longopt, target, val, fl, desc) { .letter = shortopt, .name = longopt, .ptr = CHECK_PTR_TYPE(&target, int *), .help = desc, .flags = fl, .cls = OPT_CL_SWITCH, .type = CT_LOOKUP, .u.value = val }
-#define OPT_CALL(shortopt, longopt, fn, fl, desc) { .letter = shortopt, .name = longopt, .ptr = NULL, .help = desc, .u.call = fn, .flags = fl, .cls = OPT_CL_CALL, .type = CT_USER }
+#define OPT_CALL(shortopt, longopt, fn, data, fl, desc) { .letter = shortopt, .name = longopt, .ptr = data, .help = desc, .u.call = fn, .flags = fl, .cls = OPT_CL_CALL, .type = CT_USER }
#define OPT_USER(shortopt, longopt, target, ttype, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .u.utype = &ttype, .flags = fl, .help = desc, .cls = OPT_CL_USER, .type = CT_USER }
#define OPT_INC(shortopt, longopt, target, fl, desc) { .letter = shortopt, .name = longopt, .ptr = &target, .flags = fl, .help = desc, .cls = OPT_CL_INC, .type = CT_INT }
#define OPT_SECTION(sec) { .cls = OPT_CL_SECTION, .u.section = &sec }
#define OPT_DECREMENT 0x10 /** Reversing the effect of OPT_INC **/
#define OPT_SINGLE 0x20 /** Argument must appear at most once **/
#define OPT_NO_HELP 0x40 /** Omit this line from help **/
+#define OPT_LAST_ARG 0x80 /** Stop processing argv after this line **/
/***
* Value flags defaults
fprintf(stderr, "Run with argument --help for more information.\n");
}
-static int opt_show_help_internal(const char ** param UNUSED) {
+static void opt_show_help_internal(struct opt_item * opt UNUSED, const char * value UNUSED, void * data UNUSED) {
opt_help_noexit();
exit(0);
}
exit(1);
}
-/**
- * Init the opt engine.
- **/
-void opt_init(struct opt_section * options);
-
/**
* Positional argument handler to be given to opt_parse()
**/
/**
* Parse all the arguments. Run the @callback for each of the positional argument.
**/
-void opt_parse(char ** argv, opt_positional * callback);
+void opt_parse(const struct opt_section * options, char ** argv, opt_positional * callback);
#endif