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