From 722c2b22449b2469192699f038a72a1db80b3f50 Mon Sep 17 00:00:00 2001 From: katherine Date: Sun, 18 Mar 2018 11:21:30 -0700 Subject: initial commit --- README.md | 251 +++++++++++++++++++++ doc/example.c | 120 ++++++++++ doc/interface.md | 186 ++++++++++++++++ src/simple-opt.h | 661 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1218 insertions(+) create mode 100644 README.md create mode 100644 doc/example.c create mode 100644 doc/interface.md create mode 100644 src/simple-opt.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..c806e35 --- /dev/null +++ b/README.md @@ -0,0 +1,251 @@ +simple-opt +========== + +simple-opt.h is a single header file which implements a simple, flexible and +portable version of command line option parsing for programs written in C. it +is designed to be (hopefully) intuitive while also being (hopefully) more +powerful than traditional getopt or similar, while having no dependencies +outside the standard library and remaining C99 compatible. + +what follows is a simple example usage. refer to +[interface.md](doc/interface.md) for more detail. + + +example +------- + +the following example file is available as [example.c](doc/example.c) + +```C +#include "../src/simple-opt.h" + +int main(int argc, char **argv) +{ + /* array containing all options and their types / attributes */ + struct simple_opt options[] = { + { SIMPLE_OPT_FLAG, 'h', "help", false, + "print this help message and exit" }, + { SIMPLE_OPT_INT, '\0', "int", true, + "this thing needs an integer!" }, + { SIMPLE_OPT_UNSIGNED, 'u', "uns", true, + "this one has a custom_arg_string. normally it would say" + " \"UNSIGNED\" rather than \"NON-NEG-INT\"", + "NON-NEG-INT" }, + { SIMPLE_OPT_STRING, 's', NULL, true, + "this one doesn't have a long opt version" }, + { SIMPLE_OPT_BOOL, 'b', "bool", false, + "(optionally) takes a boolean arg!" }, + { SIMPLE_OPT_END }, + }; + + /* contains an enum for identifying simple_opt_parse's return value as well + * as the argv index of the first non-option and information relevant for + * error handling */ + struct simple_opt_result result; + + int i; + + result = simple_opt_parse(argc, argv, options); + + /* handle errors */ + switch (result.result_type) { + case SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION: + fprintf(stderr, "err: unrecognised option `%s`\n", + result.option_string); + return 1; + + case SIMPLE_OPT_RESULT_BAD_ARG: + fprintf(stderr, "err: bad argument `%s` passed to option `%s`\n", + result.argument_string, result.option_string); + return 1; + + case SIMPLE_OPT_RESULT_MISSING_ARG: + fprintf(stderr, "err: argument expected for option `%s`\n", + result.option_string); + return 1; + + case SIMPLE_OPT_RESULT_OPT_ARG_TOO_LONG: + fprintf(stderr, "internal err: argument passed to option `%s` is too long\n", + result.option_string); + return 1; + + case SIMPLE_OPT_RESULT_TOO_MANY_ARGS: + fprintf(stderr, "internal err: too many cli arguments passed\n"); + return 1; + + case SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT: + fprintf(stderr, "internal err: malformed option struct\n"); + return 1; + + default: + break; + } + + /* if the help flag was passed, print usage */ + if (options[0].was_seen) { + simple_opt_print_usage(stdout, 80, argv[0], + "[OPTION]... [--] [NON-OPTION]...", + "This is where you would put an overview description of the " + "program and it's general functionality.", options); + return 0; + } + + /* print a summary of options passed */ + for (i = 0; options[i].type != SIMPLE_OPT_END; i++) { + if (options[i].long_name != NULL) + printf("--%s, ", options[i].long_name); + else + printf("-%c, ", options[i].short_name); + + printf("seen: %s", (options[i].was_seen ? "yes" : "no")); + + if (options[i].arg_is_stored) { + switch (options[i].type) { + case SIMPLE_OPT_INT: + printf(", val: %d", options[i].val_int); + break; + + case SIMPLE_OPT_UNSIGNED: + printf(", val: %u", options[i].val_unsigned); + break; + + case SIMPLE_OPT_STRING: + printf(", val: %s", options[i].val_string); + break; + + case SIMPLE_OPT_BOOL: + printf(", val: %s", options[i].val_bool ? "true" : "false"); + break; + + default: + break; + } + } + + puts(""); + } + + /* if any non-option arguments were passed, print them */ + if (result.argc > 0) { + printf("\nnon-options:", result.argc); + + for (i = 0; i < result.argc; i++) + printf(" %s", result.argv[i]); + + puts(""); + } + + return 0; +} +``` + +options are stored in an array of `struct simple_opt`, which contains fields +for the option's type, an (optional) short option alias, an (optional) long +option alias, a boolean indicating whether this option's argument is required +or optional, an (optional) string describing what the option does, and an +(optional) string describing how the option's argument type should be printed +by `simple_opt_print_usage`. the end of the array must be indicated with an +option of type `SIMPLE_OPT_END`. + +this array is passed to `simple_opt_parse` and, if the parsing is successful, +relevant values (`was_seen`, `arg_is_stored`, `val_`) are in-place stored +in the options. otherwise, the returned `struct simple_opt_result` will have a +type other than `SIMPLE_OPT_RESULT_SUCCESS`, in which case error reporting +occurs. + +``` +$ ./a.out -y +err: unrecognised option `-y` +``` + +``` +$ ./a.out --int +err: argument expected for option `--int` +``` + +``` +$ ./a.out --bool fake +err: bad argument `fake` passed to option `--bool` +``` + +if one of the options passed is the help flag (`-h` or `--help`), this example +uses `simple_opt_print_usage` to print out a neatly-formatted usage message +(using the description strings stored in the array earlier). that message looks +like this: + +``` +$ ./a.out --help +Usage: ./a.out [OPTION]... [--] [NON-OPTION]... + + This is where you would put an overview description of the program and it's + general functionality. + + -h --help print this help message and exit + --int=INT this thing needs an integer! + -u --uns=NON-NEG-INT this one has a custom_arg_string. normally it would say + "UNSIGNED" rather than "NON-NEG-INT" + -s STRING this one doesn't have a long opt version + -b --bool[=BOOL] (optionally) takes a boolean arg! +``` + +note that the output is word-wrapped. it wraps to a maximum of 80 columns +because 80 is passed as the second argument to `simple_opt_print_usage` +(allowing printing to adapt to the user's terminal width if you want to add +support for that, via ncurses or something). + +finally, if parsing was successful and usage not printed, this program prints a +quick summary of which options it accepts, which it saw, and what arguments +were passed to those options, and what non-option arguments were passed to the +command itself: + +``` +$ ./a.out +--help, seen: no +--int, seen: no +--uns, seen: no +-s, seen: no +--bool, seen: no +``` + +``` +$ ./a.out --int=-1 -s test_string -b -- trailing args passed +--help, seen: no +--int, seen: yes, val: -1 +--uns, seen: no +-s, seen: yes, val: test_string +--bool, seen: yes + +non-options: trailing args passed +``` + +``` +$ ./a.out --bool=false -u 3 +--help, seen: no +--int, seen: no +--uns, seen: yes, val: 3 +-s, seen: no +--bool, seen: yes, val: false +``` + +``` +$ ./a.out no options, just arguments +--help, seen: no +--int, seen: no +--uns, seen: no +-s, seen: no +--bool, seen: no + +non-options: no options, just arguments +``` + +``` +$ ./a.out non-options --int=+1 can -b on be --uns 0 interleaved +--help, seen: no +--int, seen: yes, val: 1 +--uns, seen: yes, val: 0 +-s, seen: no +--bool, seen: yes, val: true + +non-options: non-options can be interleaved +``` + diff --git a/doc/example.c b/doc/example.c new file mode 100644 index 0000000..878dbb6 --- /dev/null +++ b/doc/example.c @@ -0,0 +1,120 @@ +#include "../src/simple-opt.h" + +int main(int argc, char **argv) +{ + /* array containing all options and their types / attributes */ + struct simple_opt options[] = { + { SIMPLE_OPT_FLAG, 'h', "help", false, + "print this help message and exit" }, + { SIMPLE_OPT_INT, '\0', "int", true, + "this thing needs an integer!" }, + { SIMPLE_OPT_UNSIGNED, 'u', "uns", true, + "this one has a custom_arg_string. normally it would say" + " \"UNSIGNED\" rather than \"NON-NEG-INT\"", + "NON-NEG-INT" }, + { SIMPLE_OPT_STRING, 's', NULL, true, + "this one doesn't have a long opt version" }, + { SIMPLE_OPT_BOOL, 'b', "bool", false, + "(optionally) takes a boolean arg!" }, + { SIMPLE_OPT_END }, + }; + + /* contains an enum for identifying simple_opt_parse's return value as well + * as the argv index of the first non-option and information relevant for + * error handling */ + struct simple_opt_result result; + + int i; + + result = simple_opt_parse(argc, argv, options); + + /* handle errors */ + switch (result.result_type) { + case SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION: + fprintf(stderr, "err: unrecognised option `%s`\n", + result.option_string); + return 1; + + case SIMPLE_OPT_RESULT_BAD_ARG: + fprintf(stderr, "err: bad argument `%s` passed to option `%s`\n", + result.argument_string, result.option_string); + return 1; + + case SIMPLE_OPT_RESULT_MISSING_ARG: + fprintf(stderr, "err: argument expected for option `%s`\n", + result.option_string); + return 1; + + case SIMPLE_OPT_RESULT_OPT_ARG_TOO_LONG: + fprintf(stderr, "internal err: argument passed to option `%s` is too long\n", + result.option_string); + return 1; + + case SIMPLE_OPT_RESULT_TOO_MANY_ARGS: + fprintf(stderr, "internal err: too many cli arguments passed\n"); + return 1; + + case SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT: + fprintf(stderr, "internal err: malformed option struct\n"); + return 1; + + default: + break; + } + + /* if the help flag was passed, print usage */ + if (options[0].was_seen) { + simple_opt_print_usage(stdout, 80, argv[0], + "[OPTION]... [--] [NON-OPTION]...", + "This is where you would put an overview description of the " + "program and it's general functionality.", options); + return 0; + } + + /* print a summary of options passed */ + for (i = 0; options[i].type != SIMPLE_OPT_END; i++) { + if (options[i].long_name != NULL) + printf("--%s, ", options[i].long_name); + else + printf("-%c, ", options[i].short_name); + + printf("seen: %s", (options[i].was_seen ? "yes" : "no")); + + if (options[i].arg_is_stored) { + switch (options[i].type) { + case SIMPLE_OPT_INT: + printf(", val: %d", options[i].val_int); + break; + + case SIMPLE_OPT_UNSIGNED: + printf(", val: %u", options[i].val_unsigned); + break; + + case SIMPLE_OPT_STRING: + printf(", val: %s", options[i].val_string); + break; + + case SIMPLE_OPT_BOOL: + printf(", val: %s", options[i].val_bool ? "true" : "false"); + break; + + default: + break; + } + } + + puts(""); + } + + /* if any non-option arguments were passed, print them */ + if (result.argc > 0) { + printf("\nnon-options:", result.argc); + + for (i = 0; i < result.argc; i++) + printf(" %s", result.argv[i]); + + puts(""); + } + + return 0; +} diff --git a/doc/interface.md b/doc/interface.md new file mode 100644 index 0000000..53838c2 --- /dev/null +++ b/doc/interface.md @@ -0,0 +1,186 @@ +simple-opt +========== + +data types +---------- + +### struct simple_opt + +an array of `struct simple_opt`, terminating in an element with a `type` field +equal to `SIMPLE_OPT_END`, must be passed when parsing or usage printing. the +fields which should be defined in these elements are: + +``` + enum simple_opt_type type; + const char short_name; + const char *long_name; + bool arg_is_required; + + /* optional, used for usage printing */ + const char *description; + + /* optional, a custom string describing the arg, used for usage printing */ + const char *custom_arg_string; +``` + +if `type` is `SIMPLE_OPT_FLAG`, this option may not accept arguments. if `type` +is `SIMPLE_OPT_END`, parsing and usage printing will return at this point when +iterating through the array and will not see any elements which may follow. + +`short_name` is optional, and may be undefined for this option by passing '\0'. +`long_name` is also optional, and may be left undefined for this option by +passing `NULL`. however, at fewest one of these two must be defined for every +option. + +the fields which are set by `simple_opt_parse` are: + +``` + bool was_seen; + bool arg_is_stored; + + union { + bool val_bool; + int val_int; + unsigned val_unsigned; + char val_string[SIMPLE_OPT_ARG_MAX_WIDTH]; + }; +``` + +`was_seen` indicates if this option was encountered during parsing, +`arg_is_stored` if an argument was passed to the option, and the `val_` +fields contain the value passed (with the correct field to set being determined +by the `type` field shown above). + + +### struct simple_opt_result + +``` +struct simple_opt_result { + enum simple_opt_result_type result_type; + enum simple_opt_type option_type; + char option_string[SIMPLE_OPT_OPT_MAX_WIDTH]; + char argument_string[SIMPLE_OPT_OPT_ARG_MAX_WIDTH]; + int argc; + char *argv[SIMPLE_OPT_MAX_ARGC]; +}; +``` + +`simple_opt_parse` returns a `struct simple_opt_result`. upon successful +parsing, its `result_type` field will contain `SIMPLE_OPT_RESULT_SUCCESS`. +otherwise, it will contain an error which should be handled by the caller. the +first three are user-caused errors: + +``` + SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION, + SIMPLE_OPT_RESULT_BAD_ARG, + SIMPLE_OPT_RESULT_MISSING_ARG, +``` + +in the case of `SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION`, `option_string` will +contain the unrecognised option which was passed. + +if the type is `SIMPLE_OPT_BAD_ARG`, then `option_string` will be set, +`option_type` will be set to the type of that option (`SIMPLE_OPT_BOOL` etc), +and the bad argument will be stored in `argument_string`. + +if the type is `SIMPLE_OPT_MISSING_ARG`, `option_string` and `option_type` will +be set. + +the remaining result types are internal errors: + +``` + SIMPLE_OPT_RESULT_ARG_TOO_LONG, + SIMPLE_OPT_RESULT_TOO_MANY_ARGS, + SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT, +``` + +`SIMPLE_OPT_RESULT_OPT_ARG_TOO_LONG` will be returned if an option argument, +passed on the command line by a user, was too long for the internal buffer. the +internal buffer can be resized by defining `SIMPLE_OPT_OPT_MAX_WIDTH` at some +point before `simple-opt.h` is included. + +`SIMPLE_OPT_RESULT_TOO_MANY_ARGS` is returned if the user passed too many +non-option arguments to the command for its internal argv filter buffer. this +limit can also be resized by defining `SIMPLE_OPT_MAX_ARGC` + +finally, `SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT` is returned if the +programmer has passed a `struct simple_opt` array which contains disallowed +option configurations (that is, two options share a `short_name` or +`long_name`, an option has neither a `short_name` nor a `long_name`, or an +option of type `SIMPLE_OPT_FLAG` is marked as requiring an argument) + + +functions +--------- + +`simple-opt.h` defines two functions for external use, `simple_opt_parse` and +`simple_opt_print_usage`. any other functions are prefixed `sub_simple_opt` and +should not be called directly (their visibility is a by-product of simple-opt's +single header file nature) + + +### simple_opt_parse + +`simple_opt_parse` takes three arguments and returns a `struct +simple_opt_result`: + +``` +struct simple_opt_result simple_opt_parse(int argc, char **argv, struct + simple_opt *options); +``` + +`argc` is the number of arguments contained in `argv`, and `argv` is an array +of character string pointers. normally here the programmer would just pass on +the `argc` and `argv` received as arguments from the `main` function. + +`struct simple_opt` is an array of the options available to be parsed +(described above) and `struct simple_opt_result` contains a set of results +about that parsing (also described above). + + +### simple_opt_print_usage + +`simple_opt_print_usage` takes six arguments and prints a neatly-formatted +usage message, similar to those typical of GNU cli commands: + +``` +void simple_opt_print_usage(FILE *f, unsigned width, char *usage_name, + char *usage_options, char *usage_summary, struct simple_opt *options) +``` + +`f` is a file pointer to which the message should be printed + +`width` is the column width to which the output should be word-wrapped (passing +0 disables wrapping). a reasonable value here would be 80, but this could also +be used to allow more dynamic behaviour (e.g. using something like `ncurses` or +`ioctl` to get the users's current terminal width) + +`usage_name` is the name of the command as it will be printed in the usage +statement. easiest is just to pass `argv[0]` here. + +`usage_options` is a summary of what options the command takes (e.g. something +like `[OPTION]...`) + +together, these two result in something that looks like: + +``` +Usage: ./a.out [OPTION]... +``` + +if both `usage_name` and `usage_options` are `NULL`, this initial line will not +be printed, allowing more flexibility to the programmer (if you wanted to, for +example, print multiple such lines on your own in order to represent different +use-cases) + +`usage_summary` is usually a one or two sentence overview summary of how the +command behaves. if this is left as `NULL`, no summary will be printed. + +the final argument, `struct simple_opt *options`, is an array of options as +defined above. + +*note:* usage printing's word wrap operates on the assumptions that your +language used delimits words with spaces (i.e. "when i was a child..." vs. +"子供時代に..."), that the font used is fixed-width, and that every character +occupies one column (that is, there are no wide characters, combining +diacritics, etc). if these assumptions do not apply to your use case, you +should use an alternative method for usage printing. diff --git a/src/simple-opt.h b/src/simple-opt.h new file mode 100644 index 0000000..52b51dd --- /dev/null +++ b/src/simple-opt.h @@ -0,0 +1,661 @@ +#ifndef SIMPLE_OPT_H +#define SIMPLE_OPT_H + +#include +#include +#include +#include +#include + +/* the maximum number of options that can be passed on the cli */ +#ifndef SIMPLE_OPT_MAX_ARGC +#define SIMPLE_OPT_MAX_ARGC 1024 +#endif + +/* the maximum allowed width for an option passed on the cli */ +#ifndef SIMPLE_OPT_OPT_MAX_WIDTH +#define SIMPLE_OPT_OPT_MAX_WIDTH 512 +#endif + +/* the maximum allowed width for an option's argument passed on the cli */ +#ifndef SIMPLE_OPT_OPT_ARG_MAX_WIDTH +#define SIMPLE_OPT_OPT_ARG_MAX_WIDTH 2048 +#endif + +/* an internal print buffer width for usage printing. you shouldn't have to + * worry about this if you're sane */ +#ifndef SIMPLE_OPT_USAGE_PRINT_BUFFER_WIDTH +#define SIMPLE_OPT_USAGE_PRINT_BUFFER_WIDTH 256 +#endif + +enum simple_opt_type { + SIMPLE_OPT_FLAG, + SIMPLE_OPT_BOOL, + SIMPLE_OPT_INT, + SIMPLE_OPT_UNSIGNED, + SIMPLE_OPT_STRING, + SIMPLE_OPT_END, +}; + +struct simple_opt { + enum simple_opt_type type; + const char short_name; + const char *long_name; + bool arg_is_required; + + /* optional, used for usage printing */ + const char *description; + + /* optional, a custom string describing the arg, used for usage printing */ + const char *custom_arg_string; + + /* values assigned upon successful option parse */ + bool was_seen; + bool arg_is_stored; + + union { + bool val_bool; + int val_int; + unsigned val_unsigned; + char val_string[SIMPLE_OPT_OPT_ARG_MAX_WIDTH]; + }; +}; + +enum simple_opt_result_type { + SIMPLE_OPT_RESULT_SUCCESS, + SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION, + SIMPLE_OPT_RESULT_BAD_ARG, + SIMPLE_OPT_RESULT_MISSING_ARG, + SIMPLE_OPT_RESULT_OPT_ARG_TOO_LONG, + SIMPLE_OPT_RESULT_TOO_MANY_ARGS, + SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT, +}; + +struct simple_opt_result { + enum simple_opt_result_type result_type; + enum simple_opt_type option_type; + char option_string[SIMPLE_OPT_OPT_MAX_WIDTH]; + char argument_string[SIMPLE_OPT_OPT_ARG_MAX_WIDTH]; + int argc; + char *argv[SIMPLE_OPT_MAX_ARGC]; +}; + +struct simple_opt_result simple_opt_parse(int argc, char **argv, struct + simple_opt *options); + +void simple_opt_print_usage(FILE *f, unsigned width, char *usage_name, + char *usage_options, char *usage_summary, struct simple_opt *options); + + +/* + * internal definitions + * + */ + +bool sub_simple_opt_parse(struct simple_opt *o, char *s) +{ + int i, j; + char *str; + bool match; + + switch (o->type) { + case SIMPLE_OPT_BOOL: + goto loop; +strmatch: + for (j = 0; j < strlen(str); j++) { + if (s[j] == '\0' || tolower(s[j]) != str[j]) { + match = false; + goto strmatch_out; + } + } + + match = true; + goto strmatch_out; +loop: + for (i = 0; i < 6; i++) { + switch (i) { + case 0: + str = "true"; + goto strmatch; + case 1: + str = "yes"; + goto strmatch; + case 2: + str = "on"; + goto strmatch; + case 3: + str = "false"; + goto strmatch; + case 4: + str = "no"; + goto strmatch; + case 5: + str = "off"; + goto strmatch; + } +strmatch_out: + if (match) { + if (i < 3) + o->val_bool = true; + else + o->val_bool = false; + + return true; + } + } + + return false; + + + case SIMPLE_OPT_INT: + + if (s[0] == '-' || s[0] == '+') { + if (strlen(s) < 2) + return false; + j = 1; + } else { + j = 0; + } + + for (i = j; s[i] != '\0'; i++) { + if ( !isdigit(s[i]) ) + return false; + } + + if (s[0] == '-') + o->val_int = -atoi(s+j); + else + o->val_int = atoi(s+j); + + return true; + + case SIMPLE_OPT_UNSIGNED: + + for (i = 0; s[i] != '\0'; i++) { + if ( !isdigit(s[i]) ) + return false; + } + + o->val_int = atoi(s); + return true; + + case SIMPLE_OPT_STRING: + if (strlen(s) + 1 >= SIMPLE_OPT_OPT_ARG_MAX_WIDTH) + return false; + + strcpy(o->val_string, s); + return true; + + default: + return false; + } +} + +int sub_simple_opt_id(char *s, struct simple_opt *o) +{ + int i; + char c; + + if (strlen(s) < 2) + return -1; + + if (s[1] != '-') { + if (strlen(s) > 2) + return -1; + + for (i = 0; o[i].type != SIMPLE_OPT_END; i++) { + if (s[1] == o[i].short_name) + return i; + } + + return -1; + } + + for (i = 0; o[i].type != SIMPLE_OPT_END; i++) { + if ( o[i].long_name != NULL && !strncmp(s + 2, o[i].long_name, strlen(o[i].long_name)) ) { + c = s[2 + strlen(o[i].long_name)]; + if (c == '\0' || c == '=') + return i; + } + } + + return -1; +} + +struct simple_opt_result simple_opt_parse(int argc, char **argv, struct + simple_opt *options) +{ + int i, j, opt_i; + int arg_end; + char c; + char *s; + struct simple_opt_result r; + + /* check for malformed options */ + for (i = 0; options[i].type != SIMPLE_OPT_END; i++) { + if ( (options[i].short_name == '\0' && options[i].long_name == NULL) + || (options[i].type == SIMPLE_OPT_FLAG && + options[i].arg_is_required) ) { + r.result_type = SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT; + goto end; + } + } + + /* check for duplicate options. can't modify anything so this is going to + * be pretty not-optimised, but ah well */ + for (i = 0; options[i].type != SIMPLE_OPT_END; i++) { + for (j = 0; options[j].type != SIMPLE_OPT_END; j++) { + if (i != j && ( + ( options[i].short_name != '\0' + && options[j].short_name != '\0' + && options[i].short_name == options[j].short_name) + || ( options[i].long_name != NULL + && options[j].long_name != NULL + && !strcmp(options[i].long_name, options[j].long_name)) + ) + ) { + r.result_type = SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT; + goto end; + } + } + } + + r.argc = 0; + + r.result_type = SIMPLE_OPT_RESULT_SUCCESS; + + for (i = 1; i < argc; i++) { + /* "following are non-opts" marker */ + if ( !strcmp(argv[i], "--") ) { + i++; + break; + } + + /* if not an opt, add to r.argv */ + if (argv[i][0] != '-') { + + if (r.argc + 1 > SIMPLE_OPT_MAX_ARGC) { + r.result_type = SIMPLE_OPT_RESULT_TOO_MANY_ARGS; + goto end; + } + + r.argv[r.argc] = argv[i]; + r.argc++; + continue; + } + + /* unrecognised argument */ + if (strlen(argv[i]) < 2) { + r.result_type = SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION; + goto opt_copy_and_return; + } + + /* identify this option */ + opt_i = sub_simple_opt_id(argv[i], options); + + if (opt_i == -1) { + r.result_type = SIMPLE_OPT_RESULT_UNRECOGNISED_OPTION; + goto opt_copy_and_return; + } + + options[opt_i].was_seen = true; + + if (options[opt_i].type == SIMPLE_OPT_FLAG) + continue; + + /* if there's an arg, is it a separate element in argv? or is it passed + * as "--X=arg"? */ + if (argv[i][1] == '-') + c = argv[i][2 + strlen(options[opt_i].long_name)]; + else + c = '\0'; + + /* if this option doesn't require an arg and none is to be found, + * just continue */ + if (!options[opt_i].arg_is_required && c == '\0') { + if (i + 1 >= argc) + continue; + + if (!strcmp(argv[i+1], "--")) + continue; + + if (sub_simple_opt_id(argv[i+1], options) != -1) + continue; + } + + if (c == '\0') { + if (i + 1 >= argc) { + r.result_type = SIMPLE_OPT_RESULT_MISSING_ARG; + r.option_type = options[opt_i].type; + goto opt_copy_and_return; + } + s = argv[i+1]; + } else { + if (argv[i][3 + strlen(options[opt_i].long_name)] == '\0') { + r.result_type = SIMPLE_OPT_RESULT_MISSING_ARG; + r.option_type = options[opt_i].type; + goto opt_copy_and_return; + } + + s = argv[i] + 3 + strlen(options[opt_i].long_name); + } + + /* is there space for the arg (if this opt wants a string)? */ + if (options[opt_i].type == SIMPLE_OPT_STRING + && strlen(s) + 1 >= SIMPLE_OPT_OPT_ARG_MAX_WIDTH) { + r.result_type = SIMPLE_OPT_RESULT_OPT_ARG_TOO_LONG; + goto opt_copy_and_return; + } + + /* try to actually parse the thing */ + if (sub_simple_opt_parse(&(options[opt_i]), s) ) { + options[opt_i].arg_is_stored = true; + /* skip forwards in argv if this wasn't an "="-type argument + * passing */ + if (i + 1 < argc && s == argv[i+1]) + i++; + } else { + r.result_type = SIMPLE_OPT_RESULT_BAD_ARG; + strncpy(r.argument_string, s, SIMPLE_OPT_OPT_ARG_MAX_WIDTH); + goto opt_copy_and_return; + } + + continue; + } + + /* copy anything that follows -- into r.argv */ + + for (; i < argc; i++, r.argc++) + r.argv[r.argc] = argv[i]; + +end: + + return r; + +opt_copy_and_return: + for(arg_end = 0; argv[i][arg_end] != '=' && argv[i][arg_end] != '\0'; + arg_end++); + + strncpy(r.option_string, argv[i], SIMPLE_OPT_OPT_MAX_WIDTH - 1 < arg_end ? + SIMPLE_OPT_OPT_MAX_WIDTH - 1 : arg_end); + + goto end; +} + +int sub_simple_opt_wrap_print(FILE *f, unsigned width, int col, int line_start, + const char *s) +{ + bool add_newline = false, first_word = true; + int i, j, word_start, word_end; + + if (width != 0 && line_start >= width) { + line_start = 0; + add_newline = true; + } + + if (width != 0 && col >= width) { + col = line_start; + add_newline = true; + } + + if (add_newline) + fprintf(f, "\n"); + + /* print out the message, trying to wrap at words */ + word_start = 0; + while (1) { + /* get the next word */ + while ( isspace(s[word_start]) ) + word_start++; + + /* null appeared before any non-spaces */ + if (s[word_start] == '\0') + return col; + + word_end = word_start; + while ( (s[word_end] != '\0') && !isspace(s[word_end]) ) + word_end++; + + /* buffer up to line_start with spaces */ + while (col < line_start) { + fprintf(f, " "); + col++; + } + + /* if too little space left, wrap */ + if (width != 0 && col + (word_end - word_start) + (first_word ? 0 : 1) + > width && first_word == false) { + fprintf(f, "\n"); + /* buffer up to line_start with spaces */ + col = 0; + while (col < line_start) { + fprintf(f, " "); + col++; + } + first_word = true; + } + + if (first_word == false) { + fprintf(f, " "); + col++; + } + + /* if too long for whole line, print piecemeal */ + if (width != 0 && line_start + (word_end - word_start) > width) { + j = word_start; + while (1) { + for (i = 0; line_start + i < width && j < word_end; i++, j++) { + fprintf(f, "%c", s[j]); + col++; + } + + if (j == word_end) + break; + + col = 0; + fprintf(f, "\n"); + while (col < line_start) { + fprintf(f, " "); + col++; + } + } + /* else just print and move to the next word */ + } else { + for (i = 0; i < word_end - word_start; i++) + fprintf(f, "%c", s[word_start + i]); + + col += i; + } + + word_start = word_end; + first_word = false; + } + + return col; +} + +void simple_opt_print_usage(FILE *f, unsigned width, char *usage_name, + char *usage_options, char *usage_summary, struct simple_opt *options) +{ + char print_buffer[SIMPLE_OPT_USAGE_PRINT_BUFFER_WIDTH]; + int i, j, col, print_buffer_offset, desc_line_start; + + /* calculate the required line_start for printing descriptions (leaving + * space for the widest existing long-option) */ + + /* check for space for column 1 (short_name) */ + if (5 >= SIMPLE_OPT_USAGE_PRINT_BUFFER_WIDTH) { + fprintf(f, "simple-opt internal err: usage print buffer too small\n"); + return; + } + + /* 4 to start with, leaving space for " -X " */ + desc_line_start = 5; + + /* check for space for column 2 (long_name / arg type) */ + for (i = 0; options[i].type != SIMPLE_OPT_END; i++) { + j = 0; + + /* 3 for "--" and "=" */ + if (options[i].long_name != NULL) + j += 3 + strlen(options[i].long_name); + + /* 2 for optional args, where arg is wrapped in [] */ + if (!options[i].arg_is_required && options[i].type != SIMPLE_OPT_FLAG) + j += 2; + + /* the width of the arg type string (BOOL, INT, UNSIGNED, STRING etc) + * FLAGs don't take an argument, so 0 */ + if (options[i].type != SIMPLE_OPT_FLAG) { + /* if there's a custom string, use that width. else, use one of the + * default widths */ + if (options[i].custom_arg_string != NULL) { + j += strlen(options[i].custom_arg_string); + } else { + switch (options[i].type) { + case SIMPLE_OPT_BOOL: + j += 4; + break; + case SIMPLE_OPT_INT: + j += 3; + break; + case SIMPLE_OPT_UNSIGNED: + j += 8; + break; + case SIMPLE_OPT_STRING: + j += 6; + break; + default: + break; + } + } + } + + /* 5 for leading " -X ", 1 for trailing " " */ + if (desc_line_start < j + 5 + 1) + desc_line_start = j + 5 + 1; + } + + /* check for space for long_name printing */ + if (desc_line_start - 5 - 1 >= SIMPLE_OPT_USAGE_PRINT_BUFFER_WIDTH) { + fprintf(f, "simple-opt internal err: usage print buffer too small\n"); + return; + } + + + /* + * printing + * + */ + + /* print "Usage: */ + if (usage_name != NULL) { + fprintf(f, "Usage:"); + + col = sub_simple_opt_wrap_print(f, width, 6, 7, usage_name); + + if (usage_options != NULL) + sub_simple_opt_wrap_print(f, width, col, 7 + strlen(usage_name) + 1, + usage_options); + + fprintf(f, "\n\n"); + } + + /* print summary line */ + if (usage_summary != NULL) { + sub_simple_opt_wrap_print(f, width, 0, 2, usage_summary); + fprintf(f, "\n\n"); + } + + /* print option list */ + for (i = 0; options[i].type != SIMPLE_OPT_END; i++) { + + /* print column 1 (short name) */ + + if (options[i].short_name != '\0') { + if (sprintf(print_buffer, "-%c", options[i].short_name) < 0) { + fprintf(f, "\nsimple-opt internal err: encoding error printing" + "option %i\n", i); + return; + } + } else { + sprintf(print_buffer, "%c", '\0'); + } + + col = sub_simple_opt_wrap_print(f, width, 0, 2, print_buffer); + + /* print column 2 (long_name and type) */ + sprintf(print_buffer, "%c", '\0'); + print_buffer_offset = 0; + + if (options[i].long_name != NULL) { + sprintf(print_buffer, "--"); + print_buffer_offset = 2; + + if (sprintf(print_buffer + print_buffer_offset, "%s", + options[i].long_name) < 0) { + fprintf(f, "\nsimple-opt internal err: encoding error printing" + "option %i\n", i); + return; + } + print_buffer_offset += strlen(options[i].long_name); + } + + if (!options[i].arg_is_required && options[i].type != + SIMPLE_OPT_FLAG) { + sprintf(print_buffer + print_buffer_offset, "["); + print_buffer_offset++; + } + + if (options[i].long_name != NULL && options[i].type != SIMPLE_OPT_FLAG) { + sprintf(print_buffer + print_buffer_offset, "="); + print_buffer_offset++; + } + + if (options[i].type != SIMPLE_OPT_FLAG) { + if (options[i].custom_arg_string != NULL) { + sprintf(print_buffer + print_buffer_offset, + options[i].custom_arg_string); + print_buffer_offset += strlen(options[i].custom_arg_string); + } else { + switch (options[i].type) { + case SIMPLE_OPT_BOOL: + sprintf(print_buffer + print_buffer_offset, "BOOL"); + print_buffer_offset += 4; + break; + case SIMPLE_OPT_INT: + sprintf(print_buffer + print_buffer_offset, "INT"); + print_buffer_offset += 3; + break; + case SIMPLE_OPT_UNSIGNED: + sprintf(print_buffer + print_buffer_offset, "UNSIGNED"); + print_buffer_offset += 8; + break; + case SIMPLE_OPT_STRING: + sprintf(print_buffer + print_buffer_offset, "STRING"); + print_buffer_offset += 6; + break; + default: + break; + } + } + } + + if (!options[i].arg_is_required && options[i].type != + SIMPLE_OPT_FLAG) + sprintf(print_buffer + print_buffer_offset, "]"); + + /* 5 for " -X --" */ + col = sub_simple_opt_wrap_print(f, width, col, 5, print_buffer); + + /* print option description */ + if (options[i].description != NULL) + sub_simple_opt_wrap_print(f, width, col, desc_line_start, + options[i].description); + + /* end of this option */ + fprintf(f, "\n"); + } +} + +#endif -- cgit v1.2.3