summaryrefslogtreecommitdiff
path: root/opt.h
blob: 0971112cc5f6712f891f113ed79e6fb138809ccc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#ifndef OPT_H
#define OPT_H

#define OPT_ARG            1
#define OPT_AT_END         0
#define OPT_ERR_NO_PARAM  -1
#define OPT_ERR_BAD_OPT   -2

#define OPTF_MID_SHORT     1
#define OPTF_IGNORE        2
#define OPTF_NO_PRINT_ERR  4

/* fmt is a set of short option chars, preceded by : if they accept an
 * argument, followed by [foo] for a corresponding long option name. */

int opt_next(int *optf, int *argc, const char ***argv, const char *fmt);

#ifdef OPT_IMPL

#include <stdio.h>
#include <string.h>
#include <assert.h>

int opt_next(int *optf, int *argc, const char ***argv, const char *fmt) {
	if (~*optf & OPTF_MID_SHORT) {
		*argv += 1, *argc -= 1;
		if (*argc < 1) return OPT_AT_END;
	}
	if (*optf & OPTF_IGNORE) return OPT_ARG;

	int is_long = 0;
	if (~*optf & OPTF_MID_SHORT) {
		if ((**argv)[0] != '-') return OPT_ARG;
		is_long = (**argv)[1] == '-';
		if (is_long && !(**argv)[2]) {
			*optf |= OPTF_IGNORE;
			return opt_next(optf, argc, argv, fmt);
		}
	}

	const char *opt = **argv + is_long + !(*optf & OPTF_MID_SHORT);
	const char *opt_end = is_long ? strchr(opt, '=') : opt + 1;
	if (!opt_end) opt_end = opt + strlen(opt);
	unsigned opt_len = opt_end - opt;

	int opt_arg = 0, match = 0;
	char opt_char = '\0';
	while (*fmt && !match) {
		opt_arg = (*fmt == ':');
		if (opt_arg) fmt++;
		opt_char = *fmt++;
		assert(opt_char);
		match = !is_long && *opt == opt_char;
		if (*fmt == '[') {
			const char *rt = strchr(++fmt, ']');
			assert(rt);
			match |= is_long && rt-fmt == opt_len && !memcmp(opt, fmt, opt_len);
			fmt = rt + 1;
		}
	}

	*optf &= ~OPTF_MID_SHORT;
	if (match && opt_arg) {
		if (*opt_end) {
			**argv = opt_end + is_long;
		} else {
			*argv += 1, *argc -= 1;
			if (*argc > 0) return opt_char;
			if (~*optf & OPTF_NO_PRINT_ERR) {
				fprintf(stderr, "option '%.*s' expected parameter, but none given\n", opt_len, opt);
			}
			return OPT_ERR_NO_PARAM;
		}
	} else if (!is_long && *opt_end) {
		*optf |= OPTF_MID_SHORT;
		**argv = opt_end;
	}

	if (!match && ~*optf & OPTF_NO_PRINT_ERR) {
		fprintf(stderr, "unrecognized option '%.*s'\n", opt_len, opt);
	}
	return match ? opt_char : OPT_ERR_BAD_OPT;
}

#endif
#endif