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