1 /* Copyright (c) 2012-2013 Yoran Heling
2 
3   Permission is hereby granted, free of charge, to any person obtaining
4   a copy of this software and associated documentation files (the
5   "Software"), to deal in the Software without restriction, including
6   without limitation the rights to use, copy, modify, merge, publish,
7   distribute, sublicense, and/or sell copies of the Software, and to
8   permit persons to whom the Software is furnished to do so, subject to
9   the following conditions:
10 
11   The above copyright notice and this permission notice shall be included
12   in all copies or substantial portions of the Software.
13 
14   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22 
23 /* This is a simple command-line option parser. Operation is similar to
24  * getopt_long(), except with a cleaner API.
25  *
26  * This is implemented in a single header file, as it's pretty small and you
27  * generally only use an option parser in a single .c file in your program.
28  *
29  * Supports (examples from GNU tar(1)):
30  *   "--gzip"
31  *   "--file <arg>"
32  *   "--file=<arg>"
33  *   "-z"
34  *   "-f <arg>"
35  *   "-f<arg>"
36  *   "-zf <arg>"
37  *   "-zf<arg>"
38  *   "--" (To stop looking for further options)
39  *   "<arg>" (Non-option arguments)
40  *
41  * Issues/non-features:
42  * - An option either requires an argument or it doesn't.
43  * - No way to specify how often an option can/should be used.
44  * - No way to specify the type of an argument (filename/integer/enum/whatever)
45  */
46 
47 
48 #ifndef YOPT_H
49 #define YOPT_H
50 
51 
52 #include <string.h>
53 #include <stdarg.h>
54 #include <stdlib.h>
55 #include <stdio.h>
56 
57 
58 typedef struct {
59 	/* Value yopt_next() will return for this option */
60 	int val;
61 	/* Whether this option needs an argument */
62 	int needarg;
63 	/* Name(s) of this option, prefixed with '-' or '--' and separated by a
64 	 * comma. E.g. "-z", "--gzip", "-z,--gzip".
65 	 * An option can have any number of aliases.
66 	 */
67 	const char *name;
68 } yopt_opt_t;
69 
70 
71 typedef struct {
72 	int argc;
73 	int cur;
74 	int argsep; /* '--' found */
75 	char **argv;
76 	char *sh;
77 	const yopt_opt_t *opts;
78 	char errbuf[128];
79 } yopt_t;
80 
81 
82 /* opts must be an array of options, terminated with an option with val=0 */
yopt_init(yopt_t * o,int argc,char ** argv,const yopt_opt_t * opts)83 static inline void yopt_init(yopt_t *o, int argc, char **argv, const yopt_opt_t *opts) {
84 	o->argc = argc;
85 	o->argv = argv;
86 	o->opts = opts;
87 	o->cur = 0;
88 	o->argsep = 0;
89 	o->sh = NULL;
90 }
91 
92 
_yopt_find(const yopt_opt_t * o,const char * v)93 static inline const yopt_opt_t *_yopt_find(const yopt_opt_t *o, const char *v) {
94 	const char *tn, *tv;
95 
96 	for(; o->val; o++) {
97 		tn = o->name;
98 		while(*tn) {
99 			tv = v;
100 			while(*tn && *tn != ',' && *tv && *tv != '=' && *tn == *tv) {
101 				tn++;
102 				tv++;
103 			}
104 			if(!(*tn && *tn != ',') && !(*tv && *tv != '='))
105 				return o;
106 			while(*tn && *tn != ',')
107 				tn++;
108 			while(*tn == ',')
109 				tn++;
110 		}
111 	}
112 
113 	return NULL;
114 }
115 
116 
_yopt_err(yopt_t * o,char ** val,const char * fmt,...)117 static inline int _yopt_err(yopt_t *o, char **val, const char *fmt, ...) {
118 	va_list va;
119 	va_start(va, fmt);
120 	vsnprintf(o->errbuf, sizeof(o->errbuf), fmt, va);
121 	va_end(va);
122 	*val = o->errbuf;
123 	return -2;
124 }
125 
126 
127 /* Return values:
128  *  0 -> Non-option argument, val is its value
129  * -1 -> Last argument has been processed
130  * -2 -> Error, val will contain the error message.
131  *  x -> Option with val = x found. If the option requires an argument, its
132  *       value will be in val.
133  */
yopt_next(yopt_t * o,char ** val)134 static inline int yopt_next(yopt_t *o, char **val) {
135 	const yopt_opt_t *opt;
136 	char sh[3];
137 
138 	*val = NULL;
139 	if(o->sh)
140 		goto inshort;
141 
142 	if(++o->cur >= o->argc)
143 		return -1;
144 
145 	if(!o->argsep && o->argv[o->cur][0] == '-' && o->argv[o->cur][1] == '-' && o->argv[o->cur][2] == 0) {
146 		o->argsep = 1;
147 		if(++o->cur >= o->argc)
148 			return -1;
149 	}
150 
151 	if(o->argsep || *o->argv[o->cur] != '-') {
152 		*val = o->argv[o->cur];
153 		return 0;
154 	}
155 
156 	if(o->argv[o->cur][1] != '-') {
157 		o->sh = o->argv[o->cur]+1;
158 		goto inshort;
159 	}
160 
161 	/* Now we're supposed to have a long option */
162 	if(!(opt = _yopt_find(o->opts, o->argv[o->cur])))
163 		return _yopt_err(o, val, "Unknown option '%s'", o->argv[o->cur]);
164 	if((*val = strchr(o->argv[o->cur], '=')) != NULL)
165 		(*val)++;
166 	if(!opt->needarg && *val)
167 		return _yopt_err(o, val, "Option '%s' does not accept an argument", o->argv[o->cur]);
168 	if(opt->needarg && !*val) {
169 		if(o->cur+1 >= o->argc)
170 			return _yopt_err(o, val, "Option '%s' requires an argument", o->argv[o->cur]);
171 		*val = o->argv[++o->cur];
172 	}
173 	return opt->val;
174 
175 	/* And here we're supposed to have a short option */
176 inshort:
177 	sh[0] = '-';
178 	sh[1] = *o->sh;
179 	sh[2] = 0;
180 	if(!(opt = _yopt_find(o->opts, sh)))
181 		return _yopt_err(o, val, "Unknown option '%s'", sh);
182 	o->sh++;
183 	if(opt->needarg && *o->sh)
184 		*val = o->sh;
185 	else if(opt->needarg) {
186 		if(++o->cur >= o->argc)
187 			return _yopt_err(o, val, "Option '%s' requires an argument", sh);
188 		*val = o->argv[o->cur];
189 	}
190 	if(!*o->sh || opt->needarg)
191 		o->sh = NULL;
192 	return opt->val;
193 }
194 
195 
196 #endif
197 
198 /* vim: set noet sw=4 ts=4: */
199