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