1 /* [argparse.c wk 17.06.97] Argument Parser for option handling
2  * Copyright (C) 1998, 1999, 2000, 2001, 2006
3  *               2007, 2008  Free Software Foundation, Inc.
4  *
5  * This file is part of JNLIB.
6  *
7  * JNLIB is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * JNLIB is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  * 02110-1301, USA.
21  */
22 
23 #include <config.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include <string.h>
28 
29 #include "libjnlib-config.h"
30 #include "mischelp.h"
31 #include "stringhelp.h"
32 #include "logging.h"
33 #ifdef JNLIB_NEED_UTF8CONV
34 #include "utf8conv.h"
35 #endif
36 #include "argparse.h"
37 
38 
39 /*********************************
40  * @Summary arg_parse
41  *  #include <wk/lib.h>
42  *
43  *  typedef struct {
44  *	char *argc;		  pointer to argc (value subject to change)
45  *	char ***argv;		  pointer to argv (value subject to change)
46  *	unsigned flags; 	  Global flags (DO NOT CHANGE)
47  *	int err;		  print error about last option
48  *				  1 = warning, 2 = abort
49  *	int r_opt;		  return option
50  *	int r_type;		  type of return value (0 = no argument found)
51  *	union {
52  *	    int   ret_int;
53  *	    long  ret_long
54  *	    ulong ret_ulong;
55  *	    char *ret_str;
56  *	} r;			  Return values
57  *	struct {
58  *	    int idx;
59  *	    const char *last;
60  *	    void *aliases;
61  *	} internal;		  DO NOT CHANGE
62  *  } ARGPARSE_ARGS;
63  *
64  *  typedef struct {
65  *	int	    short_opt;
66  *	const char *long_opt;
67  *	unsigned flags;
68  *  } ARGPARSE_OPTS;
69  *
70  *  int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts );
71  *
72  * @Description
73  *  This is my replacement for getopt(). See the example for a typical usage.
74  *  Global flags are:
75  *     Bit 0 : Do not remove options form argv
76  *     Bit 1 : Do not stop at last option but return other args
77  *	       with r_opt set to -1.
78  *     Bit 2 : Assume options and real args are mixed.
79  *     Bit 3 : Do not use -- to stop option processing.
80  *     Bit 4 : Do not skip the first arg.
81  *     Bit 5 : allow usage of long option with only one dash
82  *     Bit 6 : ignore --version
83  *     all other bits must be set to zero, this value is modified by the
84  *     function, so assume this is write only.
85  *  Local flags (for each option):
86  *     Bit 2-0 : 0 = does not take an argument
87  *		 1 = takes int argument
88  *		 2 = takes string argument
89  *		 3 = takes long argument
90  *		 4 = takes ulong argument
91  *     Bit 3 : argument is optional (r_type will the be set to 0)
92  *     Bit 4 : allow 0x etc. prefixed values.
93  *     Bit 7 : this is a command and not an option
94  *  You stop the option processing by setting opts to NULL, the function will
95  *  then return 0.
96  * @Return Value
97  *   Returns the args.r_opt or 0 if ready
98  *   r_opt may be -2/-7 to indicate an unknown option/command.
99  * @See Also
100  *   ArgExpand
101  * @Notes
102  *  You do not need to process the options 'h', '--help' or '--version'
103  *  because this function includes standard help processing; but if you
104  *  specify '-h', '--help' or '--version' you have to do it yourself.
105  *  The option '--' stops argument processing; if bit 1 is set the function
106  *  continues to return normal arguments.
107  *  To process float args or unsigned args you must use a string args and do
108  *  the conversion yourself.
109  * @Example
110  *
111  *     ARGPARSE_OPTS opts[] = {
112  *     { 'v', "verbose",   0 },
113  *     { 'd', "debug",     0 },
114  *     { 'o', "output",    2 },
115  *     { 'c', "cross-ref", 2|8 },
116  *     { 'm', "my-option", 1|8 },
117  *     { 500, "have-no-short-option-for-this-long-option", 0 },
118  *     {0} };
119  *     ARGPARSE_ARGS pargs = { &argc, &argv, 0 }
120  *
121  *     while( ArgParse( &pargs, &opts) ) {
122  *	   switch( pargs.r_opt ) {
123  *	     case 'v': opt.verbose++; break;
124  *	     case 'd': opt.debug++; break;
125  *	     case 'o': opt.outfile = pargs.r.ret_str; break;
126  *	     case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break;
127  *	     case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break;
128  *	     case 500: opt.a_long_one++;  break
129  *	     default : pargs.err = 1; break; -- force warning output --
130  *	   }
131  *     }
132  *     if( argc > 1 )
133  *	   log_fatal( "Too many args");
134  *
135  */
136 
137 typedef struct alias_def_s *ALIAS_DEF;
138 struct alias_def_s {
139     ALIAS_DEF next;
140     char *name;   /* malloced buffer with name, \0, value */
141     const char *value; /* ptr into name */
142 };
143 
144 static const char *(*strusage_handler)( int ) = NULL;
145 
146 static int  set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s);
147 static void show_help(ARGPARSE_OPTS *opts, unsigned flags);
148 static void show_version(void);
149 
150 
151 static void
initialize(ARGPARSE_ARGS * arg,const char * filename,unsigned * lineno)152 initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno )
153 {
154     if( !(arg->flags & (1<<15)) ) { /* initialize this instance */
155 	arg->internal.idx = 0;
156 	arg->internal.last = NULL;
157 	arg->internal.inarg = 0;
158 	arg->internal.stopped = 0;
159 	arg->internal.aliases = NULL;
160 	arg->internal.cur_alias = NULL;
161 	arg->err = 0;
162 	arg->flags |= 1<<15; /* mark initialized */
163 	if( *arg->argc < 0 )
164 	    jnlib_log_bug("Invalid argument for ArgParse\n");
165     }
166 
167 
168     if( arg->err ) { /* last option was erroneous */
169 	const char *s;
170 
171 	if( filename ) {
172 	    if( arg->r_opt == -6 )
173 		s = "argument not expected\n";
174 	    else if( arg->r_opt == -5 )
175 		s = "read error\n";
176 	    else if( arg->r_opt == -4 )
177 		s = "keyword too long\n";
178 	    else if( arg->r_opt == -3 )
179 		s = "missing argument\n";
180 	    else if( arg->r_opt == -7 )
181 		s = "invalid command\n";
182 	    else if( arg->r_opt == -10 )
183 		s = "invalid alias definition\n";
184 	    else
185 		s = "invalid option\n";
186 	    jnlib_log_error("%s:%u: %s\n", filename, *lineno, s);
187 	}
188 	else {
189             s = arg->internal.last? arg->internal.last:"[??]";
190 
191 	    if( arg->r_opt == -3 )
192               jnlib_log_error ("Missing argument for option \"%.50s\"\n", s);
193 	    else if( arg->r_opt == -6 )
194               jnlib_log_error ("Option \"%.50s\" does not expect an argument\n",
195                                s );
196 	    else if( arg->r_opt == -7 )
197               jnlib_log_error ("Invalid command \"%.50s\"\n", s);
198 	    else if( arg->r_opt == -8 )
199               jnlib_log_error ("Option \"%.50s\" is ambiguous\n", s);
200 	    else if( arg->r_opt == -9 )
201               jnlib_log_error ("Command \"%.50s\" is ambiguous\n",s );
202 	    else
203               jnlib_log_error ("Invalid option \"%.50s\"\n", s);
204 	}
205 	if( arg->err != 1 )
206 	    exit(2);
207 	arg->err = 0;
208     }
209 
210     /* clearout the return value union */
211     arg->r.ret_str = NULL;
212     arg->r.ret_long= 0;
213 }
214 
215 
216 static void
store_alias(ARGPARSE_ARGS * arg,char * name,char * value)217 store_alias( ARGPARSE_ARGS *arg, char *name, char *value )
218 {
219   (void)arg;
220   (void)name;
221   (void)value;
222   /* TODO: replace this dummy function with a real one
223    * and fix the problems IRIX has with (ALIAS_DEV)arg..
224    * used as lvalue.  */
225 #if 0
226   ALIAS_DEF a = jnlib_xmalloc( sizeof *a );
227   a->name = name;
228   a->value = value;
229   a->next = (ALIAS_DEF)arg->internal.aliases;
230   (ALIAS_DEF)arg->internal.aliases = a;
231 #endif
232 }
233 
234 /****************
235  * Get options from a file.
236  * Lines starting with '#' are comment lines.
237  * Syntax is simply a keyword and the argument.
238  * Valid keywords are all keywords from the long_opt list without
239  * the leading dashes. The special keywords "help", "warranty" and "version"
240  * are not valid here.
241  * The special keyword "alias" may be used to store alias definitions,
242  * which are later expanded like long options.
243  * Caller must free returned strings.
244  * If called with FP set to NULL command line args are parse instead.
245  *
246  * Q: Should we allow the syntax
247  *     keyword = value
248  *    and accept for boolean options a value of 1/0, yes/no or true/false?
249  * Note: Abbreviation of options is here not allowed.
250  */
251 int
optfile_parse(FILE * fp,const char * filename,unsigned * lineno,ARGPARSE_ARGS * arg,ARGPARSE_OPTS * opts)252 optfile_parse( FILE *fp, const char *filename, unsigned *lineno,
253 	       ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts)
254 {
255     int state, i, c;
256     int idx=0;
257     char keyword[100];
258     char *buffer = NULL;
259     size_t buflen = 0;
260     int inverse=0;
261     int in_alias=0;
262 
263     if( !fp ) /* same as arg_parse() in this case */
264 	return arg_parse( arg, opts );
265 
266     initialize( arg, filename, lineno );
267 
268     /* find the next keyword */
269     state = i = 0;
270     for(;;) {
271 	c=getc(fp);
272 	if( c == '\n' || c== EOF ) {
273 	    if( c != EOF )
274 		++*lineno;
275 	    if( state == -1 )
276 		break;
277 	    else if( state == 2 ) {
278 		keyword[i] = 0;
279 		for(i=0; opts[i].short_opt; i++ )
280 		    if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) )
281 			break;
282 		idx = i;
283 		arg->r_opt = opts[idx].short_opt;
284 		if( inverse ) /* this does not have an effect, hmmm */
285 		    arg->r_opt = -arg->r_opt;
286 		if( !opts[idx].short_opt )   /* unknown command/option */
287 		    arg->r_opt = (opts[idx].flags & 256)? -7:-2;
288 		else if( !(opts[idx].flags & 7) ) /* does not take an arg */
289 		    arg->r_type = 0;	       /* okay */
290 		else if( (opts[idx].flags & 8) )  /* argument is optional */
291                     arg->r_type = 0;	       /* okay */
292 		else			       /* required argument */
293 		    arg->r_opt = -3;	       /* error */
294 		break;
295 	    }
296 	    else if( state == 3 ) {	       /* no argument found */
297 		if( in_alias )
298 		    arg->r_opt = -3;	       /* error */
299 		else if( !(opts[idx].flags & 7) ) /* does not take an arg */
300 		    arg->r_type = 0;	       /* okay */
301 		else if( (opts[idx].flags & 8) )  /* no optional argument */
302 		    arg->r_type = 0;	       /* okay */
303 		else			       /* no required argument */
304 		    arg->r_opt = -3;	       /* error */
305 		break;
306 	    }
307 	    else if( state == 4 ) {	/* have an argument */
308 		if( in_alias ) {
309 		    if( !buffer )
310 			arg->r_opt = -6;
311 		    else {
312 			char *p;
313 
314 			buffer[i] = 0;
315 			p = strpbrk( buffer, " \t" );
316 			if( p ) {
317 			    *p++ = 0;
318 			    trim_spaces( p );
319 			}
320 			if( !p || !*p ) {
321 			    jnlib_free( buffer );
322 			    arg->r_opt = -10;
323 			}
324 			else {
325 			    store_alias( arg, buffer, p );
326 			}
327 		    }
328 		}
329 		else if( !(opts[idx].flags & 7) )  /* does not take an arg */
330 		    arg->r_opt = -6;	    /* error */
331 		else {
332 		    char *p;
333 		    if( !buffer ) {
334 			keyword[i] = 0;
335 			buffer = jnlib_xstrdup(keyword);
336 		    }
337 		    else
338 			buffer[i] = 0;
339 
340 		    trim_spaces( buffer );
341 		    p = buffer;
342 		    if( *p == '"' ) { /* remove quotes */
343 			p++;
344 			if( *p && p[strlen(p)-1] == '"' )
345 			    p[strlen(p)-1] = 0;
346 		    }
347 		    if( !set_opt_arg(arg, opts[idx].flags, p) )
348 			jnlib_free(buffer);
349 		}
350 		break;
351 	    }
352 	    else if( c == EOF ) {
353 		if( ferror(fp) )
354 		    arg->r_opt = -5;   /* read error */
355 		else
356 		    arg->r_opt = 0;    /* eof */
357 		break;
358 	    }
359 	    state = 0;
360 	    i = 0;
361 	}
362 	else if( state == -1 )
363 	    ; /* skip */
364 	else if( !state && isspace(c) )
365 	    ; /* skip leading white space */
366 	else if( !state && c == '#' )
367 	    state = 1;	/* start of a comment */
368 	else if( state == 1 )
369 	    ; /* skip comments */
370 	else if( state == 2 && isspace(c) ) {
371 	    keyword[i] = 0;
372 	    for(i=0; opts[i].short_opt; i++ )
373 		if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) )
374 		    break;
375 	    idx = i;
376 	    arg->r_opt = opts[idx].short_opt;
377 	    if( !opts[idx].short_opt ) {
378 		if( !strcmp( keyword, "alias" ) ) {
379 		    in_alias = 1;
380 		    state = 3;
381 		}
382 		else {
383 		    arg->r_opt = (opts[idx].flags & 256)? -7:-2;
384 		    state = -1;        /* skip rest of line and leave */
385 		}
386 	    }
387 	    else
388 		state = 3;
389 	}
390 	else if( state == 3 ) { /* skip leading spaces of the argument */
391 	    if( !isspace(c) ) {
392 		i = 0;
393 		keyword[i++] = c;
394 		state = 4;
395 	    }
396 	}
397 	else if( state == 4 ) { /* collect the argument */
398 	    if( buffer ) {
399 		if( i < buflen-1 )
400 		    buffer[i++] = c;
401 		else {
402 		    buflen += 50;
403 		    buffer = jnlib_xrealloc(buffer, buflen);
404 		    buffer[i++] = c;
405 		}
406 	    }
407 	    else if( i < DIM(keyword)-1 )
408 		keyword[i++] = c;
409 	    else {
410 		buflen = DIM(keyword)+50;
411 		buffer = jnlib_xmalloc(buflen);
412 		memcpy(buffer, keyword, i);
413 		buffer[i++] = c;
414 	    }
415 	}
416 	else if( i >= DIM(keyword)-1 ) {
417 	    arg->r_opt = -4;   /* keyword to long */
418 	    state = -1;        /* skip rest of line and leave */
419 	}
420 	else {
421 	    keyword[i++] = c;
422 	    state = 2;
423 	}
424     }
425 
426     return arg->r_opt;
427 }
428 
429 
430 
431 static int
find_long_option(ARGPARSE_ARGS * arg,ARGPARSE_OPTS * opts,const char * keyword)432 find_long_option( ARGPARSE_ARGS *arg,
433 		  ARGPARSE_OPTS *opts, const char *keyword )
434 {
435     int i;
436     size_t n;
437 
438     (void)arg;
439 
440     /* Would be better if we can do a binary search, but it is not
441        possible to reorder our option table because we would mess
442        up our help strings - What we can do is: Build a nice option
443        lookup table wehn this function is first invoked */
444     if( !*keyword )
445 	return -1;
446     for(i=0; opts[i].short_opt; i++ )
447 	if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) )
448 	    return i;
449 #if 0
450     {
451 	ALIAS_DEF a;
452 	/* see whether it is an alias */
453 	for( a = args->internal.aliases; a; a = a->next ) {
454 	    if( !strcmp( a->name, keyword) ) {
455 		/* todo: must parse the alias here */
456 		args->internal.cur_alias = a;
457 		return -3; /* alias available */
458 	    }
459 	}
460     }
461 #endif
462     /* not found, see whether it is an abbreviation */
463     /* aliases may not be abbreviated */
464     n = strlen( keyword );
465     for(i=0; opts[i].short_opt; i++ ) {
466 	if( opts[i].long_opt && !strncmp( opts[i].long_opt, keyword, n ) ) {
467 	    int j;
468 	    for(j=i+1; opts[j].short_opt; j++ ) {
469 		if( opts[j].long_opt
470 		    && !strncmp( opts[j].long_opt, keyword, n ) )
471 		    return -2;	/* abbreviation is ambiguous */
472 	    }
473 	    return i;
474 	}
475     }
476     return -1;
477 }
478 
479 int
arg_parse(ARGPARSE_ARGS * arg,ARGPARSE_OPTS * opts)480 arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts)
481 {
482     int idx;
483     int argc;
484     char **argv;
485     char *s, *s2;
486     int i;
487 
488     initialize( arg, NULL, NULL );
489     argc = *arg->argc;
490     argv = *arg->argv;
491     idx = arg->internal.idx;
492 
493     if( !idx && argc && !(arg->flags & (1<<4)) ) { /* skip the first entry */
494 	argc--; argv++; idx++;
495     }
496 
497   next_one:
498     if( !argc ) { /* no more args */
499 	arg->r_opt = 0;
500 	goto leave; /* ready */
501     }
502 
503     s = *argv;
504     arg->internal.last = s;
505 
506     if( arg->internal.stopped && (arg->flags & (1<<1)) ) {
507 	arg->r_opt = -1;  /* not an option but a argument */
508 	arg->r_type = 2;
509 	arg->r.ret_str = s;
510 	argc--; argv++; idx++; /* set to next one */
511     }
512     else if( arg->internal.stopped ) { /* ready */
513 	arg->r_opt = 0;
514 	goto leave;
515     }
516     else if( *s == '-' && s[1] == '-' ) { /* long option */
517 	char *argpos;
518 
519 	arg->internal.inarg = 0;
520 	if( !s[2] && !(arg->flags & (1<<3)) ) { /* stop option processing */
521 	    arg->internal.stopped = 1;
522 	    argc--; argv++; idx++;
523 	    goto next_one;
524 	}
525 
526 	argpos = strchr( s+2, '=' );
527 	if( argpos )
528 	    *argpos = 0;
529 	i = find_long_option( arg, opts, s+2 );
530 	if( argpos )
531 	    *argpos = '=';
532 
533 	if( i < 0 && !strcmp( "help", s+2) )
534 	    show_help(opts, arg->flags);
535 	else if( i < 0 && !strcmp( "version", s+2) ) {
536 	    if( !(arg->flags & (1<<6)) ) {
537 		show_version();
538 		exit(0);
539 	    }
540 	}
541 	else if( i < 0 && !strcmp( "warranty", s+2) ) {
542 	    puts( strusage(16) );
543 	    exit(0);
544 	}
545 	else if( i < 0 && !strcmp( "dump-options", s+2) ) {
546 	    for(i=0; opts[i].short_opt; i++ ) {
547 		if( opts[i].long_opt )
548 		    printf( "--%s\n", opts[i].long_opt );
549 	    }
550 	    fputs("--dump-options\n--help\n--version\n--warranty\n", stdout );
551 	    exit(0);
552 	}
553 
554 	if( i == -2 ) /* ambiguous option */
555 	    arg->r_opt = -8;
556 	else if( i == -1 ) {
557 	    arg->r_opt = -2;
558 	    arg->r.ret_str = s+2;
559 	}
560 	else
561 	    arg->r_opt = opts[i].short_opt;
562 	if( i < 0 )
563 	    ;
564 	else if( (opts[i].flags & 7) ) {
565 	    if( argpos ) {
566 		s2 = argpos+1;
567 		if( !*s2 )
568 		    s2 = NULL;
569 	    }
570 	    else
571 		s2 = argv[1];
572 	    if( !s2 && (opts[i].flags & 8) ) { /* no argument but it is okay*/
573 		arg->r_type = 0;	       /* because it is optional */
574 	    }
575 	    else if( !s2 ) {
576 		arg->r_opt = -3; /* missing argument */
577 	    }
578 	    else if( !argpos && *s2 == '-' && (opts[i].flags & 8) ) {
579 		/* the argument is optional and the next seems to be
580 		 * an option. We do not check this possible option
581 		 * but assume no argument */
582 		arg->r_type = 0;
583 	    }
584 	    else {
585 		set_opt_arg(arg, opts[i].flags, s2);
586 		if( !argpos ) {
587 		    argc--; argv++; idx++; /* skip one */
588 		}
589 	    }
590 	}
591 	else { /* does not take an argument */
592 	    if( argpos )
593 		arg->r_type = -6; /* argument not expected */
594 	    else
595 		arg->r_type = 0;
596 	}
597 	argc--; argv++; idx++; /* set to next one */
598     }
599     else if( (*s == '-' && s[1]) || arg->internal.inarg ) { /* short option */
600 	int dash_kludge = 0;
601 	i = 0;
602 	if( !arg->internal.inarg ) {
603 	    arg->internal.inarg++;
604 	    if( arg->flags & (1<<5) ) {
605 		for(i=0; opts[i].short_opt; i++ )
606 		    if( opts[i].long_opt && !strcmp( opts[i].long_opt, s+1)) {
607 			dash_kludge=1;
608 			break;
609 		    }
610 	    }
611 	}
612 	s += arg->internal.inarg;
613 
614 	if( !dash_kludge ) {
615 	    for(i=0; opts[i].short_opt; i++ )
616 		if( opts[i].short_opt == *s )
617 		    break;
618 	}
619 
620 	if( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) )
621 	    show_help(opts, arg->flags);
622 
623 	arg->r_opt = opts[i].short_opt;
624 	if( !opts[i].short_opt ) {
625 	    arg->r_opt = (opts[i].flags & 256)? -7:-2;
626 	    arg->internal.inarg++; /* point to the next arg */
627 	    arg->r.ret_str = s;
628 	}
629 	else if( (opts[i].flags & 7) ) {
630 	    if( s[1] && !dash_kludge ) {
631 		s2 = s+1;
632 		set_opt_arg(arg, opts[i].flags, s2);
633 	    }
634 	    else {
635 		s2 = argv[1];
636 		if( !s2 && (opts[i].flags & 8) ) { /* no argument but it is okay*/
637 		    arg->r_type = 0;		   /* because it is optional */
638 		}
639 		else if( !s2 ) {
640 		    arg->r_opt = -3; /* missing argument */
641 		}
642 		else if( *s2 == '-' && s2[1] && (opts[i].flags & 8) ) {
643 		    /* the argument is optional and the next seems to be
644 		     * an option. We do not check this possible option
645 		     * but assume no argument */
646 		    arg->r_type = 0;
647 		}
648 		else {
649 		    set_opt_arg(arg, opts[i].flags, s2);
650 		    argc--; argv++; idx++; /* skip one */
651 		}
652 	    }
653 	    s = "x"; /* so that !s[1] yields false */
654 	}
655 	else { /* does not take an argument */
656 	    arg->r_type = 0;
657 	    arg->internal.inarg++; /* point to the next arg */
658 	}
659 	if( !s[1] || dash_kludge ) { /* no more concatenated short options */
660 	    arg->internal.inarg = 0;
661 	    argc--; argv++; idx++;
662 	}
663     }
664     else if( arg->flags & (1<<2) ) {
665 	arg->r_opt = -1;  /* not an option but a argument */
666 	arg->r_type = 2;
667 	arg->r.ret_str = s;
668 	argc--; argv++; idx++; /* set to next one */
669     }
670     else {
671 	arg->internal.stopped = 1; /* stop option processing */
672 	goto next_one;
673     }
674 
675   leave:
676     *arg->argc = argc;
677     *arg->argv = argv;
678     arg->internal.idx = idx;
679     return arg->r_opt;
680 }
681 
682 
683 
684 static int
set_opt_arg(ARGPARSE_ARGS * arg,unsigned flags,char * s)685 set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s)
686 {
687     int base = (flags & 16)? 0 : 10;
688 
689     switch( arg->r_type = (flags & 7) ) {
690       case 1: /* takes int argument */
691 	arg->r.ret_int = (int)strtol(s,NULL,base);
692 	return 0;
693       case 3: /* takes long argument   */
694 	arg->r.ret_long= strtol(s,NULL,base);
695 	return 0;
696       case 4: /* takes ulong argument  */
697 	arg->r.ret_ulong= strtoul(s,NULL,base);
698 	return 0;
699       case 2: /* takes string argument */
700       default:
701 	arg->r.ret_str = s;
702 	return 1;
703     }
704 }
705 
706 
707 static size_t
long_opt_strlen(ARGPARSE_OPTS * o)708 long_opt_strlen( ARGPARSE_OPTS *o )
709 {
710   size_t n = strlen (o->long_opt);
711 
712   if ( o->description && *o->description == '|' )
713     {
714       const char *s;
715 #ifdef JNLIB_NEED_UTF8CONV
716       int is_utf8 = is_native_utf8 ();
717 #endif
718 
719       s=o->description+1;
720       if ( *s != '=' )
721         n++;
722       /* For a (mostly) correct length calculation we exclude
723          continuation bytes (10xxxxxx) if we are on a native utf8
724          terminal. */
725       for (; *s && *s != '|'; s++ )
726 #ifdef JNLIB_NEED_UTF8CONV
727         if ( is_utf8 && (*s&0xc0) != 0x80 )
728 #endif
729           n++;
730     }
731   return n;
732 }
733 
734 /****************
735  * Print formatted help. The description string has some special
736  * meanings:
737  *  - A description string which is "@" suppresses help output for
738  *    this option
739  *  - a description,ine which starts with a '@' and is followed by
740  *    any other characters is printed as is; this may be used for examples
741  *    ans such.
742  *  - A description which starts with a '|' outputs the string between this
743  *    bar and the next one as arguments of the long option.
744  */
745 static void
show_help(ARGPARSE_OPTS * opts,unsigned flags)746 show_help( ARGPARSE_OPTS *opts, unsigned flags )
747 {
748     const char *s;
749 
750     show_version();
751     putchar('\n');
752     s = strusage(41);
753     puts(s);
754     if( opts[0].description ) { /* auto format the option description */
755 	int i,j, indent;
756 	/* get max. length of long options */
757 	for(i=indent=0; opts[i].short_opt; i++ ) {
758 	    if( opts[i].long_opt )
759 		if( !opts[i].description || *opts[i].description != '@' )
760 		    if( (j=long_opt_strlen(opts+i)) > indent && j < 35 )
761 			 indent = j;
762 	}
763 	/* example: " -v, --verbose   Viele Sachen ausgeben" */
764 	indent += 10;
765 	if( *opts[0].description != '@' )
766 	    puts("Options:");
767 	for(i=0; opts[i].short_opt; i++ ) {
768 	    s = _( opts[i].description );
769 	    if( s && *s== '@' && !s[1] ) /* hide this line */
770 		continue;
771 	    if( s && *s == '@' ) { /* unindented comment only line */
772 		for(s++; *s; s++ ) {
773 		    if( *s == '\n' ) {
774 			if( s[1] )
775 			    putchar('\n');
776 		    }
777 		    else
778 			putchar(*s);
779 		}
780 		putchar('\n');
781 		continue;
782 	    }
783 
784 	    j = 3;
785 	    if( opts[i].short_opt < 256 ) {
786 		printf(" -%c", opts[i].short_opt );
787 		if( !opts[i].long_opt ) {
788 		    if(s && *s == '|' ) {
789 			putchar(' '); j++;
790 			for(s++ ; *s && *s != '|'; s++, j++ )
791 			    putchar(*s);
792 			if( *s )
793 			    s++;
794 		    }
795 		}
796 	    }
797 	    else
798 		fputs("   ", stdout);
799 	    if( opts[i].long_opt ) {
800 		j += printf("%c --%s", opts[i].short_opt < 256?',':' ',
801 				       opts[i].long_opt );
802 		if(s && *s == '|' ) {
803 		    if( *++s != '=' ) {
804 			putchar(' ');
805 			j++;
806 		    }
807 		    for( ; *s && *s != '|'; s++, j++ )
808 			putchar(*s);
809 		    if( *s )
810 			s++;
811 		}
812 		fputs("   ", stdout);
813 		j += 3;
814 	    }
815 	    for(;j < indent; j++ )
816 		putchar(' ');
817 	    if( s ) {
818 		if( *s && j > indent ) {
819 		    putchar('\n');
820 		    for(j=0;j < indent; j++ )
821 			putchar(' ');
822 		}
823 		for(; *s; s++ ) {
824 		    if( *s == '\n' ) {
825 			if( s[1] ) {
826 			    putchar('\n');
827 			    for(j=0;j < indent; j++ )
828 				putchar(' ');
829 			}
830 		    }
831 		    else
832 			putchar(*s);
833 		}
834 	    }
835 	    putchar('\n');
836 	}
837 	if( flags & 32 )
838 	    puts("\n(A single dash may be used instead of the double ones)");
839     }
840     if( (s=strusage(19)) ) {  /* bug reports to ... */
841         char *s2;
842 
843 	putchar('\n');
844         s2 = strstr (s, "@EMAIL@");
845         if (s2)
846           {
847             if (s2-s)
848               fwrite (s, s2-s, 1, stdout);
849             fputs (PACKAGE_BUGREPORT, stdout);
850             s2 += 7;
851             if (*s2)
852               fputs (s2, stdout);
853           }
854         else
855           fputs(s, stdout);
856     }
857     fflush(stdout);
858     exit(0);
859 }
860 
861 static void
show_version()862 show_version()
863 {
864     const char *s;
865     int i;
866     /* version line */
867     fputs(strusage(11), stdout);
868     if( (s=strusage(12)) )
869 	printf(" (%s)", s );
870     printf(" %s\n", strusage(13) );
871     /* additional version lines */
872     for(i=20; i < 30; i++ )
873 	if( (s=strusage(i)) )
874 	    printf("%s\n", s );
875     /* copyright string */
876     if( (s=strusage(14)) )
877 	printf("%s\n", s );
878     /* copying conditions */
879     if( (s=strusage(15)) )
880 	fputs(s, stdout);
881     /* thanks */
882     if( (s=strusage(18)) )
883 	fputs(s, stdout);
884     /* additional program info */
885     for(i=30; i < 40; i++ )
886 	if( (s=strusage(i)) )
887 	    fputs (s, stdout);
888     fflush(stdout);
889 }
890 
891 
892 void
usage(int level)893 usage( int level )
894 {
895     if( !level ) {
896 	fprintf(stderr,"%s %s; %s\n", strusage(11), strusage(13),
897 						     strusage(14) );
898 	fflush(stderr);
899     }
900     else if( level == 1 ) {
901 	fputs(strusage(40),stderr);
902 	exit(2);
903     }
904     else if( level == 2 ) {
905 	puts(strusage(41));
906 	exit(0);
907     }
908 }
909 
910 /* Level
911  *     0: Copyright String auf stderr ausgeben
912  *     1: Kurzusage auf stderr ausgeben und beenden
913  *     2: Langusage auf stdout ausgeben und beenden
914  *    11: name of program
915  *    12: optional name of package which includes this program.
916  *    13: version  string
917  *    14: copyright string
918  *    15: Short copying conditions (with LFs)
919  *    16: Long copying conditions (with LFs)
920  *    17: Optional printable OS name
921  *    18: Optional thanks list	 (with LFs)
922  *    19: Bug report info
923  *20..29: Additional lib version strings.
924  *30..39: Additional program info (with LFs)
925  *    40: short usage note (with LF)
926  *    41: long usage note (with LF)
927  */
928 const char *
strusage(int level)929 strusage( int level )
930 {
931     const char *p = strusage_handler? strusage_handler(level) : NULL;
932 
933     if( p )
934 	return p;
935 
936     switch( level ) {
937       case 11: p = "foo"; break;
938       case 13: p = "0.0"; break;
939       case 14: p = "Copyright (C) 2010 Free Software Foundation, Inc."; break;
940       case 15: p =
941 "This program comes with ABSOLUTELY NO WARRANTY.\n"
942 "This is free software, and you are welcome to redistribute it\n"
943 "under certain conditions. See the file COPYING for details.\n"; break;
944       case 16:	p =
945 "This is free software; you can redistribute it and/or modify\n"
946 "it under the terms of the GNU General Public License as published by\n"
947 "the Free Software Foundation; either version 2 of the License, or\n"
948 "(at your option) any later version.\n\n"
949 "It is distributed in the hope that it will be useful,\n"
950 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
951 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
952 "GNU General Public License for more details.\n\n"
953 "You should have received a copy of the GNU General Public License\n"
954 "along with this program; if not, write to the Free Software\n"
955 "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,\n"
956 "USA.\n";
957 	break;
958       case 40: /* short and long usage */
959       case 41: p = ""; break;
960     }
961 
962     return p;
963 }
964 
965 void
set_strusage(const char * (* f)(int))966 set_strusage( const char *(*f)( int ) )
967 {
968     strusage_handler = f;
969 }
970 
971 
972 #ifdef TEST
973 static struct {
974     int verbose;
975     int debug;
976     char *outfile;
977     char *crf;
978     int myopt;
979     int echo;
980     int a_long_one;
981 }opt;
982 
983 int
main(int argc,char ** argv)984 main(int argc, char **argv)
985 {
986     ARGPARSE_OPTS opts[] = {
987     { 'v', "verbose",   0 , "Laut sein"},
988     { 'e', "echo"   ,   0 , ("Zeile ausgeben, damit wir sehen, was wir ein"
989                              " gegeben haben")},
990     { 'd', "debug",     0 , "Debug\nfalls mal etwas\nschief geht"},
991     { 'o', "output",    2   },
992     { 'c', "cross-ref", 2|8, "cross-reference erzeugen\n" },
993     /* Note that on a non-utf8 terminal the ß might garble the output. */
994     { 's', "street",  0,     "|Straße|set the name of the street to Straße" },
995     { 'm', "my-option", 1|8 },
996     { 500, "a-long-option", 0 },
997     {0} };
998     ARGPARSE_ARGS pargs = { &argc, &argv, 2|4|32 };
999     int i;
1000 
1001     while( arg_parse ( &pargs, opts) ) {
1002 	switch( pargs.r_opt ) {
1003 	  case -1 : printf( "arg=`%s'\n", pargs.r.ret_str); break;
1004 	  case 'v': opt.verbose++; break;
1005 	  case 'e': opt.echo++; break;
1006 	  case 'd': opt.debug++; break;
1007 	  case 'o': opt.outfile = pargs.r.ret_str; break;
1008 	  case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break;
1009 	  case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break;
1010 	  case 500: opt.a_long_one++;  break;
1011 	  default : pargs.err = 1; break; /* force warning output */
1012 	}
1013     }
1014     for(i=0; i < argc; i++ )
1015 	printf("%3d -> (%s)\n", i, argv[i] );
1016     puts("Options:");
1017     if( opt.verbose )
1018 	printf("  verbose=%d\n", opt.verbose );
1019     if( opt.debug )
1020 	printf("  debug=%d\n", opt.debug );
1021     if( opt.outfile )
1022 	printf("  outfile=`%s'\n", opt.outfile );
1023     if( opt.crf )
1024 	printf("  crffile=`%s'\n", opt.crf );
1025     if( opt.myopt )
1026 	printf("  myopt=%d\n", opt.myopt );
1027     if( opt.a_long_one )
1028 	printf("  a-long-one=%d\n", opt.a_long_one );
1029     if( opt.echo       )
1030 	printf("  echo=%d\n", opt.echo );
1031     return 0;
1032 }
1033 #endif
1034 
1035 /**** bottom of file ****/
1036