1 /* grecs - Gray's Extensible Configuration System
2 Copyright (C) 2007-2016 Sergey Poznyakoff
3
4 Grecs is free software; you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation; either version 3 of the License, or (at your
7 option) any later version.
8
9 Grecs is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with Grecs. If not, see <http://www.gnu.org/licenses/>. */
16
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include "grecs.h"
24 #include "grecs/opt.h"
25 #include "wordsplit.h"
26
27 static void
indent(size_t start,size_t col)28 indent(size_t start, size_t col)
29 {
30 for (; start < col; start++)
31 putchar(' ');
32 }
33
34 static void
print_option_descr(const char * descr,size_t lmargin,size_t rmargin)35 print_option_descr(const char *descr, size_t lmargin, size_t rmargin)
36 {
37 while (*descr) {
38 int i, s = 0;
39 size_t width = rmargin - lmargin;
40
41 for (i = 0; ; i++) {
42 if (descr[i] == 0 || descr[i] == ' ' ||
43 descr[i] == '\t') {
44 if (i > width)
45 break;
46 s = i;
47 if (descr[i] == 0)
48 break;
49 }
50 }
51 printf("%*.*s\n", s, s, descr);
52 descr += s;
53 if (*descr) {
54 indent(0, lmargin);
55 descr++;
56 }
57 }
58 }
59
60 static int
optcmp(const void * a,const void * b)61 optcmp(const void *a, const void *b)
62 {
63 struct grecs_opthelp const *ap = (struct grecs_opthelp const *)a;
64 struct grecs_opthelp const *bp = (struct grecs_opthelp const *)b;
65 const char *opta, *optb;
66 size_t alen, blen;
67
68 for (opta = ap->opt; *opta == '-'; opta++)
69 ;
70 alen = strcspn (opta, ",");
71
72 for (optb = bp->opt; *optb == '-'; optb++)
73 ;
74 blen = strcspn (optb, ",");
75
76 if (alen > blen)
77 blen = alen;
78
79 return strncmp (opta, optb, blen);
80 }
81
82 static void
sort_options(struct grecs_opthelp * opthelp,int start,int count)83 sort_options(struct grecs_opthelp *opthelp, int start, int count)
84 {
85 qsort(opthelp + start, count, sizeof(opthelp[0]), optcmp);
86 }
87
88 static int
sort_group(struct grecs_opthelp * opthelp,size_t optcount,int start)89 sort_group(struct grecs_opthelp *opthelp, size_t optcount, int start)
90 {
91 int i;
92
93 for (i = start; i < optcount && opthelp[i].opt; i++)
94 ;
95 sort_options(opthelp, start, i - start);
96 return i + 1;
97 }
98
99 static void
sort_opthelp(struct grecs_opthelp * opthelp,size_t optcount)100 sort_opthelp(struct grecs_opthelp *opthelp, size_t optcount)
101 {
102 int start;
103
104 for (start = 0; start < optcount; ) {
105 if (!opthelp[start].opt)
106 start = sort_group(opthelp, optcount, start + 1);
107 else
108 start = sort_group(opthelp, optcount, start);
109 }
110 }
111
112 #define ISEMPTY(s) ((s) == NULL || *(s) == 0)
113
114 void
grecs_print_help(struct grecs_proginfo * pinfo)115 grecs_print_help(struct grecs_proginfo *pinfo)
116 {
117 unsigned i;
118 int argsused = 0;
119 struct grecs_opthelp *opthelp;
120 size_t optcount;
121
122 printf("%s %s ",
123 _("Usage:"), pinfo->progname);
124 if (pinfo->subcmd)
125 printf("%s ", pinfo->subcmd[0]);
126 printf("[%s]... %s\n",
127 _("OPTION"),
128 !ISEMPTY(pinfo->args_doc) ? gettext(pinfo->args_doc) : "");
129 if (pinfo->subcmd && pinfo->subcmd[1]) {
130 char **p;
131
132 printf("%s: ", pinfo->subcmd[2] ? _("Aliases") : _("Alias"));
133 for (p = pinfo->subcmd + 1; *p; p++)
134 printf("%s%c", *p, p[1] ? ' ' : '\n');
135 }
136 if (!ISEMPTY(pinfo->docstring))
137 print_option_descr(gettext(pinfo->docstring), 0, RMARGIN);
138 putchar('\n');
139
140 opthelp = pinfo->opthelp;
141 optcount = pinfo->optcount;
142 sort_opthelp(opthelp, optcount);
143 for (i = 0; i < optcount; i++) {
144 unsigned n;
145 if (opthelp[i].opt) {
146 n = printf(" %s", opthelp[i].opt);
147 if (opthelp[i].arg) {
148 char *cb, *ce;
149 argsused = 1;
150 if (strlen(opthelp[i].opt) == 2) {
151 if (!opthelp[i].is_optional) {
152 putchar(' ');
153 n++;
154 }
155 } else {
156 putchar ('=');
157 n++;
158 }
159 if (opthelp[i].is_optional) {
160 cb = "[";
161 ce = "]";
162 } else
163 cb = ce = "";
164 n += printf("%s%s%s", cb,
165 gettext(opthelp[i].arg), ce);
166 }
167 if (n >= DESCRCOLUMN) {
168 putchar('\n');
169 n = 0;
170 }
171 indent(n, DESCRCOLUMN);
172 print_option_descr(gettext(opthelp[i].descr),
173 DESCRCOLUMN, RMARGIN);
174 } else {
175 if (i)
176 putchar('\n');
177 indent(0, GROUPCOLUMN);
178 print_option_descr(gettext(opthelp[i].descr),
179 GROUPCOLUMN, RMARGIN);
180 putchar('\n');
181 }
182 }
183
184 putchar('\n');
185 if (argsused) {
186 print_option_descr(_("Mandatory or optional arguments to "
187 "long options are also mandatory or "
188 "optional for any corresponding short "
189 "options."), 0, RMARGIN);
190 putchar('\n');
191 }
192
193 if (pinfo->print_help_hook)
194 pinfo->print_help_hook(stdout);
195
196 if (!ISEMPTY(pinfo->bug_address))
197 /* TRANSLATORS: The placeholder indicates the bug-reporting
198 address for this package. Please add _another line_ saying
199 "Report translation bugs to <...>\n" with the address for
200 translation bugs (typically your translation team's web or
201 email address). */
202 printf(_("Report bugs to %s.\n"), pinfo->bug_address);
203
204 if (!ISEMPTY(pinfo->url))
205 printf(_("%s home page: <%s>\n"), pinfo->package, pinfo->url);
206 if (!ISEMPTY(pinfo->epilogue))
207 printf("%s", gettext(pinfo->epilogue));
208 }
209
210 static int
cmpidx_short(const void * a,const void * b)211 cmpidx_short(const void *a, const void *b)
212 {
213 struct grecs_opthelp const **opta = (struct grecs_opthelp const **)a;
214 struct grecs_opthelp const **optb = (struct grecs_opthelp const **)b;
215
216 return (*opta)->opt[1] - (*optb)->opt[1];
217 }
218
219 #ifdef HAVE_GETOPT_LONG
220 static int
cmpidx_long(const void * a,const void * b)221 cmpidx_long(const void *a, const void *b)
222 {
223 struct grecs_opthelp const **ap = (struct grecs_opthelp const **)a;
224 struct grecs_opthelp const **bp = (struct grecs_opthelp const **)b;
225 char const *opta, *optb;
226 size_t lena, lenb;
227
228 if ((*ap)->opt[1] == '-')
229 opta = (*ap)->opt;
230 else
231 opta = (*ap)->opt + 4;
232 lena = strcspn(opta, ",");
233
234 if ((*bp)->opt[1] == '-')
235 optb = (*bp)->opt;
236 else
237 optb = (*bp)->opt + 4;
238 lenb = strcspn(optb, ",");
239 return strncmp(opta, optb, lena > lenb ? lenb : lena);
240 }
241 #endif
242
243 void
grecs_print_usage(struct grecs_proginfo * pinfo)244 grecs_print_usage(struct grecs_proginfo *pinfo)
245 {
246 unsigned i;
247 unsigned n;
248 char *buf;
249 size_t bufsize;
250 unsigned nidx;
251 struct grecs_opthelp **optidx;
252 struct grecs_opthelp *opthelp = pinfo->opthelp;
253 size_t optcount = pinfo->optcount;
254
255 #define FLUSH do { \
256 buf[n] = 0; \
257 printf("%s\n", buf); \
258 n = USAGECOLUMN; \
259 memset(buf, ' ', n); \
260 } while (0)
261 #define ADDC(c) \
262 do { if (n == RMARGIN) FLUSH; buf[n++] = c; } while (0)
263
264 optidx = grecs_calloc(optcount, sizeof(optidx[0]));
265
266 bufsize = RMARGIN + 1;
267 buf = grecs_malloc(bufsize);
268
269 n = snprintf(buf, bufsize, "%s %s ", _("Usage:"), pinfo->progname);
270 if (pinfo->subcmd)
271 n += snprintf(buf + n, bufsize - n, "%s ", pinfo->subcmd[0]);
272
273 /* Print a list of short options without arguments. */
274 for (i = nidx = 0; i < optcount; i++)
275 if (opthelp[i].opt &&
276 opthelp[i].descr &&
277 opthelp[i].opt[1] != '-' &&
278 opthelp[i].arg == NULL)
279 optidx[nidx++] = opthelp + i;
280
281 if (nidx) {
282 qsort(optidx, nidx, sizeof(optidx[0]), cmpidx_short);
283
284 ADDC('[');
285 ADDC('-');
286 for (i = 0; i < nidx; i++) {
287 ADDC(optidx[i]->opt[1]);
288 }
289 ADDC(']');
290 }
291
292 /* Print a list of short options with arguments. */
293 for (i = nidx = 0; i < optcount; i++) {
294 if (opthelp[i].opt &&
295 opthelp[i].descr &&
296 opthelp[i].opt[1] != '-' &&
297 opthelp[i].arg)
298 optidx[nidx++] = opthelp + i;
299 }
300
301 if (nidx) {
302 qsort(optidx, nidx, sizeof(optidx[0]), cmpidx_short);
303
304 for (i = 0; i < nidx; i++) {
305 struct grecs_opthelp *opt = optidx[i];
306 size_t len = 5 + strlen(opt->arg)
307 + (opt->is_optional ? 2 : 1);
308
309 if (n + len > RMARGIN)
310 FLUSH;
311 buf[n++] = ' ';
312 buf[n++] = '[';
313 buf[n++] = '-';
314 buf[n++] = opt->opt[1];
315 if (opt->is_optional) {
316 buf[n++] = '[';
317 strcpy(&buf[n], opt->arg);
318 n += strlen(opt->arg);
319 buf[n++] = ']';
320 } else {
321 buf[n++] = ' ';
322 strcpy(&buf[n], opt->arg);
323 n += strlen(opt->arg);
324 }
325 buf[n++] = ']';
326 }
327 }
328
329 #ifdef HAVE_GETOPT_LONG
330 /* Print a list of long options */
331 for (i = nidx = 0; i < optcount; i++) {
332 if (opthelp[i].opt && opthelp[i].descr
333 && (opthelp[i].opt[1] == '-' || opthelp[i].opt[2] == ','))
334 optidx[nidx++] = opthelp + i;
335 }
336
337 if (nidx) {
338 qsort (optidx, nidx, sizeof(optidx[0]), cmpidx_long);
339
340 for (i = 0; i < nidx; i++) {
341 struct grecs_opthelp *opt = optidx[i];
342 size_t len;
343 const char *longopt;
344
345 if (opt->opt[1] == '-')
346 longopt = opt->opt;
347 else if (opt->opt[2] == ',')
348 longopt = opt->opt + 4;
349 else
350 continue;
351
352 len = 3 + strlen(longopt)
353 + (opt->arg ? 1 + strlen(opt->arg)
354 + (opt->is_optional ? 2 : 0) : 0);
355 if (n + len > RMARGIN) {
356 FLUSH;
357 /* Make sure we have enough buffer space if
358 the string cannot be split */
359 if (n + len > bufsize) {
360 bufsize = n + len;
361 buf = grecs_realloc(buf, bufsize);
362 }
363 }
364 buf[n++] = ' ';
365 buf[n++] = '[';
366 strcpy(&buf[n], longopt);
367 n += strlen(longopt);
368 if (opt->arg) {
369 buf[n++] = '=';
370 if (opt->is_optional) {
371 buf[n++] = '[';
372 strcpy(&buf[n], opt->arg);
373 n += strlen(opt->arg);
374 buf[n++] = ']';
375 } else {
376 strcpy(&buf[n], opt->arg);
377 n += strlen(opt->arg);
378 }
379 }
380 buf[n++] = ']';
381 }
382 }
383 #endif
384 /* Print argument list */
385 if (pinfo->args_doc) {
386 size_t len = strlen(pinfo->args_doc) + 1;
387 if (n + len <= RMARGIN) {
388 buf[n++] = ' ';
389 strcpy(buf + n, pinfo->args_doc);
390 n += len;
391 } else {
392 struct wordsplit ws;
393
394 if (wordsplit(pinfo->args_doc, &ws,
395 WRDSF_SHOWERR |
396 WRDSF_NOVAR |
397 WRDSF_NOCMD |
398 WRDSF_QUOTE |
399 WRDSF_SQUEEZE_DELIMS))
400 abort();
401
402 for (i = 0; i < ws.ws_wordc; i++) {
403 len = strlen(ws.ws_wordv[i]) + 1;
404 if (n + len > RMARGIN) {
405 FLUSH;
406 /* Make sure we have enough buffer
407 space if the string cannot be
408 split */
409 if (n + len > bufsize) {
410 bufsize = n + len;
411 buf = grecs_realloc(buf,
412 bufsize);
413 }
414 }
415 buf[n++] = ' ';
416 strcpy(buf + n, ws.ws_wordv[i]);
417 n += len;
418 }
419 }
420 }
421
422 FLUSH;
423
424 if (pinfo->subcmd && pinfo->subcmd[1]) {
425 char **p;
426
427 printf("%s: %s", pinfo->subcmd[2] ? _("Aliases") : _("Alias"),
428 pinfo->progname);
429 for (p = pinfo->subcmd + 1; *p; p++)
430 printf(" %s", *p);
431 putchar('\n');
432 }
433
434 free(optidx);
435 free(buf);
436 }
437
438 const char version_etc_copyright[] =
439 /* Do *not* mark this string for translation. First %s is a copyright
440 symbol suitable for this locale, and second %s are the copyright
441 years. */
442 "Copyright %s %s %s";
443
444 void
grecs_print_version_only(struct grecs_proginfo * pinfo,FILE * stream)445 grecs_print_version_only(struct grecs_proginfo *pinfo, FILE *stream)
446 {
447 fprintf(stream, "%s", pinfo->progname);
448 if (!ISEMPTY(pinfo->package))
449 fprintf(stream, " (%s)", pinfo->package);
450 if (!ISEMPTY(pinfo->version))
451 fprintf(stream, " %s", pinfo->version);
452 fputc('\n', stream);
453
454 /* TRANSLATORS: Translate "(C)" to the copyright symbol
455 (C-in-a-circle), if this symbol is available in the user's
456 locale. Otherwise, do not translate "(C)"; leave it as-is. */
457 fprintf(stream, version_etc_copyright, _("(C)"),
458 ISEMPTY(pinfo->copyright_year) ?
459 "2012" : pinfo->copyright_year,
460 ISEMPTY(pinfo->copyright_holder) ?
461 "Free Software Foundation, inc." : pinfo->copyright_holder);
462 fputc('\n', stream);
463 }
464
465 static const char gplv3[] =
466 N_("License GPLv3+: GNU GPL version 3 or later "
467 "<http://gnu.org/licenses/gpl.html>\n"
468 "This is free software: you are free to change and redistribute it.\n"
469 "There is NO WARRANTY, to the extent permitted by law.\n\n");
470
471 void
grecs_print_version(struct grecs_proginfo * pinfo,FILE * stream)472 grecs_print_version(struct grecs_proginfo *pinfo, FILE *stream)
473 {
474 grecs_print_version_only(pinfo, stream);
475 fputs(gettext(ISEMPTY(pinfo->license) ?
476 gplv3 : pinfo->license), stream);
477 if (pinfo->print_version_hook)
478 pinfo->print_version_hook(stream);
479
480 if (pinfo->authors) {
481 int i;
482 unsigned width;
483 const char *written_by = _("Written by ");
484 /* TRANSLATORS: This string is used as a delimiter between
485 authors' names as in:
486
487 Written by Winnie the Pooh, Piglet ...
488 */
489 const char *middle_delim = _(", ");
490 /* TRANSLATORS: This string acts as a delimiter before the
491 last author's names, e.g.:
492
493 Written by Winnie the Pooh, Piglet and Christopher Robin.
494 */
495 const char *final_delim = _(" and ");
496
497 width = strlen(written_by);
498 fputs(written_by, stream);
499 for (i = 0; ; ) {
500 const char *author = pinfo->authors[i++];
501 size_t len = strlen(author);
502 const char *delim = NULL;
503
504 if (pinfo->authors[i]) {
505 delim = pinfo->authors[i+1] ?
506 middle_delim : final_delim;
507 len += strlen (delim);
508 } else
509 len++;
510 if (width + len > RMARGIN) {
511 fputc('\n', stream);
512 width = 0;
513 }
514 fputs(author, stream);
515 width += len;
516 if (delim)
517 fputs(delim, stream);
518 else
519 break;
520 }
521 fputc('.', stream);
522 fputc('\n', stream);
523 }
524 }
525