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