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