diff options
| -rw-r--r-- | README.md | 106 | ||||
| -rw-r--r-- | doc/example.c | 5 | ||||
| -rw-r--r-- | doc/interface.md | 81 | ||||
| -rw-r--r-- | src/simple-opt.h | 16 | 
4 files changed, 152 insertions, 56 deletions
| @@ -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_<type>, <short_name>, <long_name>, <true|false> ...}, +	{ SIMPLE_OPT_<type>, <short_name>, <long_name>, <true|false> ...}, +	... +	{ 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_<type>`  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 <arg_goes_here> @@ -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, <short_name>, <long_name>, <true|false>, +	[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;  	} | 
