xref: /netbsd-src/external/bsd/ntp/dist/sntp/libopts/usage.c (revision 4439cfd0acf9c7dc90625e5cd83b2317a9ab8967)
1 /*	$NetBSD: usage.c,v 1.9 2024/08/18 20:47:25 christos Exp $	*/
2 
3 
4 /*
5  * \file usage.c
6  *
7  *  This module implements the default usage procedure for
8  *  Automated Options.  It may be overridden, of course.
9  *
10  * @addtogroup autoopts
11  * @{
12  */
13 /*
14  *  Sort options:
15     --start=END-[S]TATIC-FORWARD --patt='^/\*($|[^:])' \
16     --out=xx.c key='^[a-zA-Z0-9_]+\(' --trail='^/\*:' \
17     --spac=2 --input=usage.c
18  */
19 
20 /*
21  *  This file is part of AutoOpts, a companion to AutoGen.
22  *  AutoOpts is free software.
23  *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
24  *
25  *  AutoOpts is available under any one of two licenses.  The license
26  *  in use must be one of these two and the choice is under the control
27  *  of the user of the license.
28  *
29  *   The GNU Lesser General Public License, version 3 or later
30  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
31  *
32  *   The Modified Berkeley Software Distribution License
33  *      See the file "COPYING.mbsd"
34  *
35  *  These files have the following sha256 sums:
36  *
37  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
38  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
39  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
40  */
41 
42 #define GRAPH_CH(_ch) \
43     ((((unsigned)_ch) <= 0x7E) && (((unsigned)_ch) > ' '))
44 
45 /**
46  * Parse the option usage flags string.  Any parsing problems yield
47  * a zero (no flags set) result.  This function is internal to
48  * set_usage_flags().
49  *
50  * @param[in] fnt   Flag Name Table - maps a name to a mask
51  * @param[in] txt   the text to process.  If NULL, then
52  *                  getenv("AUTOOPTS_USAGE") is used.
53  * @returns a bit mask indicating which \a fnt entries were found.
54  */
55 static unsigned int
56 parse_usage_flags(ao_flag_names_t const * fnt, char const * txt)
57 {
58     unsigned int res = 0;
59 
60     /*
61      * The text may be passed in.  If not, use the environment variable.
62      */
63     if (txt == NULL) {
64         txt = getenv("AUTOOPTS_USAGE");
65         if (txt == NULL)
66             return 0;
67     }
68 
69     txt = SPN_WHITESPACE_CHARS(txt);
70     if (*txt == NUL)
71         return 0;
72 
73     /*
74      * search the string for table entries.  We must understand everything
75      * we see in the string, or we give up on it.
76      */
77     for (;;) {
78         int ix = 0;
79 
80         for (;;) {
81             if (strneqvcmp(txt, fnt[ix].fnm_name, (int)fnt[ix].fnm_len) == 0)
82                 break;
83             if (++ix >= AOUF_COUNT)
84                 return 0;
85         }
86 
87         /*
88          *  Make sure we have a full match.  Look for whitespace,
89          *  a comma, or a NUL byte.
90          */
91         if (! IS_END_LIST_ENTRY_CHAR(txt[fnt[ix].fnm_len]))
92             return 0;
93 
94         res |= 1U << ix;
95         txt = SPN_WHITESPACE_CHARS(txt + fnt[ix].fnm_len);
96 
97         switch (*txt) {
98         case NUL:
99             return res;
100 
101         case ',':
102             txt = SPN_WHITESPACE_CHARS(txt + 1);
103             /* Something must follow the comma */
104             /* FALLTHROUGH */
105 
106         default:
107             continue;
108         }
109     }
110 }
111 
112 /**
113  * Set option usage flags.  Any parsing problems yield no changes to options.
114  * Three different bits may be fiddled: \a OPTPROC_GNUUSAGE, \a OPTPROC_MISUSE
115  * and \a OPTPROC_COMPUTE.
116  *
117  * @param[in] flg_txt   text to parse.  If NULL, then the AUTOOPTS_USAGE
118  *                      environment variable is parsed.
119  * @param[in,out] opts  the program option descriptor
120  */
121 static void
122 set_usage_flags(tOptions * opts, char const * flg_txt)
123 {
124 #   define _aof_(_n, _f)   { sizeof(#_n)-1, _f, #_n },
125     static ao_flag_names_t const fn_table[AOUF_COUNT] = {
126         AOFLAG_TABLE
127     };
128 #   undef  _aof_
129 
130     /*
131      * the flag word holds a bit for each selected table entry.
132      */
133     unsigned int flg = parse_usage_flags(fn_table, flg_txt);
134     if (flg == 0) return;
135 
136     /*
137      * Ensure we do not have conflicting selections
138      */
139     {
140         static unsigned int const form_mask =
141             AOUF_gnu | AOUF_autoopts;
142         static unsigned int const misuse_mask =
143             AOUF_no_misuse_usage | AOUF_misuse_usage;
144         if (  ((flg & form_mask)   == form_mask)
145            || ((flg & misuse_mask) == misuse_mask) )
146             return;
147     }
148 
149     /*
150      * Now fiddle the fOptSet bits, based on settings.
151      * The OPTPROC_LONGOPT bit is immutable, thus if it is set,
152      * then fnm points to a mask off mask.
153      */
154     {
155         ao_flag_names_t const * fnm = fn_table;
156         for (;;) {
157             if ((flg & 1) != 0) {
158                 if ((fnm->fnm_mask & OPTPROC_LONGOPT) != 0)
159                      opts->fOptSet &= fnm->fnm_mask;
160                 else opts->fOptSet |= fnm->fnm_mask;
161             }
162             flg >>= 1;
163             if (flg == 0)
164                 break;
165             fnm++;
166         }
167     }
168 }
169 
170 /*
171  *  Figure out if we should try to format usage text sort-of like
172  *  the way many GNU programs do.
173  */
174 static inline bool
175 do_gnu_usage(tOptions * pOpts)
176 {
177     return (pOpts->fOptSet & OPTPROC_GNUUSAGE) ? true : false;
178 }
179 
180 /*
181  *  Figure out if we should try to format usage text sort-of like
182  *  the way many GNU programs do.
183  */
184 static inline bool
185 skip_misuse_usage(tOptions * pOpts)
186 {
187     return (pOpts->fOptSet & OPTPROC_MISUSE) ? true : false;
188 }
189 
190 
191 /*=export_func  optionOnlyUsage
192  *
193  * what:  Print usage text for just the options
194  * arg:   + tOptions *  + pOpts    + program options descriptor +
195  * arg:   + int         + ex_code  + exit code for calling exit(3) +
196  *
197  * doc:
198  *  This routine will print only the usage for each option.
199  *  This function may be used when the emitted usage must incorporate
200  *  information not available to AutoOpts.
201 =*/
202 void
203 optionOnlyUsage(tOptions * pOpts, int ex_code)
204 {
205     char const * pOptTitle = NULL;
206 
207     set_usage_flags(pOpts, NULL);
208     if ((ex_code != EXIT_SUCCESS) &&
209         skip_misuse_usage(pOpts))
210         return;
211 
212     /*
213      *  Determine which header and which option formatting strings to use
214      */
215     if (do_gnu_usage(pOpts))
216         (void)setGnuOptFmts(pOpts, &pOptTitle);
217     else
218         (void)setStdOptFmts(pOpts, &pOptTitle);
219 
220     prt_opt_usage(pOpts, ex_code, pOptTitle);
221 
222     fflush(option_usage_fp);
223     if (ferror(option_usage_fp) != 0)
224         fserr_exit(pOpts->pzProgName, zwriting, (option_usage_fp == stderr)
225                    ? zstderr_name : zstdout_name);
226 }
227 
228 /**
229  * Print a message suggesting how to get help.
230  *
231  * @param[in] opts      the program options
232  */
233 static void
234 print_offer_usage(tOptions * opts)
235 {
236     char help[24];
237 
238     if (HAS_opt_usage_t(opts)) {
239         int ix = opts->presetOptCt;
240         tOptDesc * od = opts->pOptDesc + ix;
241         while (od->optUsage != AOUSE_HELP) {
242             if (++ix >= opts->optCt)
243                 ao_bug(zmissing_help_msg);
244             od++;
245         }
246         switch (opts->fOptSet & (OPTPROC_LONGOPT | OPTPROC_SHORTOPT)) {
247         case OPTPROC_SHORTOPT:
248             help[0] = '-';
249             help[1] = od->optValue;
250             help[2] = NUL;
251             break;
252 
253         case OPTPROC_LONGOPT:
254         case (OPTPROC_LONGOPT | OPTPROC_SHORTOPT):
255             help[0] = help[1] = '-';
256             strncpy(help + 2, od->pz_Name, 20);
257             break;
258 
259         case 0:
260             strncpy(help, od->pz_Name, 20);
261             break;
262         }
263 
264     } else {
265         switch (opts->fOptSet & (OPTPROC_LONGOPT | OPTPROC_SHORTOPT)) {
266         case OPTPROC_SHORTOPT:
267             strcpy(help, "-h");
268             break;
269 
270         case OPTPROC_LONGOPT:
271         case (OPTPROC_LONGOPT | OPTPROC_SHORTOPT):
272             strcpy(help, "--help");
273             break;
274 
275         case 0:
276             strcpy(help, "help");
277             break;
278         }
279     }
280 
281     fprintf(option_usage_fp, zoffer_usage_fmt, opts->pzProgName, help);
282 }
283 
284 /**
285  * Print information about each option.
286  *
287  * @param[in] opts      the program options
288  * @param[in] exit_code whether or not there was a usage error reported.
289  *                      used to select full usage versus abbreviated.
290  */
291 static void
292 print_usage_details(tOptions * opts, int exit_code)
293 {
294     {
295         char const * pOptTitle = NULL;
296         int flen;
297 
298         /*
299          *  Determine which header and which option formatting strings to use
300          */
301         if (do_gnu_usage(opts)) {
302             flen = setGnuOptFmts(opts, &pOptTitle);
303             sprintf(line_fmt_buf, zFmtFmt, flen);
304             fputc(NL, option_usage_fp);
305 
306         } else {
307             flen = setStdOptFmts(opts, &pOptTitle);
308             sprintf(line_fmt_buf, zFmtFmt, flen);
309 
310             /*
311              *  When we exit with EXIT_SUCCESS and the first option is a doc
312              *  option, we do *NOT* want to emit the column headers.
313              *  Otherwise, we do.
314              */
315             if (  (exit_code != EXIT_SUCCESS)
316                || ((opts->pOptDesc->fOptState & OPTST_DOCUMENT) == 0) )
317 
318                 fputs(pOptTitle, option_usage_fp);
319         }
320 
321         flen = 4 - ((flen + 15) / 8);
322         if (flen > 0)
323             tab_skip_ct = flen;
324         prt_opt_usage(opts, exit_code, pOptTitle);
325     }
326 
327     /*
328      *  Describe the mechanics of denoting the options
329      */
330     switch (opts->fOptSet & OPTPROC_L_N_S) {
331     case OPTPROC_L_N_S:     fputs(zFlagOkay, option_usage_fp); break;
332     case OPTPROC_SHORTOPT:  break;
333     case OPTPROC_LONGOPT:   fputs(zNoFlags,  option_usage_fp); break;
334     case 0:                 fputs(zOptsOnly, option_usage_fp); break;
335     }
336 
337     if ((opts->fOptSet & OPTPROC_NUM_OPT) != 0)
338         fputs(zNumberOpt, option_usage_fp);
339 
340     if ((opts->fOptSet & OPTPROC_REORDER) != 0)
341         fputs(zReorder, option_usage_fp);
342 
343     if (opts->pzExplain != NULL)
344         fputs(opts->pzExplain, option_usage_fp);
345 
346     /*
347      *  IF the user is asking for help (thus exiting with SUCCESS),
348      *  THEN see what additional information we can provide.
349      */
350     if (exit_code == EXIT_SUCCESS)
351         prt_prog_detail(opts);
352 
353     /*
354      * Give bug notification preference to the packager information
355      */
356     if (HAS_pzPkgDataDir(opts) && (opts->pzPackager != NULL))
357         fputs(opts->pzPackager, option_usage_fp);
358 
359     else if (opts->pzBugAddr != NULL)
360         fprintf(option_usage_fp, zPlsSendBugs, opts->pzBugAddr);
361 
362     fflush(option_usage_fp);
363 
364     if (ferror(option_usage_fp) != 0)
365         fserr_exit(opts->pzProgName, zwriting, (option_usage_fp == stderr)
366                    ? zstderr_name : zstdout_name);
367 }
368 
369 static void
370 print_one_paragraph(char const * text, bool plain, FILE * fp)
371 {
372     if (plain) {
373 #ifdef ENABLE_NLS
374 #ifdef HAVE_LIBINTL_H
375 #ifdef DEBUG_ENABLED
376 #undef gettext
377 #endif
378         char * buf = dgettext("libopts", text);
379         if (buf == text)
380             text = gettext(text);
381 #endif /* HAVE_LIBINTL_H */
382 #endif /* ENABLE_NLS */
383         fputs(text, fp);
384     }
385 
386     else {
387         char const * t = optionQuoteString(text, LINE_SPLICE);
388         fprintf(fp, PUTS_FMT, t);
389         AGFREE(t);
390     }
391 }
392 
393 /*=export_func  optionPrintParagraphs
394  * private:
395  *
396  * what:  Print a paragraph of usage text
397  * arg:   + char const * + text  + a block of text that has bee i18n-ed +
398  * arg:   + bool         + plain + false -> wrap text in fputs()        +
399  * arg:   + FILE *       + fp    + the stream file pointer for output   +
400  *
401  * doc:
402  *  This procedure is called in two contexts: when a full or short usage text
403  *  has been provided for display, and when autogen is assembling a list of
404  *  translatable texts in the optmain.tlib template.  In the former case, \a
405  *  plain is set to \a true, otherwise \a false.
406  *
407  *  Anything less than 256 characters in size is printed as a single unit.
408  *  Otherwise, paragraphs are detected.  A paragraph break is defined as just
409  *  before a non-empty line preceded by two newlines or a line that starts
410  *  with at least one space character but fewer than 8 space characters.
411  *  Lines indented with tabs or more than 7 spaces are considered continuation
412  *  lines.
413  *
414  *  If 'plain' is true, we are emitting text for a user to see.  So, if it is
415  *  true and NLS is not enabled, then just write the whole thing at once.
416 =*/
417 void
418 optionPrintParagraphs(char const * text, bool plain, FILE * fp)
419 {
420     size_t len = strlen(text);
421     char * buf;
422 #ifndef ENABLE_NLS
423     if (plain || (len < 256))
424 #else
425     if (len < 256)
426 #endif
427     {
428         print_one_paragraph(text, plain, fp);
429         return;
430     }
431 
432     AGDUPSTR(buf, text, "ppara");
433     text = buf;
434 
435     for (;;) {
436         char * scan;
437 
438         if (len < 256) {
439         done:
440             print_one_paragraph(buf, plain, fp);
441             break;
442         }
443         scan = buf;
444 
445     try_longer:
446         scan = strchr(scan, NL);
447         if (scan == NULL)
448             goto done;
449 
450         if ((scan - buf) < 40) {
451             scan++;
452             goto try_longer;
453         }
454 
455         scan++;
456         if ((! isspace((int)*scan)) || (*scan == HT))
457             /*
458              * line starts with tab or non-whitespace --> continuation
459              */
460             goto try_longer;
461 
462         if (*scan == NL) {
463             /*
464              * Double newline -> paragraph break
465              * Include all newlines in current paragraph.
466              */
467             while (*++scan == NL)  /*continue*/;
468 
469         } else {
470             char * p = scan;
471             int   sp_ct = 0;
472 
473             while (*p == ' ') {
474                 if (++sp_ct >= 8) {
475                     /*
476                      * Too many spaces --> continuation line
477                      */
478                     scan = p;
479                     goto try_longer;
480                 }
481                 p++;
482             }
483         }
484 
485         /*
486          * "scan" points to the first character of a paragraph or the
487          * terminating NUL byte.
488          */
489         {
490             char svch = *scan;
491             *scan = NUL;
492             print_one_paragraph(buf, plain, fp);
493             len -= scan - buf;
494             if (len <= 0)
495                 break;
496             *scan = svch;
497             buf = scan;
498         }
499     }
500     AGFREE(text);
501 }
502 
503 /*=export_func  optionUsage
504  * private:
505  *
506  * what:  Print usage text
507  * arg:   + tOptions * + opts + program options descriptor +
508  * arg:   + int        + exitCode + exit code for calling exit(3) +
509  *
510  * doc:
511  *  This routine will print usage in both GNU-standard and AutoOpts-expanded
512  *  formats.  The descriptor specifies the default, but AUTOOPTS_USAGE will
513  *  over-ride this, providing the value of it is set to either "gnu" or
514  *  "autoopts".  This routine will @strong{not} return.
515  *
516  *  If "exitCode" is "AO_EXIT_REQ_USAGE" (normally 64), then output will to
517  *  to stdout and the actual exit code will be "EXIT_SUCCESS".
518 =*/
519 lo_noreturn void
520 optionUsage(tOptions * opts, int usage_exit_code)
521 {
522     int exit_code = (usage_exit_code == AO_EXIT_REQ_USAGE)
523         ? EXIT_SUCCESS : usage_exit_code;
524 
525     displayEnum = false;
526     set_usage_flags(opts, NULL);
527 
528     /*
529      *  Paged usage will preset option_usage_fp to an output file.
530      *  If it hasn't already been set, then set it to standard output
531      *  on successful exit (help was requested), otherwise error out.
532      *
533      *  Test the version before obtaining pzFullUsage or pzShortUsage.
534      *  These fields do not exist before revision 30.
535      */
536     {
537         char const * pz;
538 
539         if (exit_code == EXIT_SUCCESS) {
540             pz = (opts->structVersion >= 30 * 4096)
541                 ? opts->pzFullUsage : NULL;
542 
543             if (option_usage_fp == NULL)
544                 option_usage_fp = print_exit ? stderr : stdout;
545 
546         } else {
547             pz = (opts->structVersion >= 30 * 4096)
548                 ? opts->pzShortUsage : NULL;
549 
550             if (option_usage_fp == NULL)
551                 option_usage_fp = stderr;
552         }
553 
554         if (((opts->fOptSet & OPTPROC_COMPUTE) == 0) && (pz != NULL)) {
555             if ((opts->fOptSet & OPTPROC_TRANSLATE) != 0)
556                 optionPrintParagraphs(pz, true, option_usage_fp);
557             else
558                 fputs(pz, option_usage_fp);
559             goto flush_and_exit;
560         }
561     }
562 
563     fprintf(option_usage_fp, opts->pzUsageTitle, opts->pzProgName);
564 
565     if ((exit_code == EXIT_SUCCESS) ||
566         (! skip_misuse_usage(opts)))
567 
568         print_usage_details(opts, usage_exit_code);
569     else
570         print_offer_usage(opts);
571 
572  flush_and_exit:
573     fflush(option_usage_fp);
574     if (ferror(option_usage_fp) != 0)
575         fserr_exit(opts->pzProgName, zwriting, (option_usage_fp == stdout)
576                    ? zstdout_name : zstderr_name);
577 
578     option_exits(exit_code);
579 }
580 
581 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
582  *   PER OPTION TYPE USAGE INFORMATION
583  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
584 /**
585  * print option conflicts.
586  *
587  * @param opts the program option descriptor
588  * @param od   the option descriptor
589  */
590 static void
591 prt_conflicts(tOptions * opts, tOptDesc * od)
592 {
593     const int * opt_no;
594     fputs(zTabHyp + tab_skip_ct, option_usage_fp);
595 
596     /*
597      *  REQUIRED:
598      */
599     if (od->pOptMust != NULL) {
600         opt_no = od->pOptMust;
601 
602         if (opt_no[1] == NO_EQUIVALENT) {
603             fprintf(option_usage_fp, zReqOne,
604                     opts->pOptDesc[*opt_no].pz_Name);
605         } else {
606             fputs(zReqThese, option_usage_fp);
607             for (;;) {
608                 fprintf(option_usage_fp, zTabout + tab_skip_ct,
609                         opts->pOptDesc[*opt_no].pz_Name);
610                 if (*++opt_no == NO_EQUIVALENT)
611                     break;
612             }
613         }
614 
615         if (od->pOptCant != NULL)
616             fputs(zTabHypAnd + tab_skip_ct, option_usage_fp);
617     }
618 
619     /*
620      *  CONFLICTS:
621      */
622     if (od->pOptCant == NULL)
623         return;
624 
625     opt_no = od->pOptCant;
626 
627     if (opt_no[1] == NO_EQUIVALENT) {
628         fprintf(option_usage_fp, zProhibOne,
629                 opts->pOptDesc[*opt_no].pz_Name);
630         return;
631     }
632 
633     fputs(zProhib, option_usage_fp);
634     for (;;) {
635         fprintf(option_usage_fp, zTabout + tab_skip_ct,
636                 opts->pOptDesc[*opt_no].pz_Name);
637         if (*++opt_no == NO_EQUIVALENT)
638             break;
639     }
640 }
641 
642 /**
643  *  Print the usage information for a single vendor option.
644  *
645  * @param[in] opts    the program option descriptor
646  * @param[in] od      the option descriptor
647  * @param[in] argtp   names of the option argument types
648  * @param[in] usefmt  format for primary usage line
649  */
650 static void
651 prt_one_vendor(tOptions *    opts,  tOptDesc *   od,
652                arg_types_t * argtp, char const * usefmt)
653 {
654     prt_preamble(opts, od, argtp);
655 
656     {
657         char z[ 80 ];
658         char const *  pzArgType;
659 
660         /*
661          *  Determine the argument type string first on its usage, then,
662          *  when the option argument is required, base the type string on the
663          *  argument type.
664          */
665         if (od->fOptState & OPTST_ARG_OPTIONAL) {
666             pzArgType = argtp->pzOpt;
667 
668         } else switch (OPTST_GET_ARGTYPE(od->fOptState)) {
669         case OPARG_TYPE_NONE:        pzArgType = argtp->pzNo;   break;
670         case OPARG_TYPE_ENUMERATION: pzArgType = argtp->pzKey;  break;
671         case OPARG_TYPE_FILE:        pzArgType = argtp->pzFile; break;
672         case OPARG_TYPE_MEMBERSHIP:  pzArgType = argtp->pzKeyL; break;
673         case OPARG_TYPE_BOOLEAN:     pzArgType = argtp->pzBool; break;
674         case OPARG_TYPE_NUMERIC:     pzArgType = argtp->pzNum;  break;
675         case OPARG_TYPE_HIERARCHY:   pzArgType = argtp->pzNest; break;
676         case OPARG_TYPE_STRING:      pzArgType = argtp->pzStr;  break;
677         case OPARG_TYPE_TIME:        pzArgType = argtp->pzTime; break;
678         default:                     goto bogus_desc;
679         }
680 
681         pzArgType = SPN_WHITESPACE_CHARS(pzArgType);
682         if (*pzArgType == NUL)
683             snprintf(z, sizeof(z), "%s", od->pz_Name);
684         else
685             snprintf(z, sizeof(z), "%s=%s", od->pz_Name, pzArgType);
686         fprintf(option_usage_fp, usefmt, z, od->pzText);
687 
688         switch (OPTST_GET_ARGTYPE(od->fOptState)) {
689         case OPARG_TYPE_ENUMERATION:
690         case OPARG_TYPE_MEMBERSHIP:
691             displayEnum = (od->pOptProc != NULL) ? true : displayEnum;
692         }
693     }
694 
695     return;
696 
697  bogus_desc:
698     fprintf(stderr, zbad_od, opts->pzProgName, od->pz_Name);
699     ao_bug(zbad_arg_type_msg);
700 }
701 
702 /**
703  * Print the long options processed with "-W".  These options will be the
704  * ones that do *not* have flag characters.
705  *
706  * @param opts  the program option descriptor
707  * @param title the title for the options
708  */
709 static void
710 prt_vendor_opts(tOptions * opts, char const * title)
711 {
712     static unsigned int const not_vended_mask =
713         OPTST_NO_USAGE_MASK | OPTST_DOCUMENT;
714 
715     static char const vfmtfmt[] = "%%-%us %%s\n";
716     char vfmt[sizeof(vfmtfmt)+10]; /* strlen(UINT_MAX) */
717 
718     /*
719      *  Only handle client specified options.  The "vendor option" follows
720      *  "presetOptCt", so we won't loop/recurse indefinitely.
721      */
722     int          ct     = opts->presetOptCt;
723     tOptDesc *   od     = opts->pOptDesc;
724     fprintf(option_usage_fp, zTabout + tab_skip_ct, zVendOptsAre);
725 
726     {
727         size_t   nmlen  = 0;
728         do  {
729             size_t l;
730             if (  ((od->fOptState & not_vended_mask) != 0)
731                || GRAPH_CH(od->optValue))
732                 continue;
733 
734             l = strlen(od->pz_Name);
735             if (l > nmlen)  nmlen = l;
736         } while (od++, (--ct > 0));
737 
738         snprintf(vfmt, sizeof(vfmt), vfmtfmt, (unsigned int)nmlen + 4);
739     }
740 
741     if (tab_skip_ct > 0)
742         tab_skip_ct--;
743 
744     ct    = opts->presetOptCt;
745     od    = opts->pOptDesc;
746 
747     do  {
748         if (  ((od->fOptState & not_vended_mask) != 0)
749            || GRAPH_CH(od->optValue))
750             continue;
751 
752         prt_one_vendor(opts, od, &argTypes, vfmt);
753         prt_extd_usage(opts, od, title);
754 
755     } while (od++, (--ct > 0));
756 
757     /* no need to restore "tab_skip_ct" - options are done now */
758 }
759 
760 /**
761  * Print extended usage.  Usage/help was requested.
762  *
763  * @param opts  the program option descriptor
764  * @param od   the option descriptor
765  * @param title the title for the options
766  */
767 static void
768 prt_extd_usage(tOptions * opts, tOptDesc * od, char const * title)
769 {
770     if (  ((opts->fOptSet & OPTPROC_VENDOR_OPT) != 0)
771        && (od->optActualValue == VENDOR_OPTION_VALUE)) {
772         prt_vendor_opts(opts, title);
773         return;
774     }
775 
776     /*
777      *  IF there are option conflicts or dependencies,
778      *  THEN print them here.
779      */
780     if ((od->pOptMust != NULL) || (od->pOptCant != NULL))
781         prt_conflicts(opts, od);
782 
783     /*
784      *  IF there is a disablement string
785      *  THEN print the disablement info
786      */
787     if (od->pz_DisableName != NULL )
788         fprintf(option_usage_fp, zDis + tab_skip_ct, od->pz_DisableName);
789 
790     /*
791      *  Check for argument types that have callbacks with magical properties
792      */
793     switch (OPTST_GET_ARGTYPE(od->fOptState)) {
794     case OPARG_TYPE_NUMERIC:
795         /*
796          *  IF the numeric option has a special callback,
797          *  THEN call it, requesting the range or other special info
798          */
799         if (  (od->pOptProc != NULL)
800            && (od->pOptProc != optionNumericVal) ) {
801             (*(od->pOptProc))(OPTPROC_EMIT_USAGE, od);
802         }
803         break;
804 
805     case OPARG_TYPE_FILE:
806         (*(od->pOptProc))(OPTPROC_EMIT_USAGE, od);
807         break;
808     }
809 
810     /*
811      *  IF the option defaults to being enabled,
812      *  THEN print that out
813      */
814     if (od->fOptState & OPTST_INITENABLED)
815         fputs(zEnab + tab_skip_ct, option_usage_fp);
816 
817     /*
818      *  IF  the option is in an equivalence class
819      *        AND not the designated lead
820      *  THEN print equivalence and leave it at that.
821      */
822     if (  (od->optEquivIndex != NO_EQUIVALENT)
823        && (od->optEquivIndex != od->optActualIndex )  )  {
824         fprintf(option_usage_fp, zalt_opt + tab_skip_ct,
825                  opts->pOptDesc[ od->optEquivIndex ].pz_Name);
826         return;
827     }
828 
829     /*
830      *  IF this particular option can NOT be preset
831      *    AND some form of presetting IS allowed,
832      *    AND it is not an auto-managed option (e.g. --help, et al.)
833      *  THEN advise that this option may not be preset.
834      */
835     if (  ((od->fOptState & OPTST_NO_INIT) != 0)
836        && (  (opts->papzHomeList != NULL)
837           || (opts->pzPROGNAME != NULL)
838           )
839        && (od->optIndex < opts->presetOptCt)
840        )
841 
842         fputs(zNoPreset + tab_skip_ct, option_usage_fp);
843 
844     /*
845      *  Print the appearance requirements.
846      */
847     if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_MEMBERSHIP)
848         fputs(zMembers + tab_skip_ct, option_usage_fp);
849 
850     else switch (od->optMinCt) {
851     case 1:
852     case 0:
853         switch (od->optMaxCt) {
854         case 0:       fputs(zPreset + tab_skip_ct, option_usage_fp); break;
855         case NOLIMIT: fputs(zNoLim  + tab_skip_ct, option_usage_fp); break;
856         case 1:       break;
857             /*
858              * IF the max is more than one but limited, print "UP TO" message
859              */
860         default:
861             fprintf(option_usage_fp, zUpTo + tab_skip_ct, od->optMaxCt); break;
862         }
863         break;
864 
865     default:
866         /*
867          *  More than one is required.  Print the range.
868          */
869         fprintf(option_usage_fp, zMust + tab_skip_ct,
870                 od->optMinCt, od->optMaxCt);
871     }
872 
873     if (  NAMED_OPTS(opts)
874        && (opts->specOptIdx.default_opt == od->optIndex))
875         fputs(zDefaultOpt + tab_skip_ct, option_usage_fp);
876 }
877 
878 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
879 /**
880  * Figure out where all the initialization files might live.  This requires
881  * translating some environment variables and testing to see if a name is a
882  * directory or a file.  It's squishy, but important to tell users how to
883  * find these files.
884  *
885  * @param[in]  papz        search path
886  * @param[out] ini_file    an output buffer of AG_PATH_MAX+1 bytes
887  * @param[in]  path_nm     the name of the file we're hunting for
888  */
889 static void
890 prt_ini_list(char const * const * papz, char const * ini_file,
891              char const * path_nm)
892 {
893     char pth_buf[AG_PATH_MAX+1];
894 
895     fputs(zPresetIntro, option_usage_fp);
896 
897     for (;;) {
898         char const * path   = *(papz++);
899         char const * nm_buf = pth_buf;
900 
901         if (path == NULL)
902             break;
903 
904         /*
905          * Ignore any invalid paths
906          */
907         if (! optionMakePath(pth_buf, (int)sizeof(pth_buf), path, path_nm))
908             nm_buf = path;
909 
910         /*
911          * Expand paths that are relative to the executable or installation
912          * directories.  Leave alone paths that use environment variables.
913          */
914         else if ((*path == '$')
915                  && ((path[1] == '$') || (path[1] == '@')))
916             path = nm_buf;
917 
918         /*
919          *  Print the name of the "homerc" file.  If the "rcfile" name is
920          *  not empty, we may or may not print that, too...
921          */
922         fprintf(option_usage_fp, zPathFmt, path);
923         if (*ini_file != NUL) {
924             struct stat sb;
925 
926             /*
927              *  IF the "homerc" file is a directory,
928              *  then append the "rcfile" name.
929              */
930             if ((stat(nm_buf, &sb) == 0) && S_ISDIR(sb.st_mode)) {
931                 fputc(DIRCH,    option_usage_fp);
932                 fputs(ini_file, option_usage_fp);
933             }
934         }
935 
936         fputc(NL, option_usage_fp);
937     }
938 }
939 
940 /**
941  *  Print the usage line preamble text
942  *
943  * @param opts  the program option descriptor
944  * @param od    the option descriptor
945  * @param at    names of the option argument types
946  */
947 static void
948 prt_preamble(tOptions * opts, tOptDesc * od, arg_types_t * at)
949 {
950     /*
951      *  Flag prefix: IF no flags at all, then omit it.  If not printable
952      *  (not allowed for this option), then blank, else print it.
953      *  Follow it with a comma if we are doing GNU usage and long
954      *  opts are to be printed too.
955      */
956     if ((opts->fOptSet & OPTPROC_SHORTOPT) == 0)
957         fputs(at->pzSpc, option_usage_fp);
958 
959     else if (! GRAPH_CH(od->optValue)) {
960         if (  (opts->fOptSet & (OPTPROC_GNUUSAGE|OPTPROC_LONGOPT))
961            == (OPTPROC_GNUUSAGE|OPTPROC_LONGOPT))
962             fputc(' ', option_usage_fp);
963         fputs(at->pzNoF, option_usage_fp);
964 
965     } else {
966         fprintf(option_usage_fp, "   -%c", od->optValue);
967         if (  (opts->fOptSet & (OPTPROC_GNUUSAGE|OPTPROC_LONGOPT))
968            == (OPTPROC_GNUUSAGE|OPTPROC_LONGOPT))
969             fputs(", ", option_usage_fp);
970     }
971 }
972 
973 /**
974  *  Print the usage information for a single option.
975  *
976  * @param opts  the program option descriptor
977  * @param od    the option descriptor
978  * @param at    names of the option argument types
979  */
980 static void
981 prt_one_usage(tOptions * opts, tOptDesc * od, arg_types_t * at)
982 {
983     prt_preamble(opts, od, at);
984 
985     {
986         char z[80];
987         char const * atyp;
988 
989         /*
990          *  Determine the argument type string first on its usage, then,
991          *  when the option argument is required, base the type string on the
992          *  argument type.
993          */
994         if (od->fOptState & OPTST_ARG_OPTIONAL) {
995             atyp = at->pzOpt;
996 
997         } else switch (OPTST_GET_ARGTYPE(od->fOptState)) {
998         case OPARG_TYPE_NONE:        atyp = at->pzNo;   break;
999         case OPARG_TYPE_ENUMERATION: atyp = at->pzKey;  break;
1000         case OPARG_TYPE_FILE:        atyp = at->pzFile; break;
1001         case OPARG_TYPE_MEMBERSHIP:  atyp = at->pzKeyL; break;
1002         case OPARG_TYPE_BOOLEAN:     atyp = at->pzBool; break;
1003         case OPARG_TYPE_NUMERIC:     atyp = at->pzNum;  break;
1004         case OPARG_TYPE_HIERARCHY:   atyp = at->pzNest; break;
1005         case OPARG_TYPE_STRING:      atyp = at->pzStr;  break;
1006         case OPARG_TYPE_TIME:        atyp = at->pzTime; break;
1007         default:                     goto bogus_desc;
1008         }
1009 
1010 #ifdef _WIN32
1011         if (at->pzOptFmt == zGnuOptFmt)
1012             snprintf(z, sizeof(z), "--%s%s", od->pz_Name, atyp);
1013         else if (at->pzOptFmt == zGnuOptFmt + 2)
1014             snprintf(z, sizeof(z), "%s%s", od->pz_Name, atyp);
1015         else
1016 #endif
1017         snprintf(z, sizeof(z), at->pzOptFmt, atyp, od->pz_Name,
1018                  (od->optMinCt != 0) ? at->pzReq : at->pzOpt);
1019 
1020         fprintf(option_usage_fp, line_fmt_buf, z, od->pzText);
1021 
1022         switch (OPTST_GET_ARGTYPE(od->fOptState)) {
1023         case OPARG_TYPE_ENUMERATION:
1024         case OPARG_TYPE_MEMBERSHIP:
1025             displayEnum = (od->pOptProc != NULL) ? true : displayEnum;
1026         }
1027     }
1028 
1029     return;
1030 
1031  bogus_desc:
1032     fprintf(stderr, zbad_od, opts->pzProgName, od->pz_Name);
1033     option_exits(EX_SOFTWARE);
1034 }
1035 
1036 /**
1037  *  Print out the usage information for just the options.
1038  */
1039 static void
1040 prt_opt_usage(tOptions * opts, int ex_code, char const * title)
1041 {
1042     int         ct     = opts->optCt;
1043     int         optNo  = 0;
1044     tOptDesc *  od     = opts->pOptDesc;
1045     int         docCt  = 0;
1046 
1047     do  {
1048         /*
1049          * no usage --> disallowed on command line (OPTST_NO_COMMAND), or
1050          * deprecated -- strongly discouraged (OPTST_DEPRECATED), or
1051          * compiled out of current object code (OPTST_OMITTED)
1052          */
1053         if ((od->fOptState & OPTST_NO_USAGE_MASK) != 0) {
1054 
1055             /*
1056              * IF      this is a compiled-out option
1057              *   *AND* usage was requested with "omitted-usage"
1058              *   *AND* this is NOT abbreviated usage
1059              * THEN display this option.
1060              */
1061             if (  (od->fOptState == (OPTST_OMITTED | OPTST_NO_INIT))
1062                && (od->pz_Name != NULL)
1063                && (ex_code == EXIT_SUCCESS))  {
1064 
1065                 char const * why_pz =
1066                     (od->pzText == NULL) ? zDisabledWhy : od->pzText;
1067                 prt_preamble(opts, od, &argTypes);
1068                 fprintf(option_usage_fp, zDisabledOpt, od->pz_Name, why_pz);
1069             }
1070 
1071             continue;
1072         }
1073 
1074         if ((od->fOptState & OPTST_DOCUMENT) != 0) {
1075             if (ex_code == EXIT_SUCCESS) {
1076                 fprintf(option_usage_fp, argTypes.pzBrk, od->pzText,
1077                         title);
1078                 docCt++;
1079             }
1080 
1081             continue;
1082         }
1083 
1084         /* Skip name only options when we have a vendor option */
1085         if (  ((opts->fOptSet & OPTPROC_VENDOR_OPT) != 0)
1086            && (! GRAPH_CH(od->optValue)))
1087             continue;
1088 
1089         /*
1090          *  IF       this is the first auto-opt maintained option
1091          *    *AND*  we are doing a full help
1092          *    *AND*  there are documentation options
1093          *    *AND*  the last one was not a doc option,
1094          *  THEN document that the remaining options are not user opts
1095          */
1096         if ((docCt > 0) && (ex_code == EXIT_SUCCESS)) {
1097             if (opts->presetOptCt == optNo) {
1098                 if ((od[-1].fOptState & OPTST_DOCUMENT) == 0)
1099                     fprintf(option_usage_fp, argTypes.pzBrk, zAuto, title);
1100 
1101             } else if ((ct == 1) &&
1102                        (opts->fOptSet & OPTPROC_VENDOR_OPT))
1103                 fprintf(option_usage_fp, argTypes.pzBrk, zVendIntro, title);
1104         }
1105 
1106         prt_one_usage(opts, od, &argTypes);
1107 
1108         /*
1109          *  IF we were invoked because of the --help option,
1110          *  THEN print all the extra info
1111          */
1112         if (ex_code == EXIT_SUCCESS)
1113             prt_extd_usage(opts, od, title);
1114 
1115     } while (od++, optNo++, (--ct > 0));
1116 
1117     fputc(NL, option_usage_fp);
1118 }
1119 
1120 
1121 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1122 /**
1123  *  Print program details.
1124  * @param[in] opts  the program option descriptor
1125  */
1126 static void
1127 prt_prog_detail(tOptions * opts)
1128 {
1129     bool need_intro = (opts->papzHomeList == NULL);
1130 
1131     /*
1132      *  Display all the places we look for config files, if we have
1133      *  a list of directories to search.
1134      */
1135     if (! need_intro)
1136         prt_ini_list(opts->papzHomeList, opts->pzRcName, opts->pzProgPath);
1137 
1138     /*
1139      *  Let the user know about environment variable settings
1140      */
1141     if ((opts->fOptSet & OPTPROC_ENVIRON) != 0) {
1142         if (need_intro)
1143             fputs(zPresetIntro, option_usage_fp);
1144 
1145         fprintf(option_usage_fp, zExamineFmt, opts->pzPROGNAME);
1146     }
1147 
1148     /*
1149      *  IF we found an enumeration,
1150      *  THEN hunt for it again.  Call the handler proc with a NULL
1151      *       option struct pointer.  That tells it to display the keywords.
1152      */
1153     if (displayEnum) {
1154         int        ct     = opts->optCt;
1155         int        optNo  = 0;
1156         tOptDesc * od     = opts->pOptDesc;
1157 
1158         fputc(NL, option_usage_fp);
1159         fflush(option_usage_fp);
1160         do  {
1161             switch (OPTST_GET_ARGTYPE(od->fOptState)) {
1162             case OPARG_TYPE_ENUMERATION:
1163             case OPARG_TYPE_MEMBERSHIP:
1164                 (*(od->pOptProc))(OPTPROC_EMIT_USAGE, od);
1165             }
1166         } while (od++, optNo++, (--ct > 0));
1167     }
1168 
1169     /*
1170      *  If there is a detail string, now is the time for that.
1171      */
1172     if (opts->pzDetail != NULL)
1173         fputs(opts->pzDetail, option_usage_fp);
1174 }
1175 
1176 
1177 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1178  *
1179  *   OPTION LINE FORMATTING SETUP
1180  *
1181  *  The "OptFmt" formats receive three arguments:
1182  *  1.  the type of the option's argument
1183  *  2.  the long name of the option
1184  *  3.  "YES" or "no ", depending on whether or not the option must appear
1185  *      on the command line.
1186  *  These formats are used immediately after the option flag (if used) has
1187  *  been printed.
1188  *
1189  *  Set up the formatting for GNU-style output
1190  */
1191 static int
1192 setGnuOptFmts(tOptions * opts, char const ** ptxt)
1193 {
1194     static char const zOneSpace[] = " ";
1195     int  flen = 22;
1196     *ptxt = zNoRq_ShrtTtl;
1197 
1198     argTypes.pzStr  = zGnuStrArg;
1199     argTypes.pzReq  = zOneSpace;
1200     argTypes.pzNum  = zGnuNumArg;
1201     argTypes.pzKey  = zGnuKeyArg;
1202     argTypes.pzKeyL = zGnuKeyLArg;
1203     argTypes.pzTime = zGnuTimeArg;
1204     argTypes.pzFile = zGnuFileArg;
1205     argTypes.pzBool = zGnuBoolArg;
1206     argTypes.pzNest = zGnuNestArg;
1207     argTypes.pzOpt  = zGnuOptArg;
1208     argTypes.pzNo   = zOneSpace;
1209     argTypes.pzBrk  = zGnuBreak;
1210     argTypes.pzNoF  = zSixSpaces;
1211     argTypes.pzSpc  = zThreeSpaces;
1212 
1213     switch (opts->fOptSet & OPTPROC_L_N_S) {
1214     case OPTPROC_L_N_S:    argTypes.pzOptFmt = zGnuOptFmt;     break;
1215     case OPTPROC_LONGOPT:  argTypes.pzOptFmt = zGnuOptFmt;     break;
1216     case 0:                argTypes.pzOptFmt = zGnuOptFmt + 2; break;
1217     case OPTPROC_SHORTOPT:
1218         argTypes.pzOptFmt = zShrtGnuOptFmt;
1219         zGnuStrArg[0] = zGnuNumArg[0] = zGnuKeyArg[0] = zGnuBoolArg[0] = ' ';
1220         argTypes.pzOpt = " [arg]";
1221         flen = 8;
1222         break;
1223     }
1224 
1225     return flen;
1226 }
1227 
1228 
1229 /*
1230  *  Standard (AutoOpts normal) option line formatting
1231  */
1232 static int
1233 setStdOptFmts(tOptions * opts, char const ** ptxt)
1234 {
1235     int  flen = 0;
1236 
1237     argTypes.pzStr  = zStdStrArg;
1238     argTypes.pzReq  = zStdReqArg;
1239     argTypes.pzNum  = zStdNumArg;
1240     argTypes.pzKey  = zStdKeyArg;
1241     argTypes.pzKeyL = zStdKeyLArg;
1242     argTypes.pzTime = zStdTimeArg;
1243     argTypes.pzFile = zStdFileArg;
1244     argTypes.pzBool = zStdBoolArg;
1245     argTypes.pzNest = zStdNestArg;
1246     argTypes.pzOpt  = zStdOptArg;
1247     argTypes.pzNo   = zStdNoArg;
1248     argTypes.pzBrk  = zStdBreak;
1249     argTypes.pzNoF  = zFiveSpaces;
1250     argTypes.pzSpc  = zTwoSpaces;
1251 
1252     switch (opts->fOptSet & (OPTPROC_NO_REQ_OPT | OPTPROC_SHORTOPT)) {
1253     case (OPTPROC_NO_REQ_OPT | OPTPROC_SHORTOPT):
1254         *ptxt = zNoRq_ShrtTtl;
1255         argTypes.pzOptFmt = zNrmOptFmt;
1256         flen = 19;
1257         break;
1258 
1259     case OPTPROC_NO_REQ_OPT:
1260         *ptxt = zNoRq_NoShrtTtl;
1261         argTypes.pzOptFmt = zNrmOptFmt;
1262         flen = 19;
1263         break;
1264 
1265     case OPTPROC_SHORTOPT:
1266         *ptxt = zReq_ShrtTtl;
1267         argTypes.pzOptFmt = zReqOptFmt;
1268         flen = 24;
1269         break;
1270 
1271     case 0:
1272         *ptxt = zReq_NoShrtTtl;
1273         argTypes.pzOptFmt = zReqOptFmt;
1274         flen = 24;
1275     }
1276 
1277     return flen;
1278 }
1279 
1280 /** @}
1281  *
1282  * Local Variables:
1283  * mode: C
1284  * c-file-style: "stroustrup"
1285  * indent-tabs-mode: nil
1286  * End:
1287  * end of autoopts/usage.c */
1288