Thu, 24 Feb 2005 09:12:21 +0100
Symlist improvements.
0 | 1 | /* |
2 | * libtu/optparser.c | |
3 | * | |
79 | 4 | * Copyright (c) Tuomo Valkonen 1999-2004. |
53 | 5 | * |
6 | * You may distribute and modify this library under the terms of either | |
7 | * the Clarified Artistic License or the GNU LGPL, version 2.1 or later. | |
0 | 8 | */ |
9 | ||
10 | #include <string.h> | |
11 | #include <stdlib.h> | |
12 | ||
70 | 13 | #include "util.h" |
14 | #include "misc.h" | |
15 | #include "optparser.h" | |
16 | #include "output.h" | |
79 | 17 | #include "private.h" |
0 | 18 | |
19 | ||
62 | 20 | #define O_ARGS(o) (o->flags&OPT_OPT_ARG) |
21 | #define O_ARG(o) (o->flasg&OPT_ARG) | |
22 | #define O_OPT_ARG(o) (O_ARGS(o)==OPT_OPT_ARG) | |
23 | #define O_ID(o) (o->optid) | |
0 | 24 | |
25 | ||
26 | static const OptParserOpt *o_opts=NULL; | |
27 | static char *const *o_current=NULL; | |
28 | static int o_left=0; | |
29 | static const char* o_chain_ptr=NULL; | |
30 | static int o_args_left=0; | |
31 | static const char*o_tmp=NULL; | |
32 | static int o_error=0; | |
3 | 33 | static int o_mode=OPTP_CHAIN; |
0 | 34 | |
35 | ||
36 | /* */ | |
37 | ||
38 | ||
3 | 39 | void optparser_init(int argc, char *const argv[], int mode, |
79 | 40 | const OptParserOpt *opts) |
0 | 41 | { |
62 | 42 | o_mode=mode; |
79 | 43 | o_opts=opts; |
62 | 44 | o_current=argv+1; |
45 | o_left=argc-1; | |
46 | o_chain_ptr=NULL; | |
47 | o_args_left=0; | |
48 | o_tmp=NULL; | |
0 | 49 | } |
50 | ||
51 | ||
52 | /* */ | |
53 | ||
54 | ||
55 | static const OptParserOpt *find_chain_opt(char p, const OptParserOpt *o) | |
56 | { | |
62 | 57 | for(;O_ID(o);o++){ |
58 | if((O_ID(o)&~OPT_ID_RESERVED_FLAG)==p) | |
59 | return o; | |
60 | } | |
61 | return NULL; | |
0 | 62 | } |
63 | ||
64 | ||
65 | static bool is_option(const char *p) | |
66 | { | |
62 | 67 | if(p==NULL) |
68 | return FALSE; | |
69 | if(*p++!='-') | |
70 | return FALSE; | |
71 | if(*p++!='-') | |
72 | return FALSE; | |
73 | if(*p=='\0') | |
74 | return FALSE; | |
75 | return TRUE; | |
0 | 76 | } |
62 | 77 | |
0 | 78 | |
79 | /* */ | |
80 | ||
3 | 81 | enum{ |
62 | 82 | SHORT, MIDLONG, LONG |
3 | 83 | }; |
84 | ||
0 | 85 | |
79 | 86 | int optparser_get_opt() |
0 | 87 | { |
88 | #define RET(X) return o_tmp=p, o_error=X | |
62 | 89 | const char *p, *p2=NULL; |
90 | bool dash=TRUE; | |
91 | int type=SHORT; | |
92 | const OptParserOpt *o; | |
93 | int l; | |
0 | 94 | |
62 | 95 | while(o_args_left) |
96 | optparser_get_arg(); | |
0 | 97 | |
62 | 98 | o_tmp=NULL; |
0 | 99 | |
62 | 100 | /* Are we doing a chain (i.e. opt. of style 'tar xzf')? */ |
101 | if(o_chain_ptr!=NULL){ | |
102 | p=o_chain_ptr++; | |
103 | if(!*o_chain_ptr) | |
104 | o_chain_ptr=NULL; | |
0 | 105 | |
62 | 106 | o=find_chain_opt(*p, o_opts); |
107 | ||
108 | if(o==NULL) | |
109 | RET(E_OPT_INVALID_CHAIN_OPTION); | |
0 | 110 | |
62 | 111 | goto do_arg; |
112 | } | |
113 | ||
114 | if(o_left<1) | |
115 | return OPT_ID_END; | |
0 | 116 | |
62 | 117 | o_left--; |
118 | p=*o_current++; | |
119 | ||
120 | if(*p!='-'){ | |
121 | dash=FALSE; | |
122 | if(o_mode!=OPTP_NO_DASH) | |
123 | RET(OPT_ID_ARGUMENT); | |
124 | p2=p; | |
125 | }else if(*(p+1)=='-'){ | |
126 | /* --foo */ | |
127 | if(*(p+2)=='\0'){ | |
128 | /* -- arguments */ | |
129 | o_args_left=o_left; | |
130 | RET(OPT_ID_ARGUMENT); | |
131 | } | |
132 | type=LONG; | |
133 | p2=p+2; | |
134 | }else{ | |
135 | /* -foo */ | |
136 | if(*(p+1)=='\0'){ | |
137 | /* - */ | |
138 | o_args_left=1; | |
139 | RET(OPT_ID_ARGUMENT); | |
140 | } | |
141 | if(*(p+2)!='\0' && o_mode==OPTP_MIDLONG) | |
142 | type=MIDLONG; | |
143 | ||
144 | p2=p+1; | |
145 | } | |
0 | 146 | |
62 | 147 | o=o_opts; |
0 | 148 | |
62 | 149 | for(; O_ID(o); o++){ |
150 | if(type==LONG){ | |
151 | /* Do long option (--foo=bar) */ | |
152 | if(o->longopt==NULL) | |
153 | continue; | |
154 | l=strlen(o->longopt); | |
155 | if(strncmp(p2, o->longopt, l)!=0) | |
156 | continue; | |
157 | ||
158 | if(p2[l]=='\0'){ | |
159 | if(O_ARGS(o)==OPT_ARG) | |
160 | RET(E_OPT_MISSING_ARGUMENT); | |
161 | return O_ID(o); | |
162 | }else if(p2[l]=='='){ | |
163 | if(!O_ARGS(o)) | |
164 | RET(E_OPT_UNEXPECTED_ARGUMENT); | |
165 | if(p2[l+1]=='\0') | |
166 | RET(E_OPT_MISSING_ARGUMENT); | |
167 | o_tmp=p2+l+1; | |
168 | o_args_left=1; | |
169 | return O_ID(o); | |
170 | } | |
171 | continue; | |
172 | }else if(type==MIDLONG){ | |
173 | if(o->longopt==NULL) | |
174 | continue; | |
175 | ||
176 | if(strcmp(p2, o->longopt)!=0) | |
177 | continue; | |
178 | }else{ /* type==SHORT */ | |
179 | if(*p2!=(O_ID(o)&~OPT_ID_RESERVED_FLAG)) | |
180 | continue; | |
181 | ||
182 | if(*(p2+1)!='\0'){ | |
183 | if(o_mode==OPTP_CHAIN || o_mode==OPTP_NO_DASH){ | |
184 | /*valid_chain(p2+1, o_opts)*/ | |
185 | o_chain_ptr=p2+1; | |
186 | p++; | |
187 | }else if(o_mode==OPTP_IMMEDIATE){ | |
188 | if(!O_ARGS(o)){ | |
189 | if(*(p2+1)!='\0') | |
190 | RET(E_OPT_UNEXPECTED_ARGUMENT); | |
191 | }else{ | |
192 | if(*(p2+1)=='\0') | |
193 | RET(E_OPT_MISSING_ARGUMENT); | |
194 | o_tmp=p2+1; | |
195 | o_args_left=1; | |
196 | } | |
197 | return O_ID(o); | |
198 | }else{ | |
199 | RET(E_OPT_SYNTAX_ERROR); | |
200 | } | |
201 | } | |
202 | } | |
203 | ||
204 | do_arg: | |
205 | ||
206 | if(!O_ARGS(o)) | |
207 | return O_ID(o); | |
208 | ||
209 | if(!o_left || is_option(*o_current)){ | |
210 | if(O_ARGS(o)==OPT_OPT_ARG) | |
211 | return O_ID(o); | |
212 | RET(E_OPT_MISSING_ARGUMENT); | |
213 | } | |
0 | 214 | |
62 | 215 | o_args_left=1; |
216 | return O_ID(o); | |
217 | } | |
218 | ||
219 | if(dash) | |
220 | RET(E_OPT_INVALID_OPTION); | |
221 | ||
222 | RET(OPT_ID_ARGUMENT); | |
0 | 223 | #undef RET |
224 | } | |
225 | ||
226 | ||
227 | /* */ | |
228 | ||
229 | ||
230 | const char* optparser_get_arg() | |
231 | { | |
62 | 232 | const char *p; |
233 | ||
234 | if(o_tmp!=NULL){ | |
235 | /* If o_args_left==0, then were returning an invalid option | |
236 | * otherwise an immediate argument (e.g. -funsigned-char | |
237 | * where '-f' is the option and 'unsigned-char' the argument) | |
238 | */ | |
239 | if(o_args_left>0) | |
240 | o_args_left--; | |
241 | p=o_tmp; | |
242 | o_tmp=NULL; | |
243 | return p; | |
244 | } | |
245 | ||
246 | if(o_args_left<1 || o_left<1) | |
247 | return NULL; | |
0 | 248 | |
62 | 249 | o_left--; |
250 | o_args_left--; | |
251 | return *o_current++; | |
0 | 252 | } |
253 | ||
254 | ||
255 | /* */ | |
256 | ||
257 | static void warn_arg(const char *e) | |
258 | { | |
62 | 259 | const char *p=optparser_get_arg(); |
260 | ||
261 | if(p==NULL) | |
262 | warn("%s (null)", e); | |
263 | else | |
264 | warn("%s \'%s\'", e, p); | |
0 | 265 | } |
266 | ||
267 | ||
268 | static void warn_opt(const char *e) | |
269 | { | |
62 | 270 | if(o_tmp!=NULL && o_chain_ptr!=NULL) |
271 | warn("%s \'-%c\'", e, *o_tmp); | |
272 | else | |
273 | warn_arg(e); | |
0 | 274 | } |
275 | ||
276 | ||
277 | void optparser_print_error() | |
278 | { | |
62 | 279 | switch(o_error){ |
280 | case E_OPT_INVALID_OPTION: | |
281 | case E_OPT_INVALID_CHAIN_OPTION: | |
282 | warn_opt(TR("Invalid option")); | |
283 | break; | |
0 | 284 | |
62 | 285 | case E_OPT_SYNTAX_ERROR: |
286 | warn_arg(TR("Syntax error while parsing")); | |
287 | break; | |
288 | ||
289 | case E_OPT_MISSING_ARGUMENT: | |
290 | warn_opt(TR("Missing argument to")); | |
291 | break; | |
0 | 292 | |
62 | 293 | case E_OPT_UNEXPECTED_ARGUMENT: |
294 | warn_opt(TR("No argument expected:")); | |
295 | break; | |
296 | ||
297 | case OPT_ID_ARGUMENT: | |
298 | warn(TR("Unexpected argument")); | |
299 | break; | |
0 | 300 | |
62 | 301 | default: |
302 | warn(TR("(unknown error)")); | |
303 | } | |
304 | ||
305 | o_tmp=NULL; | |
306 | o_error=0; | |
0 | 307 | } |
12 | 308 | |
309 | ||
310 | /* */ | |
311 | ||
312 | ||
313 | static uint opt_w(const OptParserOpt *opt, bool midlong) | |
314 | { | |
62 | 315 | uint w=0; |
316 | ||
317 | if((opt->optid&OPT_ID_NOSHORT_FLAG)==0){ | |
318 | w+=2; /* "-o" */ | |
319 | if(opt->longopt!=NULL) | |
320 | w+=2; /* ", " */ | |
321 | } | |
322 | ||
323 | if(opt->longopt!=NULL) | |
324 | w+=strlen(opt->longopt)+(midlong ? 1 : 2); | |
325 | ||
326 | if(O_ARGS(opt)){ | |
327 | if(opt->argname==NULL) | |
328 | w+=4; | |
329 | else | |
330 | w+=1+strlen(opt->argname); /* "=ARG" or " ARG" */ | |
331 | ||
332 | if(O_OPT_ARG(opt)) | |
333 | w+=2; /* [ARG] */ | |
334 | } | |
335 | ||
336 | return w; | |
12 | 337 | } |
338 | ||
339 | ||
340 | #define TERM_W 80 | |
341 | #define OFF1 2 | |
342 | #define OFF2 2 | |
343 | #define SPACER1 " " | |
344 | #define SPACER2 " " | |
345 | ||
346 | static void print_opt(const OptParserOpt *opt, bool midlong, | |
62 | 347 | uint maxw, uint tw) |
12 | 348 | { |
62 | 349 | FILE *f=stdout; |
350 | const char *p, *p2, *p3; | |
351 | uint w=0; | |
352 | ||
353 | fprintf(f, SPACER1); | |
354 | ||
355 | /* short opt */ | |
356 | ||
357 | if((O_ID(opt)&OPT_ID_NOSHORT_FLAG)==0){ | |
358 | fprintf(f, "-%c", O_ID(opt)&~OPT_ID_RESERVED_FLAG); | |
359 | w+=2; | |
360 | ||
361 | if(opt->longopt!=NULL){ | |
362 | fprintf(f, ", "); | |
363 | w+=2; | |
364 | } | |
365 | } | |
366 | ||
367 | /* long opt */ | |
368 | ||
369 | if(opt->longopt!=NULL){ | |
370 | if(midlong){ | |
371 | w++; | |
372 | fprintf(f, "-%s", opt->longopt); | |
373 | }else{ | |
374 | w+=2; | |
375 | fprintf(f, "--%s", opt->longopt); | |
376 | } | |
377 | w+=strlen(opt->longopt); | |
378 | } | |
379 | ||
380 | /* arg */ | |
381 | ||
382 | if(O_ARGS(opt)){ | |
383 | w++; | |
384 | if(opt->longopt!=NULL && !midlong) | |
385 | putc('=', f); | |
386 | else | |
387 | putc(' ', f); | |
388 | ||
389 | if(O_OPT_ARG(opt)){ | |
390 | w+=2; | |
391 | putc('[', f); | |
392 | } | |
393 | ||
394 | if(opt->argname!=NULL){ | |
395 | fprintf(f, "%s", opt->argname); | |
396 | w+=strlen(opt->argname); | |
397 | }else{ | |
398 | w+=3; | |
399 | fprintf(f, "ARG"); | |
400 | } | |
12 | 401 | |
62 | 402 | if(O_OPT_ARG(opt)) |
403 | putc(']', f); | |
404 | } | |
405 | ||
406 | while(w++<maxw) | |
407 | putc(' ', f); | |
408 | ||
409 | /* descr */ | |
410 | ||
411 | p=p2=opt->descr; | |
412 | ||
413 | if(p==NULL){ | |
414 | putc('\n', f); | |
415 | return; | |
416 | } | |
417 | ||
418 | fprintf(f, SPACER2); | |
12 | 419 | |
62 | 420 | maxw+=OFF1+OFF2; |
421 | tw-=maxw; | |
422 | ||
423 | while(strlen(p)>tw){ | |
424 | p3=p2=p+tw-2; | |
425 | ||
426 | while(*p2!=' ' && p2!=p) | |
427 | p2--; | |
428 | ||
429 | while(*p3!=' ' && *p3!='\0') | |
430 | p3++; | |
431 | ||
432 | if((uint)(p3-p2)>tw){ | |
433 | /* long word - just wrap */ | |
434 | p2=p+tw-2; | |
435 | } | |
436 | ||
437 | writef(f, p, p2-p); | |
438 | if(*p2==' ') | |
439 | putc('\n', f); | |
440 | else | |
441 | fprintf(f, "\\\n"); | |
442 | ||
443 | p=p2+1; | |
444 | ||
445 | w=maxw; | |
446 | while(w--) | |
447 | putc(' ', f); | |
448 | } | |
449 | ||
450 | fprintf(f, "%s\n", p); | |
12 | 451 | } |
452 | ||
62 | 453 | |
79 | 454 | void optparser_printhelp(int mode, const OptParserOpt *opts) |
12 | 455 | { |
62 | 456 | uint w, maxw=0; |
457 | const OptParserOpt *o; | |
79 | 458 | bool midlong=mode&OPTP_MIDLONG; |
62 | 459 | |
460 | o=opts; | |
461 | for(; O_ID(o); o++){ | |
462 | w=opt_w(o, midlong); | |
463 | if(w>maxw) | |
464 | maxw=w; | |
465 | } | |
466 | ||
467 | o=opts; | |
79 | 468 | |
62 | 469 | for(; O_ID(o); o++) |
470 | print_opt(o, midlong, maxw, TERM_W); | |
12 | 471 | } |