diff options
| author | katherine <shmibs@shmibbles.me> | 2018-03-18 11:21:30 -0700 | 
|---|---|---|
| committer | katherine <shmibs@shmibbles.me> | 2018-03-18 11:21:30 -0700 | 
| commit | 722c2b22449b2469192699f038a72a1db80b3f50 (patch) | |
| tree | 6bfcdea28a6657bd153a51faa372778b7842bc09 /src | |
| download | simple-opt-722c2b22449b2469192699f038a72a1db80b3f50.tar.gz | |
initial commit
Diffstat (limited to 'src')
| -rw-r--r-- | src/simple-opt.h | 661 | 
1 files changed, 661 insertions, 0 deletions
| 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 | 
