summaryrefslogtreecommitdiff
path: root/ccan/opt/parse.c
blob: 94d75ad1fbe02ee77cd522f8615b7dff36998056 (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
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
/* Licensed under GPLv2+ - see LICENSE file for details */
/* Actual code to parse commandline. */
#include <ccan/opt/opt.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "private.h"

/* glibc does this as:
/tmp/opt-example: invalid option -- 'x'
/tmp/opt-example: unrecognized option '--long'
/tmp/opt-example: option '--someflag' doesn't allow an argument
/tmp/opt-example: option '--s' is ambiguous
/tmp/opt-example: option requires an argument -- 's'
*/
static int parse_err(void (*errlog)(const char *fmt, ...),
		     const char *argv0, const char *arg, unsigned len,
		     const char *problem)
{
	errlog("%s: %.*s: %s", argv0, len, arg, problem);
	return -1;
}

static void consume_option(int *argc, char *argv[], unsigned optnum)
{
	memmove(&argv[optnum], &argv[optnum+1],
		sizeof(argv[optnum]) * (*argc-optnum));
	(*argc)--;
}

/* Returns 1 if argument consumed, 0 if all done, -1 on error. */
int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset,
	      void (*errlog)(const char *fmt, ...))
{
	unsigned i, arg, len;
	const char *o, *optarg = NULL;
	char *problem = NULL;

	if (getenv("POSIXLY_CORRECT")) {
		/* Don't find options after non-options. */
		arg = 1;
	} else {
		for (arg = 1; argv[arg]; arg++) {
			if (argv[arg][0] == '-')
				break;
		}
	}

	if (!argv[arg] || argv[arg][0] != '-')
		return 0;

	/* Special arg terminator option. */
	if (strcmp(argv[arg], "--") == 0) {
		consume_option(argc, argv, arg);
		return 0;
	}

	/* Long options start with -- */
	if (argv[arg][1] == '-') {
		assert(*offset == 0);
		for (o = first_lopt(&i, &len); o; o = next_lopt(o, &i, &len)) {
			if (strncmp(argv[arg] + 2, o, len) != 0)
				continue;
			if (argv[arg][2 + len] == '=')
				optarg = argv[arg] + 2 + len + 1;
			else if (argv[arg][2 + len] != '\0')
				continue;
			break;
		}
		if (!o)
			return parse_err(errlog, argv[0],
					 argv[arg], strlen(argv[arg]),
					 "unrecognized option");
		/* For error messages, we include the leading '--' */
		o -= 2;
		len += 2;
	} else {
		/* offset allows us to handle -abc */
		for (o = first_sopt(&i); o; o = next_sopt(o, &i)) {
			if (argv[arg][*offset + 1] != *o)
				continue;
			(*offset)++;
			break;
		}
		if (!o)
			return parse_err(errlog, argv[0],
					 argv[arg], strlen(argv[arg]),
					 "unrecognized option");
		/* For error messages, we include the leading '-' */
		o--;
		len = 2;
	}

	if ((opt_table[i].type & ~OPT_EARLY) == OPT_NOARG) {
		if (optarg)
			return parse_err(errlog, argv[0], o, len,
					 "doesn't allow an argument");
		if ((opt_table[i].type & OPT_EARLY) == is_early)
			problem = opt_table[i].cb(opt_table[i].u.arg);
	} else {
		if (!optarg) {
			/* Swallow any short options as optarg, eg -afile */
			if (*offset && argv[arg][*offset + 1]) {
				optarg = argv[arg] + *offset + 1;
				*offset = 0;
			} else
				optarg = argv[arg+1];
		}
		if (!optarg)
			return parse_err(errlog, argv[0], o, len,
					 "requires an argument");
		if ((opt_table[i].type & OPT_EARLY) == is_early)
			problem = opt_table[i].cb_arg(optarg,
						      opt_table[i].u.arg);
	}

	if (problem) {
		parse_err(errlog, argv[0], o, len, problem);
		opt_alloc.free(problem);
		return -1;
	}

	/* If no more letters in that short opt, reset offset. */
	if (*offset && !argv[arg][*offset + 1])
		*offset = 0;

	/* All finished with that option? */
	if (*offset == 0) {
		consume_option(argc, argv, arg);
		if (optarg && optarg == argv[arg])
			consume_option(argc, argv, arg);
	}
	return 1;
}