/* grecs - Gray's Extensible Configuration System
Copyright (C) 2007-2022 Sergey Poznyakoff
Grecs is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
Grecs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with Grecs. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include "grecs.h"
#include "grecs/opt.h"
#include "wordsplit.h"
static void
indent(size_t start, size_t col)
{
for (; start < col; start++)
putchar(' ');
}
static void
print_option_descr(const char *descr, size_t lmargin, size_t rmargin)
{
while (*descr) {
int i, s = 0;
size_t width = rmargin - lmargin;
for (i = 0; ; i++) {
if (descr[i] == 0 || descr[i] == ' ' ||
descr[i] == '\t') {
if (i > width)
break;
s = i;
if (descr[i] == 0)
break;
}
}
printf("%*.*s\n", s, s, descr);
descr += s;
if (*descr) {
indent(0, lmargin);
descr++;
}
}
}
static int
optcmp(const void *a, const void *b)
{
struct grecs_opthelp const *ap = (struct grecs_opthelp const *)a;
struct grecs_opthelp const *bp = (struct grecs_opthelp const *)b;
const char *opta, *optb;
size_t alen, blen;
for (opta = ap->opt; *opta == '-'; opta++)
;
alen = strcspn (opta, ",");
for (optb = bp->opt; *optb == '-'; optb++)
;
blen = strcspn (optb, ",");
if (alen > blen)
blen = alen;
return strncmp (opta, optb, blen);
}
static void
sort_options(struct grecs_opthelp *opthelp, int start, int count)
{
qsort(opthelp + start, count, sizeof(opthelp[0]), optcmp);
}
static int
sort_group(struct grecs_opthelp *opthelp, size_t optcount, int start)
{
int i;
for (i = start; i < optcount && opthelp[i].opt; i++)
;
sort_options(opthelp, start, i - start);
return i + 1;
}
static void
sort_opthelp(struct grecs_opthelp *opthelp, size_t optcount)
{
int start;
for (start = 0; start < optcount; ) {
if (!opthelp[start].opt)
start = sort_group(opthelp, optcount, start + 1);
else
start = sort_group(opthelp, optcount, start);
}
}
#define ISEMPTY(s) ((s) == NULL || *(s) == 0)
void
grecs_print_help(struct grecs_proginfo *pinfo)
{
unsigned i;
int argsused = 0;
struct grecs_opthelp *opthelp;
size_t optcount;
printf("%s %s ",
_("Usage:"), pinfo->progname);
if (pinfo->subcmd)
printf("%s ", pinfo->subcmd[0]);
printf("[%s]... %s\n",
_("OPTION"),
!ISEMPTY(pinfo->args_doc) ? gettext(pinfo->args_doc) : "");
if (pinfo->subcmd && pinfo->subcmd[1]) {
char **p;
printf("%s: ", pinfo->subcmd[2] ? _("Aliases") : _("Alias"));
for (p = pinfo->subcmd + 1; *p; p++)
printf("%s%c", *p, p[1] ? ' ' : '\n');
}
if (!ISEMPTY(pinfo->docstring))
print_option_descr(gettext(pinfo->docstring), 0, RMARGIN);
putchar('\n');
opthelp = pinfo->opthelp;
optcount = pinfo->optcount;
sort_opthelp(opthelp, optcount);
for (i = 0; i < optcount; i++) {
unsigned n;
if (opthelp[i].opt) {
n = printf(" %s", opthelp[i].opt);
if (opthelp[i].arg) {
char *cb, *ce;
argsused = 1;
if (strlen(opthelp[i].opt) == 2) {
if (!opthelp[i].is_optional) {
putchar(' ');
n++;
}
} else {
putchar ('=');
n++;
}
if (opthelp[i].is_optional) {
cb = "[";
ce = "]";
} else
cb = ce = "";
n += printf("%s%s%s", cb,
gettext(opthelp[i].arg), ce);
}
if (n >= DESCRCOLUMN) {
putchar('\n');
n = 0;
}
indent(n, DESCRCOLUMN);
print_option_descr(gettext(opthelp[i].descr),
DESCRCOLUMN, RMARGIN);
} else {
if (i)
putchar('\n');
indent(0, GROUPCOLUMN);
print_option_descr(gettext(opthelp[i].descr),
GROUPCOLUMN, RMARGIN);
putchar('\n');
}
}
putchar('\n');
if (argsused) {
print_option_descr(_("Mandatory or optional arguments to "
"long options are also mandatory or "
"optional for any corresponding short "
"options."), 0, RMARGIN);
putchar('\n');
}
if (pinfo->print_help_hook)
pinfo->print_help_hook(stdout);
if (!ISEMPTY(pinfo->bug_address))
/* TRANSLATORS: The placeholder indicates the bug-reporting
address for this package. Please add _another line_ saying
"Report translation bugs to <...>\n" with the address for
translation bugs (typically your translation team's web or
email address). */
printf(_("Report bugs to %s.\n"), pinfo->bug_address);
if (!ISEMPTY(pinfo->url))
printf(_("%s home page: <%s>\n"), pinfo->package, pinfo->url);
if (!ISEMPTY(pinfo->epilogue))
printf("%s", gettext(pinfo->epilogue));
}
static int
cmpidx_short(const void *a, const void *b)
{
struct grecs_opthelp const **opta = (struct grecs_opthelp const **)a;
struct grecs_opthelp const **optb = (struct grecs_opthelp const **)b;
return (*opta)->opt[1] - (*optb)->opt[1];
}
#ifdef HAVE_GETOPT_LONG
static int
cmpidx_long(const void *a, const void *b)
{
struct grecs_opthelp const **ap = (struct grecs_opthelp const **)a;
struct grecs_opthelp const **bp = (struct grecs_opthelp const **)b;
char const *opta, *optb;
size_t lena, lenb;
if ((*ap)->opt[1] == '-')
opta = (*ap)->opt;
else
opta = (*ap)->opt + 4;
lena = strcspn(opta, ",");
if ((*bp)->opt[1] == '-')
optb = (*bp)->opt;
else
optb = (*bp)->opt + 4;
lenb = strcspn(optb, ",");
return strncmp(opta, optb, lena > lenb ? lenb : lena);
}
#endif
void
grecs_print_usage(struct grecs_proginfo *pinfo)
{
unsigned i;
unsigned n;
char *buf;
size_t bufsize;
unsigned nidx;
struct grecs_opthelp **optidx;
struct grecs_opthelp *opthelp = pinfo->opthelp;
size_t optcount = pinfo->optcount;
#define FLUSH do { \
buf[n] = 0; \
printf("%s\n", buf); \
n = USAGECOLUMN; \
memset(buf, ' ', n); \
} while (0)
#define ADDC(c) \
do { if (n == RMARGIN) FLUSH; buf[n++] = c; } while (0)
optidx = grecs_calloc(optcount, sizeof(optidx[0]));
bufsize = RMARGIN + 1;
buf = grecs_malloc(bufsize);
n = snprintf(buf, bufsize, "%s %s ", _("Usage:"), pinfo->progname);
if (pinfo->subcmd)
n += snprintf(buf + n, bufsize - n, "%s ", pinfo->subcmd[0]);
/* Print a list of short options without arguments. */
for (i = nidx = 0; i < optcount; i++)
if (opthelp[i].opt &&
opthelp[i].descr &&
opthelp[i].opt[1] != '-' &&
opthelp[i].arg == NULL)
optidx[nidx++] = opthelp + i;
if (nidx) {
qsort(optidx, nidx, sizeof(optidx[0]), cmpidx_short);
ADDC('[');
ADDC('-');
for (i = 0; i < nidx; i++) {
ADDC(optidx[i]->opt[1]);
}
ADDC(']');
}
/* Print a list of short options with arguments. */
for (i = nidx = 0; i < optcount; i++) {
if (opthelp[i].opt &&
opthelp[i].descr &&
opthelp[i].opt[1] != '-' &&
opthelp[i].arg)
optidx[nidx++] = opthelp + i;
}
if (nidx) {
qsort(optidx, nidx, sizeof(optidx[0]), cmpidx_short);
for (i = 0; i < nidx; i++) {
struct grecs_opthelp *opt = optidx[i];
size_t len = 5 + strlen(opt->arg)
+ (opt->is_optional ? 2 : 1);
if (n + len > RMARGIN)
FLUSH;
buf[n++] = ' ';
buf[n++] = '[';
buf[n++] = '-';
buf[n++] = opt->opt[1];
if (opt->is_optional) {
buf[n++] = '[';
strcpy(&buf[n], opt->arg);
n += strlen(opt->arg);
buf[n++] = ']';
} else {
buf[n++] = ' ';
strcpy(&buf[n], opt->arg);
n += strlen(opt->arg);
}
buf[n++] = ']';
}
}
#ifdef HAVE_GETOPT_LONG
/* Print a list of long options */
for (i = nidx = 0; i < optcount; i++) {
if (opthelp[i].opt && opthelp[i].descr
&& (opthelp[i].opt[1] == '-' || opthelp[i].opt[2] == ','))
optidx[nidx++] = opthelp + i;
}
if (nidx) {
qsort (optidx, nidx, sizeof(optidx[0]), cmpidx_long);
for (i = 0; i < nidx; i++) {
struct grecs_opthelp *opt = optidx[i];
size_t len;
const char *longopt;
if (opt->opt[1] == '-')
longopt = opt->opt;
else if (opt->opt[2] == ',')
longopt = opt->opt + 4;
else
continue;
len = 3 + strlen(longopt)
+ (opt->arg ? 1 + strlen(opt->arg)
+ (opt->is_optional ? 2 : 0) : 0);
if (n + len > RMARGIN) {
FLUSH;
/* Make sure we have enough buffer space if
the string cannot be split */
if (n + len > bufsize) {
bufsize = n + len;
buf = grecs_realloc(buf, bufsize);
}
}
buf[n++] = ' ';
buf[n++] = '[';
strcpy(&buf[n], longopt);
n += strlen(longopt);
if (opt->arg) {
buf[n++] = '=';
if (opt->is_optional) {
buf[n++] = '[';
strcpy(&buf[n], opt->arg);
n += strlen(opt->arg);
buf[n++] = ']';
} else {
strcpy(&buf[n], opt->arg);
n += strlen(opt->arg);
}
}
buf[n++] = ']';
}
}
#endif
/* Print argument list */
if (pinfo->args_doc) {
size_t len = strlen(pinfo->args_doc) + 1;
if (n + len <= RMARGIN) {
buf[n++] = ' ';
strcpy(buf + n, pinfo->args_doc);
n += len;
} else {
struct wordsplit ws;
if (wordsplit(pinfo->args_doc, &ws,
WRDSF_SHOWERR |
WRDSF_NOVAR |
WRDSF_NOCMD |
WRDSF_QUOTE |
WRDSF_SQUEEZE_DELIMS))
abort();
for (i = 0; i < ws.ws_wordc; i++) {
len = strlen(ws.ws_wordv[i]) + 1;
if (n + len > RMARGIN) {
FLUSH;
/* Make sure we have enough buffer
space if the string cannot be
split */
if (n + len > bufsize) {
bufsize = n + len;
buf = grecs_realloc(buf,
bufsize);
}
}
buf[n++] = ' ';
strcpy(buf + n, ws.ws_wordv[i]);
n += len;
}
}
}
FLUSH;
if (pinfo->subcmd && pinfo->subcmd[1]) {
char **p;
printf("%s: %s", pinfo->subcmd[2] ? _("Aliases") : _("Alias"),
pinfo->progname);
for (p = pinfo->subcmd + 1; *p; p++)
printf(" %s", *p);
putchar('\n');
}
free(optidx);
free(buf);
}
const char version_etc_copyright[] =
/* Do *not* mark this string for translation. First %s is a copyright
symbol suitable for this locale, and second %s are the copyright
years. */
"Copyright %s %s %s";
void
grecs_print_version_only(struct grecs_proginfo *pinfo, FILE *stream)
{
fprintf(stream, "%s", pinfo->progname);
if (!ISEMPTY(pinfo->package))
fprintf(stream, " (%s)", pinfo->package);
if (!ISEMPTY(pinfo->version))
fprintf(stream, " %s", pinfo->version);
fputc('\n', stream);
/* TRANSLATORS: Translate "(C)" to the copyright symbol
(C-in-a-circle), if this symbol is available in the user's
locale. Otherwise, do not translate "(C)"; leave it as-is. */
fprintf(stream, version_etc_copyright, _("(C)"),
ISEMPTY(pinfo->copyright_year) ?
"2012" : pinfo->copyright_year,
ISEMPTY(pinfo->copyright_holder) ?
"Free Software Foundation, inc." : pinfo->copyright_holder);
fputc('\n', stream);
}
static const char gplv3[] =
N_("License GPLv3+: GNU GPL version 3 or later "
"\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n\n");
void
grecs_print_version(struct grecs_proginfo *pinfo, FILE *stream)
{
grecs_print_version_only(pinfo, stream);
fputs(gettext(ISEMPTY(pinfo->license) ?
gplv3 : pinfo->license), stream);
if (pinfo->print_version_hook)
pinfo->print_version_hook(stream);
if (pinfo->authors) {
int i;
unsigned width;
const char *written_by = _("Written by ");
/* TRANSLATORS: This string is used as a delimiter between
authors' names as in:
Written by Winnie the Pooh, Piglet ...
*/
const char *middle_delim = _(", ");
/* TRANSLATORS: This string acts as a delimiter before the
last author's names, e.g.:
Written by Winnie the Pooh, Piglet and Christopher Robin.
*/
const char *final_delim = _(" and ");
width = strlen(written_by);
fputs(written_by, stream);
for (i = 0; ; ) {
const char *author = pinfo->authors[i++];
size_t len = strlen(author);
const char *delim = NULL;
if (pinfo->authors[i]) {
delim = pinfo->authors[i+1] ?
middle_delim : final_delim;
len += strlen (delim);
} else
len++;
if (width + len > RMARGIN) {
fputc('\n', stream);
width = 0;
}
fputs(author, stream);
width += len;
if (delim)
fputs(delim, stream);
else
break;
}
fputc('.', stream);
fputc('\n', stream);
}
}