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