1 /*
2  * libtu/optparser.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2002.
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.
8  */
9 
10 #include <string.h>
11 #include <stdlib.h>
12 
13 #include <libtu/util.h>
14 #include <libtu/misc.h>
15 #include <libtu/optparser.h>
16 #include <libtu/output.h>
17 
18 
19 #define O_ARGS(o)		(o->flags&OPT_OPT_ARG)
20 #define O_ARG(o)		(o->flasg&OPT_ARG)
21 #define O_OPT_ARG(o)	(O_ARGS(o)==OPT_OPT_ARG)
22 #define O_ID(o)			(o->optid)
23 
24 #define OPT_ID_HELP OPT_ID_RESERVED('h')
25 #define OPT_ID_VERSION OPT_ID_RESERVED('V')
26 #define OPT_ID_ABOUT OPT_ID_RESERVED('a'|OPT_ID_NOSHORT_FLAG)
27 
28 
29 static OptParserOpt common_opts[]={
30 	{OPT_ID_HELP, "help", 0, NULL, DUMMY_TR("Show this  help")},
31 	{OPT_ID_VERSION, "version", 0, NULL, DUMMY_TR("Show program version")},
32 	{OPT_ID_ABOUT, "about", 0, NULL, DUMMY_TR("Show about text")},
33 	{0, NULL, 0, NULL, NULL}
34 };
35 
36 
37 static OptParserCommonInfo dummy_cinfo[]={
38 	NULL, /* version */
39 	"Usage: $p\n", /* usage_tmpl */
40 	NULL /* about */
41 };
42 
43 
44 static const OptParserOpt *o_opts=NULL;
45 static char *const *o_current=NULL;
46 static int o_left=0;
47 static const char* o_chain_ptr=NULL;
48 static int o_args_left=0;
49 static const char*o_tmp=NULL;
50 static int o_error=0;
51 static int o_mode=OPTP_CHAIN;
52 static const OptParserCommonInfo *o_cinfo=NULL;
53 
54 
55 /* */
56 
57 
58 static void print_help(const OptParserOpt *opts, bool midlong,
59 					   const OptParserCommonInfo *cinfo);
60 
61 
62 /* */
63 
64 
optparser_init(int argc,char * const argv[],int mode,const OptParserOpt * opts,const OptParserCommonInfo * cinfo)65 void optparser_init(int argc, char *const argv[], int mode,
66 					const OptParserOpt *opts, const OptParserCommonInfo *cinfo)
67 {
68 	o_mode=mode;
69 	o_opts=(opts==NULL ? common_opts : opts);
70 	o_current=argv+1;
71 	o_left=argc-1;
72 	o_chain_ptr=NULL;
73 	o_args_left=0;
74 	o_tmp=NULL;
75 	o_cinfo=(cinfo==NULL ? dummy_cinfo : cinfo);
76 }
77 
78 
79 /* */
80 
81 
find_chain_opt(char p,const OptParserOpt * o)82 static const OptParserOpt *find_chain_opt(char p, const OptParserOpt *o)
83 {
84 	for(;O_ID(o);o++){
85 		if((O_ID(o)&~OPT_ID_RESERVED_FLAG)==p)
86 			return o;
87 	}
88 	return NULL;
89 }
90 
91 
is_option(const char * p)92 static bool is_option(const char *p)
93 {
94 	 if(p==NULL)
95 		  return FALSE;
96 	 if(*p++!='-')
97 		  return FALSE;
98 	 if(*p++!='-')
99 		  return FALSE;
100 	 if(*p=='\0')
101 		  return FALSE;
102 	 return TRUE;
103 }
104 
105 
106 /* */
107 
108 enum{
109 	SHORT, MIDLONG, LONG
110 };
111 
112 
optparser_do_get_opt()113 static int optparser_do_get_opt()
114 {
115 #define RET(X) return o_tmp=p, o_error=X
116 	const char *p, *p2=NULL;
117 	bool dash=TRUE;
118 	int type=SHORT;
119 	const OptParserOpt *o;
120 	int l;
121 
122 	while(o_args_left)
123 		optparser_get_arg();
124 
125 	o_tmp=NULL;
126 
127 	/* Are we doing a chain (i.e. opt. of style 'tar xzf')? */
128 	if(o_chain_ptr!=NULL){
129 		p=o_chain_ptr++;
130 		if(!*o_chain_ptr)
131 			o_chain_ptr=NULL;
132 
133 		o=find_chain_opt(*p, o_opts);
134 
135 		if(o==NULL)
136 			RET(E_OPT_INVALID_CHAIN_OPTION);
137 
138 		goto do_arg;
139 	}
140 
141 	if(o_left<1)
142 		return OPT_ID_END;
143 
144 	o_left--;
145 	p=*o_current++;
146 
147 	if(*p!='-'){
148 		dash=FALSE;
149 		if(o_mode!=OPTP_NO_DASH)
150 			RET(OPT_ID_ARGUMENT);
151 		p2=p;
152 	}else if(*(p+1)=='-'){
153 		/* --foo */
154 		if(*(p+2)=='\0'){
155 			/* -- arguments */
156 			o_args_left=o_left;
157 			RET(OPT_ID_ARGUMENT);
158 		}
159 		type=LONG;
160 		p2=p+2;
161 	}else{
162 		/* -foo */
163 		if(*(p+1)=='\0'){
164 			/* - */
165 			o_args_left=1;
166 			RET(OPT_ID_ARGUMENT);
167 		}
168 		if(*(p+2)!='\0' && o_mode==OPTP_MIDLONG)
169 			type=MIDLONG;
170 
171 		p2=p+1;
172 	}
173 
174 	o=o_opts;
175 
176 again:
177 	for(; O_ID(o); o++){
178 		if(type==LONG){
179 			/* Do long option (--foo=bar) */
180 			if(o->longopt==NULL)
181 				continue;
182 			l=strlen(o->longopt);
183 			if(strncmp(p2, o->longopt, l)!=0)
184 				continue;
185 
186 			if(p2[l]=='\0'){
187 				if(O_ARGS(o)==OPT_ARG)
188 					 RET(E_OPT_MISSING_ARGUMENT);
189 				return O_ID(o);
190 			}else if(p2[l]=='='){
191 				if(!O_ARGS(o))
192 					 RET(E_OPT_UNEXPECTED_ARGUMENT);
193 				if(p2[l+1]=='\0')
194 					 RET(E_OPT_MISSING_ARGUMENT);
195 				o_tmp=p2+l+1;
196 				o_args_left=1;
197 				return O_ID(o);
198 			}
199 			continue;
200 		}else if(type==MIDLONG){
201 			if(o->longopt==NULL)
202 				continue;
203 
204 			if(strcmp(p2, o->longopt)!=0)
205 				continue;
206 		}else{ /* type==SHORT */
207 			if(*p2!=(O_ID(o)&~OPT_ID_RESERVED_FLAG))
208 				continue;
209 
210 			if(*(p2+1)!='\0'){
211 				if(o_mode==OPTP_CHAIN || o_mode==OPTP_NO_DASH){
212 					/*valid_chain(p2+1, o_opts)*/
213 					o_chain_ptr=p2+1;
214 					p++;
215 				}else if(o_mode==OPTP_IMMEDIATE){
216 					if(!O_ARGS(o)){
217 						if(*(p2+1)!='\0')
218 							RET(E_OPT_UNEXPECTED_ARGUMENT);
219 					}else{
220 						if(*(p2+1)=='\0')
221 							RET(E_OPT_MISSING_ARGUMENT);
222 						o_tmp=p2+1;
223 						o_args_left=1;
224 					}
225 					return O_ID(o);
226 				}else{
227 					RET(E_OPT_SYNTAX_ERROR);
228 				}
229 			}
230 		}
231 
232 	do_arg:
233 
234 		if(!O_ARGS(o))
235 			return O_ID(o);
236 
237 		if(!o_left || is_option(*o_current)){
238 			if(O_ARGS(o)==OPT_OPT_ARG)
239 				return O_ID(o);
240 			RET(E_OPT_MISSING_ARGUMENT);
241 		}
242 
243 		o_args_left=1;
244 		return O_ID(o);
245 	}
246 
247 	if(o!=&(common_opts[3])){
248 		o=common_opts;
249 		goto again;
250 	}
251 
252 	if(dash)
253 		RET(E_OPT_INVALID_OPTION);
254 
255 	RET(OPT_ID_ARGUMENT);
256 #undef RET
257 }
258 
259 
optparser_get_opt()260 int optparser_get_opt()
261 {
262 	int oid=optparser_do_get_opt();
263 
264 	if(oid<=0 || (oid&OPT_ID_RESERVED_FLAG)==0)
265 		return oid;
266 
267 	switch(oid){
268 	case OPT_ID_ABOUT:
269 		if(o_cinfo->about!=NULL)
270 			printf("%s", o_cinfo->about);
271 		break;
272 
273 	case OPT_ID_VERSION:
274 		if(o_cinfo->version!=NULL)
275 			printf("%s\n", o_cinfo->version);
276 		break;
277 
278 	case OPT_ID_HELP:
279 		print_help(o_opts, o_mode==OPTP_MIDLONG, o_cinfo);
280 		break;
281 	}
282 
283 	exit(EXIT_SUCCESS);
284 }
285 
286 
287 /* */
288 
289 
optparser_get_arg()290 const char* optparser_get_arg()
291 {
292 	const char *p;
293 
294 	if(o_tmp!=NULL){
295 		/* If o_args_left==0, then were returning an invalid option
296 		 * otherwise an immediate argument (e.g. -funsigned-char
297 		 * where '-f' is the option and 'unsigned-char' the argument)
298 		 */
299 		if(o_args_left>0)
300 			o_args_left--;
301 		p=o_tmp;
302 		o_tmp=NULL;
303 		return p;
304 	}
305 
306 	if(o_args_left<1 || o_left<1)
307 		return NULL;
308 
309 	o_left--;
310 	o_args_left--;
311 	return *o_current++;
312 }
313 
314 
315 /* */
316 
warn_arg(const char * e)317 static void warn_arg(const char *e)
318 {
319 	const char *p=optparser_get_arg();
320 
321 	if(p==NULL)
322 		warn("%s (null)", e);
323 	else
324 		warn("%s \'%s\'", e, p);
325 }
326 
327 
warn_opt(const char * e)328 static void warn_opt(const char *e)
329 {
330 	if(o_tmp!=NULL && o_chain_ptr!=NULL)
331 		warn("%s \'-%c\'", e, *o_tmp);
332 	else
333 		warn_arg(e);
334 }
335 
336 
optparser_print_error()337 void optparser_print_error()
338 {
339 	switch(o_error){
340 	case E_OPT_INVALID_OPTION:
341 	case E_OPT_INVALID_CHAIN_OPTION:
342 		warn_opt(TR("Invalid option"));
343 		break;
344 
345 	case E_OPT_SYNTAX_ERROR:
346 		warn_arg(TR("Syntax error while parsing"));
347 		break;
348 
349 	case E_OPT_MISSING_ARGUMENT:
350 		warn_opt(TR("Missing argument to"));
351 		break;
352 
353 	case E_OPT_UNEXPECTED_ARGUMENT:
354 		warn_opt(TR("No argument expected:"));
355 		break;
356 
357 	case OPT_ID_ARGUMENT:
358 		warn(TR("Unexpected argument"));
359 		break;
360 
361 	default:
362 		warn(TR("(unknown error)"));
363 	}
364 
365 	o_tmp=NULL;
366 	o_error=0;
367 }
368 
369 
370 /* */
371 
372 
opt_w(const OptParserOpt * opt,bool midlong)373 static uint opt_w(const OptParserOpt *opt, bool midlong)
374 {
375 	uint w=0;
376 
377 	if((opt->optid&OPT_ID_NOSHORT_FLAG)==0){
378 		w+=2; /* "-o" */
379 		if(opt->longopt!=NULL)
380 			w+=2; /* ", " */
381 	}
382 
383 	if(opt->longopt!=NULL)
384 		w+=strlen(opt->longopt)+(midlong ? 1 : 2);
385 
386 	if(O_ARGS(opt)){
387 		if(opt->argname==NULL)
388 			w+=4;
389 		else
390 			w+=1+strlen(opt->argname); /* "=ARG" or " ARG" */
391 
392 		if(O_OPT_ARG(opt))
393 			w+=2; /* [ARG] */
394 	}
395 
396 	return w;
397 }
398 
399 
400 #define TERM_W 80
401 #define OFF1 2
402 #define OFF2 2
403 #define SPACER1 "  "
404 #define SPACER2 "  "
405 
print_opt(const OptParserOpt * opt,bool midlong,uint maxw,uint tw)406 static void print_opt(const OptParserOpt *opt, bool midlong,
407 					  uint maxw, uint tw)
408 {
409 	FILE *f=stdout;
410 	const char *p, *p2, *p3;
411 	uint w=0;
412 
413 	fprintf(f, SPACER1);
414 
415 	/* short opt */
416 
417 	if((O_ID(opt)&OPT_ID_NOSHORT_FLAG)==0){
418 		fprintf(f, "-%c", O_ID(opt)&~OPT_ID_RESERVED_FLAG);
419 		w+=2;
420 
421 		if(opt->longopt!=NULL){
422 			fprintf(f, ", ");
423 			w+=2;
424 		}
425 	}
426 
427 	/* long opt */
428 
429 	if(opt->longopt!=NULL){
430 		if(midlong){
431 			w++;
432 			fprintf(f, "-%s", opt->longopt);
433 		}else{
434 			w+=2;
435 			fprintf(f, "--%s", opt->longopt);
436 		}
437 		w+=strlen(opt->longopt);
438 	}
439 
440 	/* arg */
441 
442 	if(O_ARGS(opt)){
443 		w++;
444 		if(opt->longopt!=NULL && !midlong)
445 			putc('=', f);
446 		else
447 			putc(' ', f);
448 
449 		if(O_OPT_ARG(opt)){
450 			w+=2;
451 			putc('[', f);
452 		}
453 
454 		if(opt->argname!=NULL){
455 			fprintf(f, "%s", opt->argname);
456 			w+=strlen(opt->argname);
457 		}else{
458 			w+=3;
459 			fprintf(f, "ARG");
460 		}
461 
462 		if(O_OPT_ARG(opt))
463 			putc(']', f);
464 	}
465 
466 	while(w++<maxw)
467 		putc(' ', f);
468 
469 	/* descr */
470 
471 	p=p2=opt->descr;
472 
473 	if(p==NULL){
474 		putc('\n', f);
475 		return;
476 	}
477 
478 	fprintf(f, SPACER2);
479 
480 	maxw+=OFF1+OFF2;
481 	tw-=maxw;
482 
483 	while(strlen(p)>tw){
484 		p3=p2=p+tw-2;
485 
486 		while(*p2!=' ' && p2!=p)
487 			p2--;
488 
489 		while(*p3!=' ' && *p3!='\0')
490 			p3++;
491 
492 		if((uint)(p3-p2)>tw){
493 			/* long word - just wrap */
494 			p2=p+tw-2;
495 		}
496 
497 		writef(f, p, p2-p);
498 		if(*p2==' ')
499 			putc('\n', f);
500 		else
501 			fprintf(f, "\\\n");
502 
503 		p=p2+1;
504 
505 		w=maxw;
506 		while(w--)
507 			putc(' ', f);
508 	}
509 
510 	fprintf(f, "%s\n", p);
511 }
512 
513 
print_opts(const OptParserOpt * opts,bool midlong,const OptParserCommonInfo * cinfo)514 static void print_opts(const OptParserOpt *opts, bool midlong,
515 					   const OptParserCommonInfo *cinfo)
516 {
517 	uint w, maxw=0;
518 	const OptParserOpt *o;
519 
520 	o=opts;
521 again:
522 	for(; O_ID(o); o++){
523 		w=opt_w(o, midlong);
524 		if(w>maxw)
525 			maxw=w;
526 	}
527 
528 	if(o!=&(common_opts[3])){
529 		o=common_opts;
530 		goto again;
531 	}
532 
533 	o=opts;
534 again2:
535 	for(; O_ID(o); o++)
536 		print_opt(o, midlong, maxw, TERM_W);
537 
538 	if(o!=&(common_opts[3])){
539 		printf("\n");
540 		o=common_opts;
541 		goto again2;
542 	}
543 }
544 
545 
print_help(const OptParserOpt * opts,bool midlong,const OptParserCommonInfo * cinfo)546 static void print_help(const OptParserOpt *opts, bool midlong,
547 					   const OptParserCommonInfo *cinfo)
548 {
549 	const char *tmp, *p=cinfo->usage_tmpl;
550 	size_t len;
551 	size_t start;
552 
553 	while(1){
554 		tmp=strchr(p, '$');
555 
556 		if(tmp==NULL){
557 			writef(stdout, p, strlen(p));
558 			return;
559 		}
560 
561 		if(tmp!=p)
562 			writef(stdout, p, tmp-p);
563 
564 		p=tmp+1;
565 
566 		if(*p=='p'){
567 			tmp=prog_execname();
568 			writef(stdout, tmp, strlen(tmp));
569 		}else if(*p=='o'){
570 			print_opts(opts, midlong, cinfo);
571 		}
572 		p++;
573 	}
574 }
575 
576