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