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