Commit 53eb6813 53eb68133eeede20c49445cd4ff58ad6e4becc07 by Sergey Poznyakoff

Support for single-dash long options (as in MH) and negation for boolean options.

* include/mailutils/opt.h (MU_PARSEOPT_SINGLE_DASH)
(MU_PARSEOPT_NEGATION): New flags.
(mu_parseopt) <po_negation, po_long_opt_start>: New fields.
(mu_option_describe_options): Change signature.
* libmailutils/opt/help.c (init_usage_vars): Initialize
long_opt_col depending on the value of MU_PARSEOPT_SINGLE_DASH
bit.
(print_option): Handle single-dash long options and boolean
options with negations.
(option_summary): Likewise.
(mu_option_describe_options): Take struct mu_parseopt *
as its second parameter.
* libmailutils/opt/opt.c (find_long_option): Handle
boolean options with negations.  Return also a pointer
to the mu_option describing the originally used option
(as opposed to the canonical one returned by the function).
(parse): Handle single-dash long options.
(parseopt_init): Initialize po_negation and po_long_opt_start.
* libmailutils/tests/parseopt.c (parseopt_param): Handle
MU_PARSEOPT_SINGLE_DASH and MU_PARSEOPT_NEGATION.
1 parent fa7ac605
......@@ -103,13 +103,17 @@ struct mu_option_cache
#define MU_PARSEOPT_DATA 0x00400000
#define MU_PARSEOPT_VERSION_HOOK 0x00800000
#define MU_PARSEOPT_PROG_DOC_HOOK 0x01000000
/* Long options start with single dash. Disables recognition of traditional
short options */
#define MU_PARSEOPT_SINGLE_DASH 0x02000000
/* Negation prefix is set */
#define MU_PARSEOPT_NEGATION 0x04000000
/* Reuse mu_parseopt struct initialized previously */
#define MU_PARSEOPT_REUSE 0x80000000
/* Mask for immutable flag bits */
#define MU_PARSEOPT_IMMUTABLE_MASK 0xFFFFF000
struct mu_parseopt
{
/* Input data: */
......@@ -119,6 +123,7 @@ struct mu_parseopt
struct mu_option **po_optv; /* Array of ptrs to option structures */
int po_flags;
char *po_negation; /* Negation prefix for boolean options */
void *po_data; /* Call-specific data */
int po_exit_error; /* Exit on error with this code */
......@@ -145,6 +150,9 @@ struct mu_parseopt
/* Auxiliary data */
char *po_cur; /* Points to the next character */
int po_chr; /* Single-char option */
char *po_long_opt_start; /* Character sequence that starts
long option */
/* The following two keep the position of the first non-optional argument
and the number of contiguous non-optional arguments after it.
......@@ -161,6 +169,7 @@ struct mu_parseopt
unsigned po_permuted:1; /* Whether the arguments were permuted */
};
int mu_parseopt (struct mu_parseopt *p,
int argc, char **argv, struct mu_option **optv,
int flags);
......@@ -171,8 +180,7 @@ void mu_parseopt_free (struct mu_parseopt *p);
unsigned mu_parseopt_getcolumn (const char *name);
void mu_option_describe_options (mu_stream_t str,
struct mu_option **optbuf, size_t optcnt);
void mu_option_describe_options (mu_stream_t str, struct mu_parseopt *p);
void mu_program_help (struct mu_parseopt *p, mu_stream_t str);
void mu_program_usage (struct mu_parseopt *p, int optsummary, mu_stream_t str);
void mu_program_version (struct mu_parseopt *po, mu_stream_t str);
......@@ -180,4 +188,6 @@ void mu_program_version (struct mu_parseopt *po, mu_stream_t str);
void mu_option_set_value (struct mu_parseopt *po, struct mu_option *opt,
char const *arg);
int mu_option_possible_negation (struct mu_parseopt *po, struct mu_option *opt);
#endif
......
......@@ -30,7 +30,7 @@
#include <mailutils/stream.h>
unsigned short_opt_col = 2;
unsigned long_opt_col = 6;
unsigned long_opt_col; /* Initialized in init_usage_vars */
/*FIXME: doc_opt_col? */
unsigned header_col = 1;
unsigned opt_doc_col = 29;
......@@ -170,6 +170,11 @@ init_usage_vars (struct mu_parseopt *po)
struct mu_wordsplit ws;
size_t i;
if (po->po_flags & MU_PARSEOPT_SINGLE_DASH)
long_opt_col = 2;
else
long_opt_col = 6;
fmt = getenv ("ARGP_HELP_FMT");
if (!fmt)
return;
......@@ -223,11 +228,10 @@ print_opt_arg (mu_stream_t str, struct mu_option *opt, int delim)
}
static size_t
print_option (mu_stream_t str,
struct mu_option **optbuf, size_t optcnt, size_t num,
print_option (mu_stream_t str, struct mu_parseopt *po, size_t num,
int *argsused)
{
struct mu_option *opt = optbuf[num];
struct mu_option *opt = po->po_optv[num];
size_t next, i;
int delim;
int first_option = 1;
......@@ -240,38 +244,47 @@ print_option (mu_stream_t str,
if (opt->opt_doc[0])
{
set_margin (str, header_col);
mu_stream_printf (str, "%s", gettext (opt->opt_doc));
mu_stream_printf (str, "%s\n", gettext (opt->opt_doc));
}
return num + 1;
}
/* count aliases */
for (next = num + 1;
next < optcnt && optbuf[next]->opt_flags & MU_OPTION_ALIAS;
next < po->po_optc && po->po_optv[next]->opt_flags & MU_OPTION_ALIAS;
next++);
if (opt->opt_flags & MU_OPTION_HIDDEN)
return next;
set_margin (str, short_opt_col);
for (i = num; i < next; i++)
if (po->po_flags & MU_PARSEOPT_SINGLE_DASH)
{
if (!opt->opt_long)
return num + 1; /* Ignore erroneous option */
set_margin (str, long_opt_col);
}
else
{
if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]))
set_margin (str, short_opt_col);
for (i = num; i < next; i++)
{
if (first_option)
first_option = 0;
else
mu_stream_printf (str, ", ");
mu_stream_printf (str, "-%c", optbuf[i]->opt_short);
delim = ' ';
if (opt->opt_arg && dup_args)
print_opt_arg (str, opt, delim);
if (MU_OPTION_IS_VALID_SHORT_OPTION (po->po_optv[i]))
{
if (first_option)
first_option = 0;
else
mu_stream_printf (str, ", ");
mu_stream_printf (str, "-%c", po->po_optv[i]->opt_short);
delim = ' ';
if (opt->opt_arg && dup_args)
print_opt_arg (str, opt, delim);
}
}
}
for (i = num; i < next; i++)
{
if (MU_OPTION_IS_VALID_LONG_OPTION (optbuf[i]))
if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i]))
{
if (first_option)
first_option = 0;
......@@ -287,8 +300,12 @@ print_option (mu_stream_t str,
first_long_option = 0;
}
mu_stream_printf (str, "--%s", optbuf[i]->opt_long);
delim = '=';
mu_stream_printf (str, "%s", po->po_long_opt_start);
if (mu_option_possible_negation (po, po->po_optv[i]))
mu_stream_printf (str, "[%s]", po->po_negation);
mu_stream_printf (str, "%s", po->po_optv[i]->opt_long);
delim = ((po->po_flags & MU_PARSEOPT_SINGLE_DASH)
&& !(opt->opt_flags & MU_OPTION_ARG_OPTIONAL)) ? ' ' : '=';
if (opt->opt_arg && dup_args)
print_opt_arg (str, opt, delim);
}
......@@ -308,14 +325,13 @@ print_option (mu_stream_t str,
}
void
mu_option_describe_options (mu_stream_t str,
struct mu_option **optbuf, size_t optcnt)
mu_option_describe_options (mu_stream_t str, struct mu_parseopt *po)
{
unsigned i;
int argsused = 0;
for (i = 0; i < optcnt; )
i = print_option (str, optbuf, optcnt, i, &argsused);
for (i = 0; i < po->po_optc; )
i = print_option (str, po, i, &argsused);
mu_stream_printf (str, "\n");
if (argsused && dup_args_note)
......@@ -334,7 +350,7 @@ mu_program_help (struct mu_parseopt *po, mu_stream_t outstr)
{
mu_stream_t str;
int rc;
init_usage_vars (po);
rc = mu_wordwrap_stream_create (&str, outstr, 0, rmargin);
......@@ -358,7 +374,7 @@ mu_program_help (struct mu_parseopt *po, mu_stream_t outstr)
mu_stream_printf (str, "\n");
}
mu_option_describe_options (str, po->po_optv, po->po_optc);
mu_option_describe_options (str, po);
if (po->po_help_hook)
{
......@@ -432,41 +448,44 @@ option_summary (struct mu_parseopt *po, mu_stream_t str)
idxbuf = mu_calloc (optcnt, sizeof (idxbuf[0]));
/* Print a list of short options without arguments. */
for (i = nidx = 0; i < optcnt; i++)
if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]) && !optbuf[i]->opt_arg)
idxbuf[nidx++] = i;
if (nidx)
if (!(po->po_flags & MU_PARSEOPT_SINGLE_DASH))
{
qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short);
mu_stream_printf (str, "[-");
for (i = 0; i < nidx; i++)
/* Print a list of short options without arguments. */
for (i = nidx = 0; i < optcnt; i++)
if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]) && !optbuf[i]->opt_arg)
idxbuf[nidx++] = i;
if (nidx)
{
mu_stream_printf (str, "%c", optbuf[idxbuf[i]]->opt_short);
qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short);
mu_stream_printf (str, "[-");
for (i = 0; i < nidx; i++)
{
mu_stream_printf (str, "%c", optbuf[idxbuf[i]]->opt_short);
}
mu_stream_printf (str, "%c", ']');
}
mu_stream_printf (str, "%c", ']');
}
/* Print a list of short options with arguments. */
for (i = nidx = 0; i < optcnt; i++)
{
if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]) && optbuf[i]->opt_arg)
idxbuf[nidx++] = i;
}
/* Print a list of short options with arguments. */
for (i = nidx = 0; i < optcnt; i++)
{
if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]) && optbuf[i]->opt_arg)
idxbuf[nidx++] = i;
}
if (nidx)
{
qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short);
for (i = 0; i < nidx; i++)
if (nidx)
{
struct mu_option *opt = optbuf[idxbuf[i]];
const char *arg = gettext (opt->opt_arg);
if (opt->opt_flags & MU_OPTION_ARG_OPTIONAL)
mu_stream_printf (str, " [-%c[%s]]", opt->opt_short, arg);
else
mu_stream_printf (str, " [-%c %s]", opt->opt_short, arg);
qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short);
for (i = 0; i < nidx; i++)
{
struct mu_option *opt = optbuf[idxbuf[i]];
const char *arg = gettext (opt->opt_arg);
if (opt->opt_flags & MU_OPTION_ARG_OPTIONAL)
mu_stream_printf (str, " [-%c[%s]]", opt->opt_short, arg);
else
mu_stream_printf (str, " [-%c %s]", opt->opt_short, arg);
}
}
}
......@@ -486,11 +505,17 @@ option_summary (struct mu_parseopt *po, mu_stream_t str)
struct mu_option *opt = optbuf[idxbuf[i]];
const char *arg = opt->opt_arg ? gettext (opt->opt_arg) : NULL;
mu_stream_printf (str, " [--%s", opt->opt_long);
mu_stream_printf (str, " [%s", po->po_long_opt_start);
if (mu_option_possible_negation (po, opt))
mu_stream_printf (str, "[%s]", po->po_negation);
mu_stream_printf (str, "%s", opt->opt_long);
if (opt->opt_arg)
{
if (opt->opt_flags & MU_OPTION_ARG_OPTIONAL)
mu_stream_printf (str, "[=%s]", arg);
else if (po->po_flags & MU_PARSEOPT_SINGLE_DASH)
mu_stream_printf (str, " %s", arg);
else
mu_stream_printf (str, "=%s", arg);
}
......
......@@ -193,58 +193,94 @@ find_short_option (struct mu_parseopt *po, int chr)
it in *ARGPTR. */
struct mu_option *
find_long_option (struct mu_parseopt *po, char const *optstr,
char **argptr)
struct mu_option **used_opt_ptr,
char **used_value,
char **value)
{
size_t i;
size_t optlen;
size_t ind;
int found = 0;
size_t optlen; /* Length of the option in optstr */
int found = 0; /* 1 if the match was found, 2 if option is ambiguous */
int neglen; /* Length of the negation prefix, if any */
int neg = 0; /* 1 if a boolean option is negated */
struct mu_option *ret_opt = NULL;
struct mu_option *used_opt;
optlen = strcspn (optstr, "=");
if (po->po_negation)
neglen = strlen (po->po_negation);
for (i = 0; i < po->po_optc; i++)
{
if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i])
&& optlen <= strlen (po->po_optv[i]->opt_long)
&& memcmp (po->po_optv[i]->opt_long, optstr, optlen) == 0)
if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i]))
{
switch (found)
size_t len = strlen (po->po_optv[i]->opt_long);
struct mu_option *opt = option_unalias (po, i);
if ((optlen <= len
&& memcmp (po->po_optv[i]->opt_long, optstr, optlen) == 0)
|| (neg = (mu_option_possible_negation (po, opt)
&& optlen <= neglen + len
&& memcmp (optstr, po->po_negation, neglen) == 0
&& memcmp (optstr + neglen, po->po_optv[i]->opt_long,
optlen - neglen) == 0)))
{
case 0:
ind = i;
found++;
if (optlen == strlen (po->po_optv[i]->opt_long))
i = po->po_optc - 1; /* exact match: break the loop */
break;
case 1:
if (option_unalias (po, i) == option_unalias (po, ind))
continue;
if (po->po_flags & MU_PARSEOPT_IGNORE_ERRORS)
return NULL;
mu_parseopt_error (po,
_("option '--%*.*s' is ambiguous; possibilities:"),
optlen, optlen, optstr);
fprintf (stderr, "--%s\n", po->po_optv[ind]->opt_long);
found++;
case 2:
fprintf (stderr, "--%s\n", po->po_optv[i]->opt_long);
switch (found)
{
case 0:
used_opt = po->po_optv[i];
ret_opt = opt;
found++;
if (optlen == len || (neg && optlen == neglen + len))
i = po->po_optc - 1; /* exact match: break the loop */
break;
case 1:
if (opt == ret_opt)
continue;
if (po->po_flags & MU_PARSEOPT_IGNORE_ERRORS)
return NULL;
mu_parseopt_error (po,
_("option '%s%*.*s' is ambiguous; possibilities:"),
po->po_long_opt_start,
optlen, optlen, optstr);
fprintf (stderr, "%s%s%s\n",
po->po_long_opt_start,
neg ? po->po_negation : "",
used_opt->opt_long);
found++;
case 2:
fprintf (stderr, "%s%s%s\n",
po->po_long_opt_start,
neg ? po->po_negation : "",
po->po_optv[i]->opt_long);
}
}
}
}
switch (found)
{
case 0:
mu_parseopt_error (po, _("unrecognized option '--%s'"), optstr);
mu_parseopt_error (po, _("unrecognized option '%s%s'"),
po->po_long_opt_start, optstr);
break;
case 1:
*used_opt_ptr = used_opt;
if (optstr[optlen])
++optlen;
*argptr = (char *)(optstr + optlen);
return option_unalias (po, ind);
*used_value = (char *)(optstr + optlen);
if (ret_opt->opt_type == mu_c_bool)
{
if (neg)
*value = "0";
else
*value = "1";
}
else
*value = NULL;
return ret_opt;
case 2:
break;
......@@ -313,7 +349,36 @@ next_opt (struct mu_parseopt *po)
if (!po->po_cur)
return 1;
if (po->po_cur[0] == '-' && po->po_cur[1])
break;
{
if (*++po->po_cur == '-')
{
if (*++po->po_cur == 0)
{
/* End of options */
permute (po);
++po->po_ind;
return 1;
}
if (po->po_flags & MU_PARSEOPT_SINGLE_DASH)
/* a non-optional argument */;
else
{
/* It's a long option */
po->po_chr = 0;
return 0;
}
}
else if (po->po_flags & MU_PARSEOPT_SINGLE_DASH)
{
/* Assume single-dash long option */
po->po_chr = 0;
return 0;
}
else
break;
}
if (!(po->po_flags & MU_PARSEOPT_IN_ORDER))
{
if (!po->po_permuted && po->po_arg_count == 0)
......@@ -324,21 +389,6 @@ next_opt (struct mu_parseopt *po)
else
return 1;
}
if (*++po->po_cur == '-')
{
if (*++po->po_cur == 0)
{
/* End of options */
permute (po);
++po->po_ind;
return 1;
}
/* It's a long option */
po->po_chr = 0;
return 0;
}
}
po->po_chr = *po->po_cur++;
......@@ -386,18 +436,18 @@ parse (struct mu_parseopt *po)
while (next_opt (po) == 0)
{
struct mu_option *opt;
char *long_opt;
struct mu_option *opt, *uopt;
char *value;
if (po->po_chr)
{
opt = find_short_option (po, po->po_chr);
long_opt = NULL;
uopt = NULL;
value = NULL;
}
else
{
long_opt = po->po_cur;
opt = find_long_option (po, long_opt, &po->po_cur);
opt = find_long_option (po, po->po_cur, &uopt, &po->po_cur, &value);
}
if (opt)
......@@ -417,14 +467,14 @@ parse (struct mu_parseopt *po)
arg = po->po_argv[po->po_ind++];
else
{
if (long_opt)
if (uopt)
mu_parseopt_error (po,
_("option '--%s' requires an argument"),
long_opt);
_("option '%s%s' requires an argument"),
po->po_long_opt_start, uopt->opt_long);
else
mu_parseopt_error (po,
_("option '-%c' requires an argument"),
po->po_chr);
po->po_chr);
po->po_opterr = po->po_ind;
if (po->po_flags & MU_PARSEOPT_NO_ERREXIT)
{
......@@ -437,13 +487,14 @@ parse (struct mu_parseopt *po)
}
else
{
if (long_opt
if (uopt
&& po->po_cur[0]
&& !(po->po_flags & MU_OPTION_ARG_OPTIONAL))
{
mu_parseopt_error (po,
_("option '--%s' doesn't allow an argument"),
long_opt);
_("option '%s%s' doesn't allow an argument"),
po->po_long_opt_start,
uopt->opt_long);
po->po_opterr = po->po_ind;
if (po->po_flags & MU_PARSEOPT_NO_ERREXIT)
{
......@@ -455,6 +506,9 @@ parse (struct mu_parseopt *po)
}
arg = NULL;
}
if (!arg && value)
arg = value;
add_option_cache (po, opt, arg);
}
......@@ -515,6 +569,13 @@ parseopt_init (struct mu_parseopt *po, struct mu_option **options,
po->po_version_hook = NULL;
if (!(flags & MU_PARSEOPT_PROG_DOC_HOOK))
po->po_prog_doc_hook = NULL;
if (!(flags & MU_PARSEOPT_NEGATION))
po->po_negation = NULL;
if (flags & MU_PARSEOPT_SINGLE_DASH)
po->po_long_opt_start = "-";
else
po->po_long_opt_start = "--";
/* Count the options */
po->po_optc = 0;
......@@ -686,3 +747,9 @@ mu_option_set_value (struct mu_parseopt *po, struct mu_option *opt,
}
}
}
int
mu_option_possible_negation (struct mu_parseopt *po, struct mu_option *opt)
{
return po->po_negation && opt->opt_type == mu_c_bool && !opt->opt_arg;
}
......
......@@ -141,6 +141,10 @@ static struct parseopt_param parseopt_param[] = {
{ "MU_PARSEOPT_EXIT_ERROR", MU_PARSEOPT_EXIT_ERROR,
mu_c_int, mu_offsetof(struct mu_parseopt, po_exit_error) },
{ "MU_PARSEOPT_VERSION_HOOK", MU_PARSEOPT_VERSION_HOOK, mu_c_void },
{ "MU_PARSEOPT_SINGLE_DASH", MU_PARSEOPT_SINGLE_DASH, mu_c_void },
{ "MU_PARSEOPT_NEGATION", MU_PARSEOPT_NEGATION,
mu_c_string, mu_offsetof(struct mu_parseopt, po_negation) },
{ NULL }
};
......