aboutsummaryrefslogtreecommitdiff
path: root/lib/cli/optparse.c
blob: 7134b37d02316060c86ff7f4c2bf63229e1748e5 (plain) (blame)
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include <stdio.h>
#include <string.h>

#include "cli.h"
#include "macros.h"
#include "mbstring.h"

#define CLI_MSG_INVALID "invalid option"
#define CLI_MSG_MISSING "option requires an argument"
#define CLI_MSG_TOOMANY "option takes no arguments"

#define IS_DASHDASH(s) ((s).len == 2 && (s).p[0] == '-' && (s).p[1] == '-')
#define IS_LONGOPT(s)  ((s).len >= 3 && (s).p[0] == '-' && (s).p[1] == '-')
#define IS_SHORTOPT(s) ((s).len >= 2 && (s).p[0] == '-' && (s).p[1] != '-')

#define error(st, msg, x)                                                      \
	_Generic((x), struct u8view: error_s, rune: error_r)((st), (msg), (x))

static rune error_r(struct optparser *, const char *, rune);
static rune error_s(struct optparser *, const char *, struct u8view);
static rune shortopt(struct optparser *, const struct cli_option *, size_t);

rune
optparse(struct optparser *st, const struct cli_option *opts, size_t nopts)
{
	st->errmsg[0] = '\0';
	st->optarg = (struct u8view){};

	struct u8view opt = {.p = st->_argv[st->optind]};
	if (opt.p == nullptr)
		return 0;
	opt.len = strlen(opt.p);

	if (IS_DASHDASH(opt)) {
		st->optind++;
		return 0;
	}
	if (IS_SHORTOPT(opt))
		return shortopt(st, opts, nopts);
	if (!IS_LONGOPT(opt))
		return 0;

	st->_subopt = 0;
	st->optind++;

	/* Skip ‘--’ */
	VSHFT(&opt, 2);

	const struct cli_option *o = nullptr;
	const char8_t *eq_p = u8chr(opt, '=');
	struct u8view opt_no_eq = {
		.p = opt.p,
		.len = eq_p == nullptr ? opt.len : (size_t)(eq_p - opt.p),
	};

	for (size_t i = 0; i < nopts; i++) {
		struct u8view lo = opts[i].longopt;
		if (lo.p == nullptr || !u8haspfx(lo, opt_no_eq))
			continue;
		if (o != nullptr)
			return error(st, CLI_MSG_INVALID, opt_no_eq);
		o = opts + i;
	}

	if (o == nullptr)
		return error(st, CLI_MSG_INVALID, opt_no_eq);

	switch (o->argtype) {
	case CLI_NONE:
		if (eq_p != nullptr)
			return error(st, CLI_MSG_TOOMANY, opt);
		break;
	case CLI_OPT:
		if (eq_p == nullptr)
			st->optarg = (struct u8view){};
		else {
			ASSUME(opt.len > opt_no_eq.len);
			st->optarg = (struct u8view){
				.p = eq_p + 1,
				.len = opt.len - opt_no_eq.len + 1,
			};
		}
		break;
	case CLI_REQ:
		if (eq_p == nullptr) {
			if (st->_argv[st->optind] == nullptr)
				return error(st, CLI_MSG_MISSING, opt);
			st->optarg.p = st->_argv[st->optind++];
			st->optarg.len = strlen(st->optarg.p);
		} else {
			ASSUME(opt.len > opt_no_eq.len);
			st->optarg = (struct u8view){
				.p = eq_p + 1,
				.len = opt.len - opt_no_eq.len + 1,
			};
		}
		break;
	}

	return o->shortopt;
}

rune
shortopt(struct optparser *st, const struct cli_option *opts, size_t nopts)
{
	rune ch;
	const char8_t *opt = st->_argv[st->optind];
	st->_subopt += ucstor(&ch, opt + st->_subopt + 1);
	if (ch == '\0') {
		st->_subopt = 0;
		st->optind++;
		return optparse(st, opts, nopts);
	}
	for (size_t i = 0; i < nopts; i++) {
		if (opts[i].shortopt != ch)
			continue;
		if (opts[i].argtype == CLI_NONE)
			goto out;
		if (opt[st->_subopt + 1] != '\0') {
			st->optarg.p = opt + st->_subopt + 1;
			st->optarg.len = strlen(st->optarg.p);
			st->_subopt = 0;
			st->optind++;
			goto out;
		}
		if (opts[i].argtype == CLI_OPT) {
			st->optarg = (struct u8view){};
			goto out;
		}
		if (st->_argv[st->optind + 1] == nullptr) {
			st->optarg = (struct u8view){};
			return error(st, CLI_MSG_MISSING, ch);
		}
		st->optarg.p = st->_argv[st->optind + 1];
		st->optarg.len = strlen(st->optarg.p);
		st->optind += 2;
		goto out;
	}
	return error(st, CLI_MSG_INVALID, ch);
out:
	return ch;
}

rune
error_s(struct optparser *st, const char *msg, struct u8view s)
{
	snprintf(st->errmsg, sizeof(st->errmsg), u8"%s — ‘%.*s’", msg,
	         SV_PRI_ARGS(s));
	return -1;
}

rune
error_r(struct optparser *st, const char *msg, rune ch)
{
	char buf[U8_LEN_MAX + 1] = {};
	snprintf(st->errmsg, sizeof(st->errmsg), u8"%s — ‘%.*s’", msg,
	         rtou8(buf, sizeof(buf), ch), buf);
	return -1;
}