1 /* help.c -- general-purpose command line option parser
2    Copyright (C) 2016-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as
6    published by the Free Software Foundation; either version 3, or (at
7    your option) any later version.
8 
9    GNU Mailutils is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <stdlib.h>
21 #include <string.h>
22 #include <limits.h>
23 #include <errno.h>
24 #include <unistd.h>
25 #include <mailutils/alloc.h>
26 #include <mailutils/opt.h>
27 #include <mailutils/cctype.h>
28 #include <mailutils/nls.h>
29 #include <mailutils/wordsplit.h>
30 #include <mailutils/stream.h>
31 
32 unsigned short_opt_col = 2;
33 unsigned long_opt_col; /* Initialized in init_usage_vars */
34 /*FIXME: doc_opt_col? */
35 unsigned header_col = 1;
36 unsigned opt_doc_col = 29;
37 unsigned usage_indent = 12;
38 unsigned rmargin = 79;
39 
40 unsigned dup_args = 0;
41 unsigned dup_args_note = 1;
42 
43 enum usage_var_type
44   {
45     usage_var_column,
46     usage_var_bool
47   };
48 
49 static struct usage_var
50 {
51   char *name;
52   unsigned *valptr;
53   enum usage_var_type type;
54 } usage_var[] = {
55   { "short-opt-col", &short_opt_col, usage_var_column },
56   { "header-col",    &header_col,    usage_var_column },
57   { "opt-doc-col",   &opt_doc_col,   usage_var_column },
58   { "usage-indent",  &usage_indent,  usage_var_column },
59   { "rmargin",       &rmargin,       usage_var_column },
60   { "dup-args",      &dup_args,      usage_var_bool },
61   { "dup-args-note", &dup_args_note, usage_var_bool },
62   { "long-opt-col",  &long_opt_col,  usage_var_column },
63   { "doc_opt_col",   NULL,           usage_var_column },
64   { NULL }
65 };
66 
67 unsigned
mu_parseopt_getcolumn(const char * name)68 mu_parseopt_getcolumn (const char *name)
69 {
70   struct usage_var *p;
71   unsigned retval = 0;
72   for (p = usage_var; p->name; p++)
73     {
74       if (strcmp (p->name, name) == 0)
75 	{
76 	  if (p->valptr)
77 	    retval = *p->valptr;
78 	  break;
79 	}
80     }
81   return retval;
82 }
83 
84 static void
set_usage_var(struct mu_parseopt * po,char const * id)85 set_usage_var (struct mu_parseopt *po, char const *id)
86 {
87   struct usage_var *p;
88   size_t len;
89   int boolval = 1;
90 
91   if (strlen (id) > 3 && memcmp (id, "no-", 3) == 0)
92     {
93       id += 3;
94       boolval = 0;
95     }
96   len = strcspn (id, "=");
97 
98   for (p = usage_var; p->name; p++)
99     {
100       if (strlen (p->name) == len && memcmp (p->name, id, len) == 0)
101 	{
102 	  if (!p->valptr)
103 	    return;
104 
105 	  if (p->type == usage_var_bool)
106 	    {
107 	      if (id[len])
108 		{
109 		  if (po->po_prog_name)
110 		    fprintf (stderr, "%s: ", po->po_prog_name);
111 		  fprintf (stderr,
112 			   "error in ARGP_HELP_FMT: improper usage of [no-]%s\n",
113 			   id);
114 		  return;
115 		}
116 	      *p->valptr = boolval;
117 	      return;
118 	    }
119 
120 	  if (id[len])
121 	    {
122 	      char *endp;
123 	      unsigned long val;
124 
125 	      errno = 0;
126 	      val = strtoul (id + len + 1, &endp, 10);
127 	      if (errno || *endp)
128 		{
129 		  if (po->po_prog_name)
130 		    fprintf (stderr, "%s: ", po->po_prog_name);
131 		  fprintf (stderr,
132 			   "error in ARGP_HELP_FMT: bad value for %s\n",
133 			   id);
134 		}
135 	      else if (val > UINT_MAX)
136 		{
137 		  if (po->po_prog_name)
138 		    fprintf (stderr, "%s: ", po->po_prog_name);
139 		  fprintf (stderr,
140 			   "error in ARGP_HELP_FMT: %s value is out of range\n",
141 			   id);
142 		}
143 	      else
144 		*p->valptr = val;
145 	    }
146 	  else
147 	    {
148 	      if (po->po_prog_name)
149 		fprintf (stderr, "%s: ", po->po_prog_name);
150 	      fprintf (stderr,
151 		       "%s: ARGP_HELP_FMT parameter requires a value\n",
152 		       id);
153 	      return;
154 	    }
155 	  return;
156 	}
157     }
158 
159   if (po->po_prog_name)
160     fprintf (stderr, "%s: ", po->po_prog_name);
161   fprintf (stderr,
162 	   "%s: Unknown ARGP_HELP_FMT parameter\n",
163 	   id);
164 }
165 
166 static void
init_usage_vars(struct mu_parseopt * po)167 init_usage_vars (struct mu_parseopt *po)
168 {
169   char *fmt;
170   struct mu_wordsplit ws;
171   size_t i;
172 
173   if (po->po_flags & MU_PARSEOPT_SINGLE_DASH)
174     long_opt_col = 2;
175   else
176     long_opt_col = 6;
177 
178   fmt = getenv ("ARGP_HELP_FMT");
179   if (!fmt)
180     return;
181   ws.ws_delim = ",";
182   if (mu_wordsplit (fmt, &ws,
183 		    MU_WRDSF_DELIM | MU_WRDSF_NOVAR | MU_WRDSF_NOCMD
184 		    | MU_WRDSF_WS | MU_WRDSF_SHOWERR))
185     return;
186   for (i = 0; i < ws.ws_wordc; i++)
187     {
188       set_usage_var (po, ws.ws_wordv[i]);
189     }
190 
191   mu_wordsplit_free (&ws);
192 }
193 
194 static void
set_margin(mu_stream_t str,unsigned margin)195 set_margin (mu_stream_t str, unsigned margin)
196 {
197   mu_stream_ioctl (str, MU_IOCTL_WORDWRAPSTREAM,
198 		   MU_IOCTL_WORDWRAP_SET_MARGIN,
199 		   &margin);
200 }
201 static void
set_next_margin(mu_stream_t str,unsigned margin)202 set_next_margin (mu_stream_t str, unsigned margin)
203 {
204   mu_stream_ioctl (str, MU_IOCTL_WORDWRAPSTREAM,
205 		   MU_IOCTL_WORDWRAP_SET_NEXT_MARGIN,
206 		   &margin);
207 }
208 static void
get_offset(mu_stream_t str,unsigned * offset)209 get_offset (mu_stream_t str, unsigned *offset)
210 {
211   mu_stream_ioctl (str, MU_IOCTL_WORDWRAPSTREAM,
212 		   MU_IOCTL_WORDWRAP_GET_COLUMN,
213 		   offset);
214 }
215 
216 static void
print_opt_arg(mu_stream_t str,struct mu_option * opt,int delim)217 print_opt_arg (mu_stream_t str, struct mu_option *opt, int delim)
218 {
219   if (opt->opt_flags & MU_OPTION_ARG_OPTIONAL)
220     {
221       if (delim == '=')
222 	mu_stream_printf (str, "[=%s]", gettext (opt->opt_arg));
223       else
224 	mu_stream_printf (str, "[%s]", gettext (opt->opt_arg));
225     }
226   else
227     mu_stream_printf (str, "%c%s", delim, gettext (opt->opt_arg));
228 }
229 
230 static size_t
print_option(mu_stream_t str,struct mu_parseopt * po,size_t num,int * argsused)231 print_option (mu_stream_t str, struct mu_parseopt *po, size_t num,
232 	      int *argsused)
233 {
234   struct mu_option *opt = po->po_optv[num];
235   size_t next, i;
236   int delim;
237   int first_option = 1;
238   int first_long_option = 1;
239 
240   if (MU_OPTION_IS_GROUP_HEADER (opt))
241     {
242       if (num)
243 	mu_stream_printf (str, "\n");
244       if (opt->opt_doc[0])
245 	{
246 	  set_margin (str, header_col);
247 	  mu_stream_printf (str, "%s\n", gettext (opt->opt_doc));
248 	}
249       return num + 1;
250     }
251 
252   /* count aliases */
253   for (next = num + 1;
254        next < po->po_optc && po->po_optv[next]->opt_flags & MU_OPTION_ALIAS;
255        next++);
256 
257   if (opt->opt_flags & MU_OPTION_HIDDEN)
258     return next;
259 
260   if (po->po_flags & MU_PARSEOPT_SINGLE_DASH)
261     {
262       if (!opt->opt_long)
263 	return num + 1; /* Ignore erroneous option */
264       set_margin (str, long_opt_col);
265     }
266   else
267     {
268       set_margin (str, short_opt_col);
269       for (i = num; i < next; i++)
270 	{
271 	  if (MU_OPTION_IS_VALID_SHORT_OPTION (po->po_optv[i]))
272 	    {
273 	      if (first_option)
274 		first_option = 0;
275 	      else
276 		mu_stream_printf (str, ", ");
277 	      mu_stream_printf (str, "-%c", po->po_optv[i]->opt_short);
278 	      delim = ' ';
279 	      if (opt->opt_arg && dup_args)
280 		print_opt_arg (str, opt, delim);
281 	    }
282 	}
283     }
284 
285   for (i = num; i < next; i++)
286     {
287       if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i]))
288 	{
289 	  if (first_option)
290 	    first_option = 0;
291 	  else
292 	    mu_stream_printf (str, ", ");
293 
294 	  if (first_long_option)
295 	    {
296 	      unsigned off;
297 	      get_offset (str, &off);
298 	      if (off < long_opt_col)
299 		set_margin (str, long_opt_col);
300 	      first_long_option = 0;
301 	    }
302 
303   	  mu_stream_printf (str, "%s", po->po_long_opt_start);
304 	  if (mu_option_possible_negation (po, po->po_optv[i]))
305 	    mu_stream_printf (str, "[%s]", po->po_negation);
306   	  mu_stream_printf (str, "%s", po->po_optv[i]->opt_long);
307 	  delim = ((po->po_flags & MU_PARSEOPT_SINGLE_DASH)
308 		   && !(opt->opt_flags & MU_OPTION_ARG_OPTIONAL)) ? ' ' : '=';
309 	  if (opt->opt_arg && dup_args)
310 	    print_opt_arg (str, opt, delim);
311 	}
312     }
313 
314   if (opt->opt_arg)
315     {
316       *argsused = 1;
317       if (!dup_args)
318 	print_opt_arg (str, opt, delim);
319     }
320 
321   set_margin (str, opt_doc_col);
322   mu_stream_printf (str, "%s\n", gettext (opt->opt_doc));
323 
324   return next;
325 }
326 
327 void
mu_option_describe_options(mu_stream_t str,struct mu_parseopt * po)328 mu_option_describe_options (mu_stream_t str, struct mu_parseopt *po)
329 {
330   unsigned i;
331   int argsused = 0;
332 
333   for (i = 0; i < po->po_optc; )
334     i = print_option (str, po, i, &argsused);
335 
336   set_margin (str, 0);
337   mu_stream_printf (str, "\n");
338 
339   if (argsused && !(po->po_flags & MU_PARSEOPT_SINGLE_DASH) && dup_args_note)
340     {
341       mu_stream_printf (str, "%s\n\n",
342 			_("Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options."));
343     }
344 }
345 
346 static void print_program_usage (struct mu_parseopt *po, int optsum,
347 				 mu_stream_t str);
348 
349 int
mu_parseopt_help_stream_create(mu_stream_t * retstr,struct mu_parseopt * po,mu_stream_t outstr)350 mu_parseopt_help_stream_create (mu_stream_t *retstr,
351 				struct mu_parseopt *po, mu_stream_t outstr)
352 {
353   init_usage_vars (po);
354   return mu_wordwrap_stream_create (retstr, outstr, 0, rmargin);
355 }
356 
357 void
mu_program_help(struct mu_parseopt * po,mu_stream_t outstr)358 mu_program_help (struct mu_parseopt *po, mu_stream_t outstr)
359 {
360   mu_stream_t str;
361 
362   if (mu_parseopt_help_stream_create (&str, po, outstr))
363     abort ();
364 
365   print_program_usage (po, 0, str);
366 
367   if (po->po_prog_doc)
368     {
369       set_margin (str, 0);
370       mu_stream_printf (str, "%s\n", gettext (po->po_prog_doc));
371     }
372   mu_stream_printf (str, "\n");
373 
374   if (po->po_prog_doc_hook)
375     {
376       po->po_prog_doc_hook (po, str);
377       mu_stream_printf (str, "\n");
378     }
379 
380   mu_option_describe_options (str, po);
381 
382   if (po->po_help_hook)
383     {
384       po->po_help_hook (po, str);
385       mu_stream_printf (str, "\n");
386     }
387 
388   set_margin (str, 0);
389   if (po->po_bug_address)
390     /* TRANSLATORS: The placeholder indicates the bug-reporting address
391        for this package.  Please add _another line_ saying
392        "Report translation bugs to <...>\n" with the address for translation
393        bugs (typically your translation team's web or email address).  */
394     mu_stream_printf (str, _("Report bugs to <%s>.\n"), po->po_bug_address);
395 
396   if (po->po_package_name && po->po_package_url)
397     mu_stream_printf (str, _("%s home page: <%s>\n"),
398 		      po->po_package_name, po->po_package_url);
399   if (po->po_flags & MU_PARSEOPT_EXTRA_INFO)
400     mu_stream_printf (str, "%s\n", _(po->po_extra_info));
401 
402   mu_stream_destroy (&str);
403 }
404 
405 static struct mu_option **option_tab;
406 
407 static int
cmpidx_short(const void * a,const void * b)408 cmpidx_short (const void *a, const void *b)
409 {
410   unsigned const *ai = (unsigned const *)a;
411   unsigned const *bi = (unsigned const *)b;
412   int ac = option_tab[*ai]->opt_short;
413   int bc = option_tab[*bi]->opt_short;
414   int d;
415 
416   if (mu_isalpha (ac))
417     {
418       if (!mu_isalpha (bc))
419 	return -1;
420     }
421   else if (mu_isalpha (bc))
422     return 1;
423 
424   d = mu_tolower (ac) - mu_tolower (bc);
425   if (d == 0)
426     d =  mu_isupper (ac) ? 1 : -1;
427   return d;
428 }
429 
430 static int
cmpidx_long(const void * a,const void * b)431 cmpidx_long (const void *a, const void *b)
432 {
433   unsigned const *ai = (unsigned const *)a;
434   unsigned const *bi = (unsigned const *)b;
435   struct mu_option const *ap = option_tab[*ai];
436   struct mu_option const *bp = option_tab[*bi];
437   return strcmp (ap->opt_long, bp->opt_long);
438 }
439 
440 static void
option_summary(struct mu_parseopt * po,mu_stream_t str)441 option_summary (struct mu_parseopt *po, mu_stream_t str)
442 {
443   unsigned i;
444   unsigned *idxbuf;
445   unsigned nidx;
446 
447   struct mu_option **optbuf = po->po_optv;
448   size_t optcnt = po->po_optc;
449 
450   option_tab = optbuf;
451 
452   idxbuf = mu_calloc (optcnt, sizeof (idxbuf[0]));
453 
454   if (!(po->po_flags & MU_PARSEOPT_SINGLE_DASH))
455     {
456       /* Print a list of short options without arguments. */
457       for (i = nidx = 0; i < optcnt; i++)
458 	if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]) &&
459 	    !(optbuf[i]->opt_flags & MU_OPTION_HIDDEN) &&
460 	    !optbuf[i]->opt_arg)
461 	  idxbuf[nidx++] = i;
462 
463       if (nidx)
464 	{
465 	  qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short);
466 	  mu_stream_printf (str, "[-");
467 	  for (i = 0; i < nidx; i++)
468 	    {
469 	      mu_stream_printf (str, "%c", optbuf[idxbuf[i]]->opt_short);
470 	    }
471 	  mu_stream_printf (str, "%c", ']');
472 	}
473 
474       /* Print a list of short options with arguments. */
475       for (i = nidx = 0; i < optcnt; i++)
476 	{
477 	  if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]) &&
478 	      !(optbuf[i]->opt_flags & MU_OPTION_HIDDEN) &&
479 	      optbuf[i]->opt_arg)
480 	    idxbuf[nidx++] = i;
481 	}
482 
483       if (nidx)
484 	{
485 	  qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short);
486 
487 	  for (i = 0; i < nidx; i++)
488 	    {
489 	      struct mu_option *opt = optbuf[idxbuf[i]];
490 	      const char *arg = gettext (opt->opt_arg);
491 	      if (opt->opt_flags & MU_OPTION_ARG_OPTIONAL)
492 		mu_stream_printf (str, " [-%c[%s]]", opt->opt_short, arg);
493 	      else
494 		mu_stream_printf (str, " [-%c %s]", opt->opt_short, arg);
495 	    }
496 	}
497     }
498 
499   /* Print a list of long options */
500   for (i = nidx = 0; i < optcnt; i++)
501     {
502       if (MU_OPTION_IS_VALID_LONG_OPTION (optbuf[i]) &&
503 	  !(optbuf[i]->opt_flags & MU_OPTION_HIDDEN))
504 	idxbuf[nidx++] = i;
505     }
506 
507   if (nidx)
508     {
509       qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_long);
510 
511       for (i = 0; i < nidx; i++)
512 	{
513 	  struct mu_option *opt = optbuf[idxbuf[i]];
514 	  const char *arg = opt->opt_arg ? gettext (opt->opt_arg) : NULL;
515 
516 	  mu_stream_printf (str, " [%s", po->po_long_opt_start);
517 	  if (mu_option_possible_negation (po, opt))
518 	    mu_stream_printf (str, "[%s]", po->po_negation);
519 	  mu_stream_printf (str, "%s", opt->opt_long);
520 
521 	  if (opt->opt_arg)
522 	    {
523 	      if (opt->opt_flags & MU_OPTION_ARG_OPTIONAL)
524 		mu_stream_printf (str, "[=%s]", arg);
525 	      else if (po->po_flags & MU_PARSEOPT_SINGLE_DASH)
526 		mu_stream_printf (str, " %s", arg);
527 	      else
528 		mu_stream_printf (str, "=%s", arg);
529 	    }
530 	  mu_stream_printf (str, "%c", ']');
531 	}
532     }
533 
534   if (po->po_special_args)
535     mu_stream_printf (str, " %s", gettext (po->po_special_args));
536 
537   free (idxbuf);
538 }
539 
540 static void
print_program_usage(struct mu_parseopt * po,int optsum,mu_stream_t str)541 print_program_usage (struct mu_parseopt *po, int optsum, mu_stream_t str)
542 {
543   char const *usage_text;
544   char const **arg_text;
545   size_t i;
546 
547   usage_text = _("Usage:");
548 
549   arg_text = po->po_prog_args;
550   i = 0;
551 
552   do
553     {
554       mu_stream_printf (str, "%s %s ", usage_text, po->po_prog_name);
555       set_next_margin (str, usage_indent);
556 
557       if (optsum)
558 	{
559 	  option_summary (po, str);
560 	  optsum = 0;
561 	}
562       else
563 	{
564 	  mu_stream_printf (str, "[%s...]", _("OPTION"));
565 	  if (po->po_special_args)
566 	    mu_stream_printf (str, " %s", gettext (po->po_special_args));
567 	}
568 
569       if (arg_text)
570 	{
571 	  mu_stream_printf (str, " %s\n", gettext (arg_text[i]));
572 	  if (i == 0)
573 	    usage_text = _("or: ");
574 	  set_margin (str, 2);
575 	  i++;
576 	}
577       else
578 	mu_stream_flush (str);
579     }
580   while (arg_text && arg_text[i]);
581 }
582 
583 void
mu_program_usage(struct mu_parseopt * po,int optsum,mu_stream_t outstr)584 mu_program_usage (struct mu_parseopt *po, int optsum, mu_stream_t outstr)
585 {
586   mu_stream_t str;
587 
588   if (mu_parseopt_help_stream_create (&str, po, outstr))
589     abort ();
590   print_program_usage (po, optsum, str);
591   mu_stream_destroy (&str);
592 }
593 
594 void
mu_program_version(struct mu_parseopt * po,mu_stream_t outstr)595 mu_program_version (struct mu_parseopt *po, mu_stream_t outstr)
596 {
597   mu_stream_t str;
598 
599   if (mu_parseopt_help_stream_create (&str, po, outstr))
600     abort ();
601 
602   po->po_version_hook (po, str);
603 
604   mu_stream_destroy (&str);
605 }
606