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