Commit 43fb1f13 43fb1f13a88924654da3d45bbb820a7206a69d55 by Sergey Poznyakoff

Fix handling of ambiguous command line options.

The approach used so far failed to recognize ambiguous abbreviations
located in different groups.  It also didn't work when
MU_PARSEOPT_NO_SORT was requested.  This commit fixes it by keeping
an additional array of indices to long options.  The array is sorted
so that its elements produce a lexicographically ascending list of
long options.

* include/mailutils/opt.h (mu_parseopt): New members po_longcnt,
po_longidx keep a sorted array of indices to po_optv with long
options.
* libmailutils/opt/opt.c (find_long_option): Iterate over po_longidx.
(parseopt_init): Initialize and sort po_longidx.
(mu_parseopt_free): Free po_longidx.

* libmailutils/tests/parseopt.c: Add three more potentially
ambiguous options
* libmailutils/tests/parseopt26.at: New testcase.
* libmailutils/tests/parseopt27.at: New testcase.
* libmailutils/tests/Makefile.am: Add new testcases.
* libmailutils/tests/testsuite.at: Likewise.
1 parent 0f97d2e1
......@@ -171,6 +171,9 @@ struct mu_parseopt
int po_arg_count;
unsigned po_permuted:1; /* Whether the arguments were permuted */
size_t po_longcnt; /* Number of long options */
size_t *po_longidx; /* Indices of long options in po_optv */
};
......
......@@ -285,61 +285,60 @@ find_long_option (struct mu_parseopt *po, char const *optstr,
optlen = strcspn (optstr, "=");
for (i = 0; i < po->po_optc; i++)
for (i = 0; i < po->po_longcnt; i++)
{
if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i]))
size_t j = po->po_longidx[i];
size_t len = strlen (po->po_optv[j]->opt_long);
struct mu_option *opt = option_unalias (po, j);
neg = neg_nomatch;
if ((optlen <= len
&& memcmp (po->po_optv[j]->opt_long, optstr, optlen) == 0)
|| (neg = negmatch (po, j, optstr, optlen)))
{
size_t len = strlen (po->po_optv[i]->opt_long);
struct mu_option *opt = option_unalias (po, i);
neg = neg_nomatch;
if ((optlen <= len
&& memcmp (po->po_optv[i]->opt_long, optstr, optlen) == 0)
|| (neg = negmatch (po, i, optstr, optlen)))
switch (found)
{
switch (found)
{
case 0:
used_opt = po->po_optv[i];
ret_opt = opt;
found++;
if (optlen == len || neg == neg_match_exact)
i = po->po_optc - 1; /* exact match: break the loop */
break;
case 0:
used_opt = po->po_optv[j];
ret_opt = opt;
found++;
if (optlen == len || neg == neg_match_exact)
i = po->po_longcnt - 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);
if (neg == neg_nomatch && negmatch (po, i, optstr, optlen))
fprintf (stderr, "%s%s%s\n",
po->po_long_opt_start,
po->po_negation,
po->po_optv[i]->opt_long);
found++;
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);
if (neg == neg_nomatch && negmatch (po, j, optstr, optlen))
fprintf (stderr, "%s%s%s\n",
po->po_long_opt_start,
po->po_negation,
po->po_optv[j]->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);
if (neg == neg_nomatch && negmatch (po, i, optstr, optlen))
fprintf (stderr, "%s%s%s\n",
po->po_long_opt_start,
po->po_negation,
po->po_optv[i]->opt_long);
}
case 2:
fprintf (stderr, "%s%s%s\n",
po->po_long_opt_start,
neg ? po->po_negation : "",
po->po_optv[j]->opt_long);
if (neg == neg_nomatch && negmatch (po, j, optstr, optlen))
fprintf (stderr, "%s%s%s\n",
po->po_long_opt_start,
po->po_negation,
po->po_optv[j]->opt_long);
}
}
}
switch (found)
{
......@@ -612,6 +611,29 @@ parse (struct mu_parseopt *po)
return 0;
}
#define LONGOPT(po, i) po->po_optv[po->po_longidx[i]]->opt_long
static void
sort_longidx (struct mu_parseopt *po)
{
/* Sort the po_longidx array so that its elements produce lexicographically
ascending list of long options.
Given relatively small number of command line options, simple insertion
sort is used.
*/
size_t i, j;
for (i = 1; i < po->po_longcnt; i++)
{
for (j = i; j > 0 && strcmp (LONGOPT (po, j-1), LONGOPT (po, j)) > 0; j--)
{
size_t tmp = po->po_longidx[j];
po->po_longidx[j] = po->po_longidx[j-1];
po->po_longidx[j-1] = tmp;
}
}
}
/* Initialize structure mu_parseopt with given options and flags. */
static int
parseopt_init (struct mu_parseopt *po, struct mu_option **options,
......@@ -716,6 +738,20 @@ parseopt_init (struct mu_parseopt *po, struct mu_option **options,
}
}
j = 0;
for (i = 0; i < po->po_optc; i++)
if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i]))
j++;
po->po_longcnt = j;
po->po_longidx = mu_calloc (j + 1, sizeof (po->po_longidx[0]));
j = 0;
for (i = 0; i < po->po_optc; i++)
if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i]))
po->po_longidx[j++] = i;
sort_longidx (po);
po->po_ind = 0;
po->po_opterr = 0;
po->po_optlist = NULL;
......@@ -772,6 +808,7 @@ void
mu_parseopt_free (struct mu_parseopt *popt)
{
free (popt->po_optv);
free (popt->po_longidx);
mu_list_destroy (&popt->po_optlist);
}
......
......@@ -138,6 +138,8 @@ TESTSUITE_AT = \
parseopt23.at\
parseopt24.at\
parseopt25.at\
parseopt26.at\
parseopt27.at\
parseopt_help00.at\
parseopt_help01.at\
parseopt_help02.at\
......
......@@ -27,6 +27,8 @@ int jobs = 0;
int x_option;
int a_option;
int d_option;
int debug_level_value;
char *debug_info_value;
struct mu_option group_a[] = {
MU_OPTION_GROUP("Group A"),
......@@ -43,6 +45,7 @@ struct mu_option group_a[] = {
{ "all", 'a', NULL, MU_OPTION_DEFAULT,
"no arguments to this one",
mu_c_bool, &a_option },
{ "debug-all", 0, NULL, MU_OPTION_ALIAS },
MU_OPTION_END
};
......@@ -61,7 +64,18 @@ struct mu_option group_b[] = {
MU_OPTION_END
};
struct mu_option *optv[] = { group_a, group_b, NULL };
struct mu_option group_c[] = {
MU_OPTION_GROUP("Group C"),
{ "debug-level", 0, "NUM", MU_OPTION_DEFAULT,
"debug level option",
mu_c_int, &debug_level_value },
{ "debug-info", 0, "S", MU_OPTION_DEFAULT,
"debug information",
mu_c_string, &debug_info_value },
MU_OPTION_END
};
struct mu_option *optv[] = { group_a, group_b, group_c, NULL };
static void
version_hook (struct mu_parseopt *po, mu_stream_t str)
......@@ -207,6 +221,9 @@ main (int argc, char *argv[])
printf ("find_value=%s\n", S(find_value));
printf ("d_option=%d\n", d_option);
printf ("jobs=%d\n", jobs);
printf ("debug_level_value=%d\n", debug_level_value);
printf ("debug_info_value=%s\n", S(debug_info_value));
printf ("argv:\n");
for (i = 0; i < argc; i++)
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
])
AT_CLEANUP
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=1
find_value=(null)
d_option=1
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=1
find_value=(null)
d_option=1
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=2
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=1
find_value=(null)
d_option=2
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=1
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=2
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=Word
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=1
find_value=word
d_option=0
jobs=10
debug_level_value=0
debug_info_value=(null)
argv:
0: command
1: line
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=3
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
])
AT_CLEANUP
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: more
1: arguments
......@@ -49,6 +51,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: more
1: arguments
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: -a
1: --optional
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: more
1: -a
......
......@@ -29,6 +29,8 @@ a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=(null)
argv:
0: more
1: -a
......
# This file is part of GNU Mailutils. -*- Autotest -*-
# Copyright (C) 2016-2017 Free Software Foundation, Inc.
#
# GNU Mailutils 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, or (at
# your option) any later version.
#
# GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_SETUP([ambiguous abbreviated long options (2)])
AT_KEYWORDS([parseopt parseopt_long parseopt_long_abbr parseopt_long_ambig parseopt26])
AT_CHECK([
PARSEOPT_DEFAULT
parseopt --debug- command line arguments
],
[1],
[],
[parseopt: option '--debug-' is ambiguous; possibilities:
--debug-all
--debug-info
--debug-level
])
AT_CLEANUP
# This file is part of GNU Mailutils. -*- Autotest -*-
# Copyright (C) 2016-2017 Free Software Foundation, Inc.
#
# GNU Mailutils 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, or (at
# your option) any later version.
#
# GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_SETUP([ambiguous abbreviated long options (3)])
AT_KEYWORDS([parseopt parseopt_long parseopt_long_abbr parseopt_long_ambig parseopt27])
AT_CHECK([
PARSEOPT_DEFAULT
parseopt --debug-i command line arguments
],
[0],
[rc=0
file_name=(null)
opt_value=initial
x_option=0
a_option=0
find_value=(null)
d_option=0
jobs=0
debug_level_value=0
debug_info_value=command
argv:
0: line
1: arguments
])
AT_CLEANUP
......@@ -24,7 +24,7 @@ parseopt --help
[[Usage: parseopt [OPTION...]
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[=FILE] optional argument
-x short-only option
......@@ -34,6 +34,10 @@ parseopt --help
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -15,14 +15,15 @@
# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_SETUP([standard usage output])
AT_KEYWORDS([parseopt parseopt_help parseopt_help00])
AT_KEYWORDS([parseopt parseopt_help parseopt_help01])
AT_CHECK([
PARSEOPT_DEFAULT
parseopt --usage
],
[0],
[[Usage: parseopt [-advx?] [-f FILE] [-F VALUE] [-j N] [-o[FILE]] [--all]
[--debug] [--file=FILE] [--find=VALUE] [--help] [--jobs=N]
[--debug] [--debug-all] [--debug-info=S] [--debug-level=NUM]
[--file=FILE] [--find=VALUE] [--help] [--jobs=N]
[--optional[=FILE]] [--usage] [--verbose]
]])
AT_CLEANUP
......
......@@ -24,7 +24,7 @@ MU_PARSEOPT_PROG_NAME=newname parseopt --help
[[Usage: newname [OPTION...]
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[=FILE] optional argument
-x short-only option
......@@ -34,6 +34,10 @@ MU_PARSEOPT_PROG_NAME=newname parseopt --help
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -25,7 +25,7 @@ MU_PARSEOPT_PROG_DOC="Tests option parsing" parseopt --help
Tests option parsing
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[=FILE] optional argument
-x short-only option
......@@ -35,6 +35,10 @@ Tests option parsing
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -24,7 +24,7 @@ MU_PARSEOPT_PROG_ARGS="SOME MORE ARGS" parseopt --help
[Usage: parseopt [[OPTION...]] SOME MORE ARGS
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[[=FILE]] optional argument
-x short-only option
......@@ -34,6 +34,10 @@ MU_PARSEOPT_PROG_ARGS="SOME MORE ARGS" parseopt --help
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -24,7 +24,7 @@ MU_PARSEOPT_BUG_ADDRESS='gray@gnu.org' parseopt --help
[Usage: parseopt [[OPTION...]]
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[[=FILE]] optional argument
-x short-only option
......@@ -34,6 +34,10 @@ MU_PARSEOPT_BUG_ADDRESS='gray@gnu.org' parseopt --help
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -24,7 +24,7 @@ MU_PARSEOPT_PACKAGE_NAME='GNU Mailutils' MU_PARSEOPT_PACKAGE_URL='http://mailuti
[[Usage: parseopt [OPTION...]
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[=FILE] optional argument
-x short-only option
......@@ -34,6 +34,10 @@ MU_PARSEOPT_PACKAGE_NAME='GNU Mailutils' MU_PARSEOPT_PACKAGE_URL='http://mailuti
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -31,7 +31,7 @@ MU_PARSEOPT_PROG_DOC="Tests option parsing"\
Tests option parsing
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[=FILE] optional argument
-x short-only option
......@@ -41,6 +41,10 @@ Tests option parsing
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -25,7 +25,7 @@ ARGP_HELP_FMT=dup-args,no-dup-args-note,short-opt-col=1,opt-doc-col=32,header-co
[[Usage: parseopt [OPTION...]
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f FILE, --file=FILE set file name
-o[FILE], --optional[=FILE] optional argument
-x short-only option
......@@ -35,6 +35,10 @@ ARGP_HELP_FMT=dup-args,no-dup-args-note,short-opt-col=1,opt-doc-col=32,header-co
-F VALUE, --find=VALUE find VALUE
-j N, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -23,7 +23,8 @@ ARGP_HELP_FMT=rmargin=62,usage-indent=1\
],
[0],
[[Usage: parseopt [-advx?] [-f FILE] [-F VALUE] [-j N]
[-o[FILE]] [--all] [--debug] [--file=FILE] [--find=VALUE]
[--help] [--jobs=N] [--optional[=FILE]] [--usage] [--verbose]
[-o[FILE]] [--all] [--debug] [--debug-all] [--debug-info=S]
[--debug-level=NUM] [--file=FILE] [--find=VALUE] [--help]
[--jobs=N] [--optional[=FILE]] [--usage] [--verbose]
]])
AT_CLEANUP
......
......@@ -22,7 +22,8 @@ MU_PARSEOPT_VERSION_HOOK=1 parseopt --usage
],
[0],
[[Usage: parseopt [-advVx?] [-f FILE] [-F VALUE] [-j N] [-o[FILE]] [--all]
[--debug] [--file=FILE] [--find=VALUE] [--help] [--jobs=N]
[--debug] [--debug-all] [--debug-info=S] [--debug-level=NUM]
[--file=FILE] [--find=VALUE] [--help] [--jobs=N]
[--optional[=FILE]] [--usage] [--verbose] [--version]
]])
AT_CLEANUP
......
......@@ -24,7 +24,7 @@ MU_PARSEOPT_VERSION_HOOK=1 parseopt --help
[[Usage: parseopt [OPTION...]
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[=FILE] optional argument
-x short-only option
......@@ -34,6 +34,10 @@ MU_PARSEOPT_VERSION_HOOK=1 parseopt --help
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
-V, --version print program version
......
......@@ -26,7 +26,7 @@ MU_PARSEOPT_PROG_ARGS="SOME MORE ARGS|ALTERNATIVE ARGS|ANOTHER ARGS" parseopt --
or: parseopt [[OPTION...]] ANOTHER ARGS
Group A
-a, --all no arguments to this one
-a, --all, --debug-all no arguments to this one
-f, --file=FILE set file name
-o, --optional[[=FILE]] optional argument
-x short-only option
......@@ -36,6 +36,10 @@ MU_PARSEOPT_PROG_ARGS="SOME MORE ARGS|ALTERNATIVE ARGS|ANOTHER ARGS" parseopt --
-F, --find=VALUE find VALUE
-j, --jobs=N sets numeric value
Group C
--debug-info=S debug information
--debug-level=NUM debug level option
-?, --help give this help list
--usage give a short usage message
......
......@@ -129,6 +129,8 @@ m4_include([parseopt22.at])
m4_include([parseopt23.at])
m4_include([parseopt24.at])
m4_include([parseopt25.at])
m4_include([parseopt26.at])
m4_include([parseopt27.at])
AT_BANNER([Command line help output])
m4_include([parseopt_help00.at])
......