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