From e2efbbe64e1cea3942e9ffc5c60f9e3f60975180 Mon Sep 17 00:00:00 2001 From: katherine Date: Mon, 19 Mar 2018 20:38:34 -0700 Subject: update documentation for new functionality added SIMPLE_OPT_CHAR, SIMPLE_OPT_DOUBLE, and SIMPLE_OPT_STRING_SET types --- README.md | 106 ++++++++++++++++++++++++++++++++++++++++--------------- doc/example.c | 5 ++- doc/interface.md | 81 ++++++++++++++++++++++++++++++++---------- src/simple-opt.h | 16 +++++---- 4 files changed, 152 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index c651acd..71e7fdb 100644 --- a/README.md +++ b/README.md @@ -22,20 +22,27 @@ you can compile and test with yourself. int main(int argc, char **argv) { + const char *set[] = { "str_a", "str_b", NULL }; + /* 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_BOOL, 'b', "bool", false, + "(optionally) takes a boolean arg!" }, { SIMPLE_OPT_INT, '\0', "int", true, - "this thing needs an integer!" }, + "requires an integer. has no short_name!" }, { 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_DOUBLE, 'd', "double", true, + "a floating point number" }, { 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!" }, + "this one doesn't have a long_name version" }, + { SIMPLE_OPT_STRING_SET, '\0', "set-choice", true, + "a choice of one string from a NULL-terminated array", + "(str_a|str_b)", set }, { SIMPLE_OPT_END }, }; @@ -102,22 +109,35 @@ int main(int argc, char **argv) if (options[i].arg_is_stored) { switch (options[i].type) { + case SIMPLE_OPT_BOOL: + printf(", val: %s", options[i].val_bool ? "true" : "false"); + break; + case SIMPLE_OPT_INT: - printf(", val: %d", options[i].val_int); + printf(", val: %ld", options[i].val_int); break; case SIMPLE_OPT_UNSIGNED: - printf(", val: %u", options[i].val_unsigned); + printf(", val: %lu", options[i].val_unsigned); + break; + + case SIMPLE_OPT_DOUBLE: + printf(", val: %lf", options[i].val_double); + break; + + case SIMPLE_OPT_CHAR: + printf(", val: %c", options[i].val_char); 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"); + case SIMPLE_OPT_STRING_SET: + printf(", val: %s", + options[i].string_set[options[i].val_string_set_idx]); break; - + default: break; } @@ -179,20 +199,41 @@ $ ./a.out --help Usage: ./a.out [OPTION]... [--] [NON-OPTION]... This is where you would put an overview description of the program and its - 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! + general functionality. + + -h --help print this help message and exit + -b --bool[=BOOL] (optionally) takes a boolean arg! + --int=INT requires an integer. has no short_name! + -u --uns=NON-NEG-INT this one has a custom_arg_string. normally it + would say "UNSIGNED" rather than "NON-NEG-INT" + -d --double=DOUBLE a floating point number + -s STRING this one doesn't have a long_name version + --set-choice=(str_a|str_b) a choice of one string from a NULL-terminated + array ``` 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). +support for that, via ncurses or something). also note that the indentation of +the option descriptions is dependent on the width of the `long_name` column. if +the lengthy `set-choice` line was removed, for example, the output would +become: + +``` +Usage: ./a.out [OPTION]... [--] [NON-OPTION]... + + This is where you would put an overview description of the program and its + general functionality. + + -h --help print this help message and exit + -b --bool[=BOOL] (optionally) takes a boolean arg! + --int=INT requires an integer. has no short_name! + -u --uns=NON-NEG-INT this one has a custom_arg_string. normally it would say + "UNSIGNED" rather than "NON-NEG-INT" + -d --double=DOUBLE a floating point number + -s STRING this one doesn't have a long_name version +``` 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 @@ -202,51 +243,60 @@ command itself: ``` $ ./a.out --help, seen: no +--bool, seen: no --int, seen: no --uns, seen: no +--double, seen: no -s, seen: no ---bool, seen: no +--set-choice, seen: no ``` ``` -$ ./a.out --int=-1 -s test_string -b -- trailing args passed +$ ./a.out --int=-1 -s test_string -b -- trailing --args -passed --help, seen: no +--bool, seen: yes --int, seen: yes, val: -1 --uns, seen: no +--double, seen: no -s, seen: yes, val: test_string ---bool, seen: yes +--set-choice, seen: no -non-options: trailing args passed +non-options: trailing --args -passed ``` ``` -$ ./a.out --bool=false -u 3 +$ ./a.out --bool=false -u 3 --set-choice "str_b" --help, seen: no +--bool, seen: yes, val: false --int, seen: no --uns, seen: yes, val: 3 +--double, seen: no -s, seen: no ---bool, seen: yes, val: false +--set-choice, seen: yes, val: str_b ``` ``` $ ./a.out no options, just arguments --help, seen: no +--bool, seen: no --int, seen: no --uns, seen: no +--double, seen: no -s, seen: no ---bool, seen: no +--set-choice, seen: no non-options: no options, just arguments ``` ``` -$ ./a.out non-options --int=+1 can -b on be --uns 0 interleaved +$ ./a.out non-options --int=+1 can -d 3.9 be --uns 0 interleaved --help, seen: no +--bool, seen: no --int, seen: yes, val: 1 --uns, seen: yes, val: 0 +--double, seen: yes, val: 3.900000 -s, seen: no ---bool, seen: yes, val: true +--set-choice, seen: no non-options: non-options can be interleaved ``` - diff --git a/doc/example.c b/doc/example.c index 1f2708c..18aec1c 100644 --- a/doc/example.c +++ b/doc/example.c @@ -23,8 +23,6 @@ int main(int argc, char **argv) { SIMPLE_OPT_STRING_SET, '\0', "set-choice", true, "a choice of one string from a NULL-terminated array", "(str_a|str_b)", set }, - { SIMPLE_OPT_CHAR, 'c', "char", false, - "(optionally) takes a character argument" }, { SIMPLE_OPT_END }, }; @@ -116,7 +114,8 @@ int main(int argc, char **argv) break; case SIMPLE_OPT_STRING_SET: - printf(", val: %s", set[options[i].val_string_set_idx]); + printf(", val: %s", + options[i].string_set[options[i].val_string_set_idx]); break; default: diff --git a/doc/interface.md b/doc/interface.md index 5408332..86937f8 100644 --- a/doc/interface.md +++ b/doc/interface.md @@ -21,16 +21,29 @@ fields which should be defined in these elements are: /* optional, a custom string describing the arg, used for usage printing */ const char *custom_arg_string; + + /* required for type SIMPLE_OPT_STRING_SET, a NULL-terminated array of + * string possibilities against which an option's argument is matched */ + const char **string_set; ``` 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. +thus, in practice, the array definition should look something like this: + +``` +struct simple_opt options[] = { + { SIMPLE_OPT_, , , ...}, + { SIMPLE_OPT_, , , ...}, + ... + { SIMPLE_OPT_END } +}; +``` -`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. +`short_name` is optional, and it may be left undefined for this option by +passing '\0'. `long_name` is also optional and may be left undefined 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: @@ -49,7 +62,9 @@ the fields which are set by `simple_opt_parse` are: `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). +by the `type` field shown above) for all but `SIMPLE_OPT_STRING_SET`, for which +`val_string_set_idx` is set, an index into the `string_set` field's array, +indicating which possibility was matched. options of the following types: @@ -57,10 +72,13 @@ options of the following types: SIMPLE_OPT_BOOL, SIMPLE_OPT_INT, SIMPLE_OPT_UNSIGNED, + SIMPLE_OPT_DOUBLE, + SIMPLE_OPT_CHAR, SIMPLE_OPT_STRING, + SIMPLE_OPT_STRING_SET, ``` -take arguments. if the user passes a short option on the cli, that options +take arguments. if the user passes a short option on the cli, that option's argument is passed as the following cli argument, like so: ``` @@ -68,8 +86,7 @@ argument is passed as the following cli argument, like so: ``` if the user passes a long option on the cli, that option's argument can be -passed either as the following cli argument or following an `=` typed at the -end of the argument, like so: +passed either as the next cli argument or appended to a trailing `=`, like so: ``` ./a.out --opt-x @@ -80,15 +97,42 @@ arguments acceptable to type `SIMPLE_OPT_BOOL` are `true`, `yes`, or `on`, all of which result in a value of true, and `false`, `no`, or `off`, which result in a value of false. -arguments acceptable to type `SIMPLE_OPT_INT` must be decimal integers (that is -digit-only strings) with an optional leading sign indicator of `-` or `+`. +arguments acceptable to type `SIMPLE_OPT_INT` must be integers with an optional +leading sign indicator of '-' or '+'. they are assumed decimal unless given a +prefix to indicate otherwise ('0' for octal and '0x' for hexadecimal). +arguments with values too large (negative or positive) to be stored in a signed +integer `long` will also be rejected. + +arguments acceptable to type `SIMPLE_OPT_UNSIGNED` are the same as those +acceptable to `SIMPLE_OPT_INT`, save that they cannot have a sign indicator and +are limited to the size of an `unsigned long`. -arguments acceptable to type `SIMPLE_OPT_UNSIGNED` must be decimal integers -(that is digit-only strings). +arguments acceptable to type `SIMPLE_OPT_DOUBLE` may be any representation of a +floating point number that can be read by the standard library `strtod` +function and stored in a `double` type. this includes arguments like "4.9", +"-1.2e20", "infinity", or "nan". + +arguments acceptable to type `SIMPLE_OPT_CHAR` may be any single-byte +character. arguments acceptable to type `SIMPLE_OPT_STRING` may be any string of characters the user passes. +arguments acceptable to type `SIMPLE_OPT_STRING_SET` may be any character +string which also appears in the programmer-defined NULL-terminated array of +strings in the `string_set` field. in practice, adding an argument of this type +would look something like this: + +``` +const char *set[] = { "choice_a", "choice_b", ..., NULL }; + +struct simple_opt options[] = { + ... + { SIMPLE_OPT_STRING_SET, , , , + [description], [custom_arg_string], set }, + ... +}; +``` ### struct simple_opt_result @@ -144,8 +188,9 @@ 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) +`long_name`, an option has neither a `short_name` nor a `long_name`, an option +of type `SIMPLE_OPT_FLAG` is marked as requiring an argument, or an option of +type `SIMPLE_OPT_STRING_SET` has a NULL `string_set` field) functions @@ -218,8 +263,8 @@ defined above. *note:* usage printing's word wrap operates under the assumptions that your language delimits words with spaces (i.e. "when i was a child..." vs. -"子供時代に..."), that the font used is fixed-width, and that every character +"子供時代に..."), that the font used is fixed-width and that every character occupies one column (that is, there are no wide characters, combining -diacritics, etc) and one byte (no multi-byte utf-8 characters). if these -assumptions do not apply to your use case, you should use an alternative method -for usage printing. +diacritics, etc), and that all characters are one byte (no multi-byte utf-8 +characters). 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 index e6f329d..d250e73 100644 --- a/src/simple-opt.h +++ b/src/simple-opt.h @@ -53,8 +53,8 @@ struct simple_opt { /* optional, a custom string describing the arg, used for usage printing */ const char *custom_arg_string; - /* for type SIMPLE_OPT_STRING_SET, a NULL-terminated array of string - * possibilities against which an option's argument is matched */ + /* required for type SIMPLE_OPT_STRING_SET, a NULL-terminated array of + * string possibilities against which an option's argument is matched */ const char **string_set; /* values assigned upon successful option parse */ @@ -261,7 +261,9 @@ static struct simple_opt_result simple_opt_parse(int argc, char **argv, 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) ) { + options[i].arg_is_required) + || (options[i].type == SIMPLE_OPT_STRING_SET && + options[i].string_set == NULL) ) { r.result_type = SIMPLE_OPT_RESULT_MALFORMED_OPTION_STRUCT; goto end; } @@ -568,13 +570,13 @@ static void simple_opt_print_usage(FILE *f, unsigned width, char *usage_name, } } - /* 5 for leading " -X ", 1 for trailing " " */ - if (desc_line_start < j + 5 + 1) - desc_line_start = j + 5 + 1; + /* 5 for leading " -X ", 2 for trailing " " */ + if (desc_line_start < j + 5 + 2) + desc_line_start = j + 5 + 2; } /* check for space for long_name printing */ - if (desc_line_start - 5 - 1 >= SIMPLE_OPT_USAGE_PRINT_BUFFER_WIDTH) { + if (desc_line_start - 5 - 2 >= SIMPLE_OPT_USAGE_PRINT_BUFFER_WIDTH) { fprintf(f, "simple-opt internal err: usage print buffer too small\n"); return; } -- cgit v1.2.3