17adc6f2b0bc1e39bfc41b491553f4204c33402a
[rsync/rsync.git] / popt / popthelp.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2
3 /*@-type@*/
4 /** \ingroup popt
5  * \file popt/popthelp.c
6  */
7
8 /* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING
9    file accompanying popt source distributions, available from 
10    ftp://ftp.rpm.org/pub/rpm/dist. */
11
12 #include "system.h"
13 #include "poptint.h"
14
15 /**
16  * @param con           context
17  * @param key           option(s)
18  */
19 static void displayArgs(poptContext con,
20                 /*@unused@*/ enum poptCallbackReason foo,
21                 struct poptOption * key, 
22                 /*@unused@*/ const char * arg, /*@unused@*/ void * data)
23         /*@globals fileSystem@*/
24         /*@modifies fileSystem@*/
25 {
26     if (key->shortName == '?')
27         poptPrintHelp(con, stdout, 0);
28     else
29         poptPrintUsage(con, stdout, 0);
30     exit(0);
31 }
32
33 #ifdef  NOTYET
34 /*@unchecked@*/
35 static int show_option_defaults = 0;
36 #endif
37
38 /**
39  * Empty table marker to enable displaying popt alias/exec options.
40  */
41 /*@observer@*/ /*@unchecked@*/
42 struct poptOption poptAliasOptions[] = {
43     POPT_TABLEEND
44 };
45
46 /**
47  * Auto help table options.
48  */
49 /*@-castfcnptr@*/
50 /*@observer@*/ /*@unchecked@*/
51 struct poptOption poptHelpOptions[] = {
52   { NULL, '\0', POPT_ARG_CALLBACK, (void *)&displayArgs, '\0', NULL, NULL },
53   { "help", '?', 0, NULL, '?', N_("Show this help message"), NULL },
54   { "usage", '\0', 0, NULL, 'u', N_("Display brief usage message"), NULL },
55 #ifdef  NOTYET
56   { "defaults", '\0', POPT_ARG_NONE, &show_option_defaults, 0,
57         N_("Display option defaults in message"), NULL },
58 #endif
59     POPT_TABLEEND
60 } ;
61 /*@=castfcnptr@*/
62
63 /**
64  * @param table         option(s)
65  */
66 /*@observer@*/ /*@null@*/ static const char *const
67 getTableTranslationDomain(/*@null@*/ const struct poptOption *table)
68         /*@*/
69 {
70     const struct poptOption *opt;
71
72     if (table != NULL)
73     for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) {
74         if (opt->argInfo == POPT_ARG_INTL_DOMAIN)
75             return opt->arg;
76     }
77     return NULL;
78 }
79
80 /**
81  * @param opt           option(s)
82  * @param translation_domain    translation domain
83  */
84 /*@observer@*/ /*@null@*/ static const char *const
85 getArgDescrip(const struct poptOption * opt,
86                 /*@-paramuse@*/         /* FIX: wazzup? */
87                 /*@null@*/ const char * translation_domain)
88                 /*@=paramuse@*/
89         /*@*/
90 {
91     if (!(opt->argInfo & POPT_ARG_MASK)) return NULL;
92
93     if (opt == (poptHelpOptions + 1) || opt == (poptHelpOptions + 2))
94         if (opt->argDescrip) return POPT_(opt->argDescrip);
95
96     if (opt->argDescrip) return D_(translation_domain, opt->argDescrip);
97
98     switch (opt->argInfo & POPT_ARG_MASK) {
99     case POPT_ARG_NONE:         return POPT_("NONE");
100     case POPT_ARG_VAL:          return POPT_("VAL");
101     case POPT_ARG_INT:          return POPT_("INT");
102     case POPT_ARG_LONG:         return POPT_("LONG");
103     case POPT_ARG_STRING:       return POPT_("STRING");
104     case POPT_ARG_FLOAT:        return POPT_("FLOAT");
105     case POPT_ARG_DOUBLE:       return POPT_("DOUBLE");
106     default:                    return POPT_("ARG");
107     }
108 }
109
110 /**
111  * @param opt           option(s)
112  * @param translation_domain    translation domain
113  */
114 static /*@only@*/ /*@null@*/ char *
115 singleOptionDefaultValue(int lineLength,
116                 const struct poptOption * opt,
117                 /*@-paramuse@*/ /* FIX: i18n macros disable with lclint */
118                 /*@null@*/ const char * translation_domain)
119                 /*@=paramuse@*/
120         /*@*/
121 {
122     const char * defstr = D_(translation_domain, "default");
123     char * le = malloc(4*lineLength + 1);
124     char * l = le;
125
126     if (le == NULL) return NULL;        /* XXX can't happen */
127     *le = '\0';
128     *le++ = '(';
129     strcpy(le, defstr); le += strlen(le);
130     *le++ = ':';
131     *le++ = ' ';
132     if (opt->arg)       /* XXX programmer error */
133     switch (opt->argInfo & POPT_ARG_MASK) {
134     case POPT_ARG_VAL:
135     case POPT_ARG_INT:
136     {   long aLong = *((int *)opt->arg);
137         le += sprintf(le, "%ld", aLong);
138     }   break;
139     case POPT_ARG_LONG:
140     {   long aLong = *((long *)opt->arg);
141         le += sprintf(le, "%ld", aLong);
142     }   break;
143     case POPT_ARG_FLOAT:
144     {   double aDouble = *((float *)opt->arg);
145         le += sprintf(le, "%g", aDouble);
146     }   break;
147     case POPT_ARG_DOUBLE:
148     {   double aDouble = *((double *)opt->arg);
149         le += sprintf(le, "%g", aDouble);
150     }   break;
151     case POPT_ARG_STRING:
152     {   const char * s = *(const char **)opt->arg;
153         if (s == NULL) {
154             strcpy(le, "null"); le += strlen(le);
155         } else {
156             size_t slen = 4*lineLength - (le - l) - sizeof("\"...\")");
157             *le++ = '"';
158             strncpy(le, s, slen); le[slen] = '\0'; le += strlen(le);    
159             if (slen < strlen(s)) {
160                 strcpy(le, "...");      le += strlen(le);
161             }
162             *le++ = '"';
163         }
164     }   break;
165     case POPT_ARG_NONE:
166     default:
167         l = _free(l);
168         return NULL;
169         /*@notreached@*/ break;
170     }
171     *le++ = ')';
172     *le = '\0';
173
174     return l;
175 }
176
177 /**
178  * @param fp            output file handle
179  * @param opt           option(s)
180  * @param translation_domain    translation domain
181  */
182 static void singleOptionHelp(FILE * fp, int maxLeftCol, 
183                 const struct poptOption * opt,
184                 /*@null@*/ const char * translation_domain)
185         /*@globals fileSystem @*/
186         /*@modifies *fp, fileSystem @*/
187 {
188     int indentLength = maxLeftCol + 5;
189     int lineLength = 79 - indentLength;
190     const char * help = D_(translation_domain, opt->descrip);
191     const char * argDescrip = getArgDescrip(opt, translation_domain);
192     int helpLength;
193     char * defs = NULL;
194     char * left;
195     int nb = maxLeftCol + 1;
196
197     /* Make sure there's more than enough room in target buffer. */
198     if (opt->longName)  nb += strlen(opt->longName);
199     if (argDescrip)     nb += strlen(argDescrip);
200
201     left = malloc(nb);
202     if (left == NULL) return;   /* XXX can't happen */
203     left[0] = '\0';
204     left[maxLeftCol] = '\0';
205
206     if (opt->longName && opt->shortName)
207         sprintf(left, "-%c, %s%s", opt->shortName,
208                 ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"),
209                 opt->longName);
210     else if (opt->shortName != '\0') 
211         sprintf(left, "-%c", opt->shortName);
212     else if (opt->longName)
213         sprintf(left, "%s%s",
214                 ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"),
215                 opt->longName);
216     if (!*left) goto out;
217     if (argDescrip) {
218         char * le = left + strlen(left);
219
220         if (opt->argInfo & POPT_ARGFLAG_OPTIONAL)
221             *le++ = '[';
222
223         /* Choose type of output */
224         /*@-branchstate@*/
225         if (opt->argInfo & POPT_ARGFLAG_SHOW_DEFAULT) {
226             defs = singleOptionDefaultValue(lineLength, opt, translation_domain);
227             if (defs) {
228                 char * t = malloc((help ? strlen(help) : 0) +
229                                 strlen(defs) + sizeof(" "));
230                 if (t) {
231                     char * te = t;
232                     *te = '\0';
233                     if (help) {
234                         strcpy(te, help);       te += strlen(te);
235                     }
236                     *te++ = ' ';
237                     strcpy(te, defs);
238                     defs = _free(defs);
239                 }
240                 defs = t;
241             }
242         }
243         /*@=branchstate@*/
244
245         if (opt->argDescrip == NULL) {
246             switch (opt->argInfo & POPT_ARG_MASK) {
247             case POPT_ARG_NONE:
248                 break;
249             case POPT_ARG_VAL:
250             {   long aLong = opt->val;
251                 int ops = (opt->argInfo & POPT_ARGFLAG_LOGICALOPS);
252                 int negate = (opt->argInfo & POPT_ARGFLAG_NOT);
253
254                 /* Don't bother displaying typical values */
255                 if (!ops && (aLong == 0L || aLong == 1L || aLong == -1L))
256                     break;
257                 *le++ = '[';
258                 switch (ops) {
259                 case POPT_ARGFLAG_OR:
260                     *le++ = '|';
261                     /*@innerbreak@*/ break;
262                 case POPT_ARGFLAG_AND:
263                     *le++ = '&';
264                     /*@innerbreak@*/ break;
265                 case POPT_ARGFLAG_XOR:
266                     *le++ = '^';
267                     /*@innerbreak@*/ break;
268                 default:
269                     /*@innerbreak@*/ break;
270                 }
271                 *le++ = '=';
272                 if (negate) *le++ = '~';
273                 /*@-formatconst@*/
274                 le += sprintf(le, (ops ? "0x%lx" : "%ld"), aLong);
275                 /*@=formatconst@*/
276                 *le++ = ']';
277             }   break;
278             case POPT_ARG_INT:
279             case POPT_ARG_LONG:
280             case POPT_ARG_FLOAT:
281             case POPT_ARG_DOUBLE:
282             case POPT_ARG_STRING:
283                 *le++ = '=';
284                 strcpy(le, argDescrip);         le += strlen(le);
285                 break;
286             default:
287                 break;
288             }
289         } else {
290             *le++ = '=';
291             strcpy(le, argDescrip);             le += strlen(le);
292         }
293         if (opt->argInfo & POPT_ARGFLAG_OPTIONAL)
294             *le++ = ']';
295         *le = '\0';
296     }
297
298     if (help)
299         fprintf(fp,"  %-*s   ", maxLeftCol, left);
300     else {
301         fprintf(fp,"  %s\n", left); 
302         goto out;
303     }
304
305     left = _free(left);
306     if (defs) {
307         help = defs; defs = NULL;
308     }
309
310     helpLength = strlen(help);
311     while (helpLength > lineLength) {
312         const char * ch;
313         char format[10];
314
315         ch = help + lineLength - 1;
316         while (ch > help && !isspace(*ch)) ch--;
317         if (ch == help) break;          /* give up */
318         while (ch > (help + 1) && isspace(*ch)) ch--;
319         ch++;
320
321         sprintf(format, "%%.%ds\n%%%ds", (int) (ch - help), indentLength);
322         /*@-formatconst@*/
323         fprintf(fp, format, help, " ");
324         /*@=formatconst@*/
325         help = ch;
326         while (isspace(*help) && *help) help++;
327         helpLength = strlen(help);
328     }
329
330     if (helpLength) fprintf(fp, "%s\n", help);
331
332 out:
333     /*@-dependenttrans@*/
334     defs = _free(defs);
335     /*@=dependenttrans@*/
336     left = _free(left);
337 }
338
339 /**
340  * @param opt           option(s)
341  * @param translation_domain    translation domain
342  */
343 static int maxArgWidth(const struct poptOption * opt,
344                        /*@null@*/ const char * translation_domain)
345         /*@*/
346 {
347     int max = 0;
348     int len = 0;
349     const char * s;
350     
351     if (opt != NULL)
352     while (opt->longName || opt->shortName || opt->arg) {
353         if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
354             if (opt->arg)       /* XXX program error */
355             len = maxArgWidth(opt->arg, translation_domain);
356             if (len > max) max = len;
357         } else if (!(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
358             len = sizeof("  ")-1;
359             if (opt->shortName != '\0') len += sizeof("-X")-1;
360             if (opt->shortName != '\0' && opt->longName) len += sizeof(", ")-1;
361             if (opt->longName) {
362                 len += ((opt->argInfo & POPT_ARGFLAG_ONEDASH)
363                         ? sizeof("-")-1 : sizeof("--")-1);
364                 len += strlen(opt->longName);
365             }
366
367             s = getArgDescrip(opt, translation_domain);
368             if (s)
369                 len += sizeof("=")-1 + strlen(s);
370             if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) len += sizeof("[]")-1;
371             if (len > max) max = len;
372         }
373
374         opt++;
375     }
376     
377     return max;
378 }
379
380 /**
381  * Display popt alias and exec help.
382  * @param fp            output file handle
383  * @param items         alias/exec array
384  * @param nitems        no. of alias/exec entries
385  * @param translation_domain    translation domain
386  */
387 static void itemHelp(FILE * fp,
388                 /*@null@*/ poptItem items, int nitems, int left,
389                 /*@null@*/ const char * translation_domain)
390         /*@globals fileSystem @*/
391         /*@modifies *fp, fileSystem @*/
392 {
393     poptItem item;
394     int i;
395
396     if (items != NULL)
397     for (i = 0, item = items; i < nitems; i++, item++) {
398         const struct poptOption * opt;
399         opt = &item->option;
400         if ((opt->longName || opt->shortName) && 
401             !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN))
402             singleOptionHelp(fp, left, opt, translation_domain);
403     }
404 }
405
406 /**
407  * @param fp            output file handle
408  * @param table         option(s)
409  * @param translation_domain    translation domain
410  */
411 static void singleTableHelp(poptContext con, FILE * fp,
412                 /*@null@*/ const struct poptOption * table, int left,
413                 /*@null@*/ const char * translation_domain)
414         /*@globals fileSystem @*/
415         /*@modifies *fp, fileSystem @*/
416 {
417     const struct poptOption * opt;
418     const char *sub_transdom;
419
420     if (table == poptAliasOptions) {
421         itemHelp(fp, con->aliases, con->numAliases, left, NULL);
422         itemHelp(fp, con->execs, con->numExecs, left, NULL);
423         return;
424     }
425
426     if (table != NULL)
427     for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) {
428         if ((opt->longName || opt->shortName) && 
429             !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN))
430             singleOptionHelp(fp, left, opt, translation_domain);
431     }
432
433     if (table != NULL)
434     for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) {
435         if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_INCLUDE_TABLE)
436             continue;
437         sub_transdom = getTableTranslationDomain(opt->arg);
438         if (sub_transdom == NULL)
439             sub_transdom = translation_domain;
440             
441         if (opt->descrip)
442             fprintf(fp, "\n%s\n", D_(sub_transdom, opt->descrip));
443
444         singleTableHelp(con, fp, opt->arg, left, sub_transdom);
445     }
446 }
447
448 /**
449  * @param con           context
450  * @param fp            output file handle
451  */
452 static int showHelpIntro(poptContext con, FILE * fp)
453         /*@globals fileSystem @*/
454         /*@modifies *fp, fileSystem @*/
455 {
456     int len = 6;
457     const char * fn;
458
459     fprintf(fp, POPT_("Usage:"));
460     if (!(con->flags & POPT_CONTEXT_KEEP_FIRST)) {
461         /*@-nullderef@*/        /* LCL: wazzup? */
462         fn = con->optionStack->argv[0];
463         /*@=nullderef@*/
464         if (fn == NULL) return len;
465         if (strchr(fn, '/')) fn = strrchr(fn, '/') + 1;
466         fprintf(fp, " %s", fn);
467         len += strlen(fn) + 1;
468     }
469
470     return len;
471 }
472
473 void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ int flags)
474 {
475     int leftColWidth;
476
477     (void) showHelpIntro(con, fp);
478     if (con->otherHelp)
479         fprintf(fp, " %s\n", con->otherHelp);
480     else
481         fprintf(fp, " %s\n", POPT_("[OPTION...]"));
482
483     leftColWidth = maxArgWidth(con->options, NULL);
484     singleTableHelp(con, fp, con->options, leftColWidth, NULL);
485 }
486
487 /**
488  * @param fp            output file handle
489  * @param opt           option(s)
490  * @param translation_domain    translation domain
491  */
492 static int singleOptionUsage(FILE * fp, int cursor, 
493                 const struct poptOption * opt,
494                 /*@null@*/ const char *translation_domain)
495         /*@globals fileSystem @*/
496         /*@modifies *fp, fileSystem @*/
497 {
498     int len = 3;
499     char shortStr[2] = { '\0', '\0' };
500     const char * item = shortStr;
501     const char * argDescrip = getArgDescrip(opt, translation_domain);
502
503     if (opt->shortName!= '\0' ) {
504         if (!(opt->argInfo & POPT_ARG_MASK)) 
505             return cursor;      /* we did these already */
506         len++;
507         shortStr[0] = opt->shortName;
508         shortStr[1] = '\0';
509     } else if (opt->longName) {
510         len += 1 + strlen(opt->longName);
511         item = opt->longName;
512     }
513
514     if (len == 3) return cursor;
515
516     if (argDescrip) 
517         len += strlen(argDescrip) + 1;
518
519     if ((cursor + len) > 79) {
520         fprintf(fp, "\n       ");
521         cursor = 7;
522     } 
523
524     fprintf(fp, " [-%s%s%s%s]",
525         ((opt->shortName || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) ? "" : "-"),
526         item,
527         (argDescrip ? (opt->shortName != '\0' ? " " : "=") : ""),
528         (argDescrip ? argDescrip : ""));
529
530     return cursor + len + 1;
531 }
532
533 /**
534  * Display popt alias and exec usage.
535  * @param fp            output file handle
536  * @param item          alias/exec array
537  * @param nitems        no. of ara/exec entries
538  * @param translation_domain    translation domain
539  */
540 static int itemUsage(FILE * fp, int cursor, poptItem item, int nitems,
541                 /*@null@*/ const char * translation_domain)
542         /*@globals fileSystem @*/
543         /*@modifies *fp, fileSystem @*/
544 {
545     int i;
546
547     /*@-branchstate@*/          /* FIX: W2DO? */
548     if (item != NULL)
549     for (i = 0; i < nitems; i++, item++) {
550         const struct poptOption * opt;
551         opt = &item->option;
552         if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) {
553             translation_domain = (const char *)opt->arg;
554         } else if ((opt->longName || opt->shortName) &&
555                  !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
556             cursor = singleOptionUsage(fp, cursor, opt, translation_domain);
557         }
558     }
559     /*@=branchstate@*/
560
561     return cursor;
562 }
563
564 /**
565  * @param fp            output file handle
566  * @param opt           option(s)
567  * @param translation_domain    translation domain
568  */
569 static int singleTableUsage(poptContext con, FILE * fp,
570                 int cursor, const struct poptOption * opt,
571                 /*@null@*/ const char * translation_domain)
572         /*@globals fileSystem @*/
573         /*@modifies *fp, fileSystem @*/
574 {
575     /*@-branchstate@*/          /* FIX: W2DO? */
576     if (opt != NULL)
577     for (; (opt->longName || opt->shortName || opt->arg) ; opt++) {
578         if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) {
579             translation_domain = (const char *)opt->arg;
580         } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
581             if (opt->arg)       /* XXX program error */
582             cursor = singleTableUsage(con, fp, cursor, opt->arg,
583                         translation_domain);
584         } else if ((opt->longName || opt->shortName) &&
585                  !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
586             cursor = singleOptionUsage(fp, cursor, opt, translation_domain);
587         }
588     }
589     /*@=branchstate@*/
590
591     return cursor;
592 }
593
594 /**
595  * Return concatenated short options for display.
596  * @param opt           option(s)
597  * @param fp            output file handle
598  * @retval str          concatenation of short options
599  * @return              length of display string
600  */
601 static int showShortOptions(const struct poptOption * opt, FILE * fp,
602                 /*@null@*/ char * str)
603         /*@globals fileSystem @*/
604         /*@modifies *str, *fp, fileSystem @*/
605 {
606     char * s = alloca(300);     /* larger then the ascii set */
607
608     s[0] = '\0';
609     /*@-branchstate@*/          /* FIX: W2DO? */
610     if (str == NULL) {
611         memset(s, 0, sizeof(s));
612         str = s;
613     }
614     /*@=branchstate@*/
615
616     if (opt != NULL)
617     for (; (opt->longName || opt->shortName || opt->arg); opt++) {
618         if (opt->shortName && !(opt->argInfo & POPT_ARG_MASK))
619             str[strlen(str)] = opt->shortName;
620         else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE)
621             if (opt->arg)       /* XXX program error */
622                 (void) showShortOptions(opt->arg, fp, str);
623     } 
624
625     if (s != str || *s != '\0')
626         return 0;
627
628     fprintf(fp, " [-%s]", s);
629     return strlen(s) + 4;
630 }
631
632 void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ int flags)
633 {
634     int cursor;
635
636     cursor = showHelpIntro(con, fp);
637     cursor += showShortOptions(con->options, fp, NULL);
638     (void) singleTableUsage(con, fp, cursor, con->options, NULL);
639     (void) itemUsage(fp, cursor, con->aliases, con->numAliases, NULL);
640     (void) itemUsage(fp, cursor, con->execs, con->numExecs, NULL);
641
642     if (con->otherHelp) {
643         cursor += strlen(con->otherHelp) + 1;
644         if (cursor > 79) fprintf(fp, "\n       ");
645         fprintf(fp, " %s", con->otherHelp);
646     }
647
648     fprintf(fp, "\n");
649 }
650
651 void poptSetOtherOptionHelp(poptContext con, const char * text)
652 {
653     con->otherHelp = _free(con->otherHelp);
654     con->otherHelp = xstrdup(text);
655 }
656 /*@=type@*/