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