diff options
| -rw-r--r-- | README.md | 251 | ||||
| -rw-r--r-- | doc/example.c | 120 | ||||
| -rw-r--r-- | doc/interface.md | 186 | ||||
| -rw-r--r-- | src/simple-opt.h | 661 | 
4 files changed, 1218 insertions, 0 deletions
| 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_<type>`) 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_<type>` +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 <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +/* 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: <exec> <options> */ +	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 | 
