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