summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOliver Kiddle <opk@zsh.org>2017-01-11 20:49:32 +0100
committerOliver Kiddle <opk@zsh.org>2017-01-11 20:50:02 +0100
commitb6082cd1e2bbeb3f0538789c244e59eca4838ce8 (patch)
tree9e59db949222f706e7673ecbb80c7730e4596943
parent47b7f2adef9d7e399af9d06a67b4db1ad55d4f32 (diff)
downloadzsh-b6082cd1e2bbeb3f0538789c244e59eca4838ce8.tar.gz
zsh-b6082cd1e2bbeb3f0538789c244e59eca4838ce8.zip
40321: _arguments option groups
-rw-r--r--ChangeLog5
-rw-r--r--Doc/Zsh/compsys.yo81
-rw-r--r--Src/Zle/computil.c191
-rw-r--r--Test/Y03arguments.ztst77
4 files changed, 248 insertions, 106 deletions
diff --git a/ChangeLog b/ChangeLog
index da1ece42a..78f590c87 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2017-01-11 Oliver Kiddle <opk@zsh.org>
+
+ * 40321: Doc/Zsh/compsys.yo, Src/Zle/computil.c,
+ Test/Y03arguments.ztst: _arguments option groups
+
2017-01-11 Peter Stephenson <p.stephenson@samsung.com>
* unposted: Src/builtin.c, Src/exec.c: be more careful to free
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 953d51c4c..2a112ed15 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -3573,13 +3573,11 @@ This function can be used to give a complete specification for completion
for a command whose arguments follow standard UNIX option and argument
conventions.
-em(Options overview)
+em(Options Overview)
Options to tt(_arguments) itself must be in separate words, i.e. tt(-s -w),
not tt(-sw). The options are followed by var(spec)s that describe options and
-arguments of the analyzed command. var(spec)s that describe option flags must
-precede var(spec)s that describe non-option ("positional" or "normal")
-arguments of the analyzed line. To avoid ambiguity, all
+arguments of the analyzed command. To avoid ambiguity, all
options to tt(_arguments) itself may be separated from the var(spec) forms
by a single colon.
@@ -3997,18 +3995,48 @@ example(local curcontext="$curcontext")
This is useful where it is not possible for multiple states to be valid
together.
-em(Specifying multiple sets of options)
+em(Grouping Options)
-It is possible to specify multiple sets of options and
-arguments with the sets separated by single hyphens. The specifications
-before the first hyphen (if any) are shared by all the remaining sets.
-The first word in every other set provides a name for the
-set which may appear in exclusion lists in specifications,
-either alone or before one of the possible values described above.
-In the second case a `tt(-)' should appear between this name and the
-remainder.
+Options can be grouped to simplify exclusion lists. A group is
+introduced with `tt(PLUS())' followed by a name for the group in the
+subsequent word. Whole groups can then be referenced in an exclusion
+list or a group name can be used to disambiguate between two forms of
+the same option. For example:
-For example:
+example(_arguments \
+ '(group2--x)-a' \
+ PLUS() group1 \
+ -m \
+ '(group2)-n' \
+ PLUS() group2 \
+ -x -y)
+
+If the name of a group is specified in the form
+`tt(LPAR())var(name)tt(RPAR())' then only one value from that group
+will ever be completed; more formally, all specifications are mutually
+exclusive to all other specifications in that group. This is useful for
+defining options that are aliases for each other. For example:
+
+example(_arguments \
+ -a -b \
+ PLUS() '(operation)' \
+ {-c,--compress}'[compress]' \
+ {-d,--decompress}'[decompress]' \
+ {-l,--list}'[list]')
+
+If an option in a group appears on the command line, it is stored in the
+associative array `tt(opt_args)' with 'var(group)tt(-)var(option)'
+as a key. In the example above, a key `tt(operation--c)' is used if the option
+`tt(-c)' is present on the command line.
+
+em(Specifying Multiple Sets of Arguments)
+
+It is possible to specify multiple sets of options and arguments with
+the sets separated by single hyphens. This differs from groups in that
+sets are considered to be mutually exclusive of each other.
+
+Specifications before the first set and from any group are common to
+all sets. For example:
example(_arguments \
-a \
@@ -4024,28 +4052,11 @@ possible completions. When it contains `tt(-d)' or an argument, the
option `tt(-c)' will not be considered. However, after `tt(-a)'
both sets will still be considered valid.
-If an option in a set appears on the command line, it is stored in the
-associative array `tt(opt_args)' with 'var(set)tt(-)var(option)'
-as a key. In the example above, a key `tt(set1--c)' is used if the option
-`tt(-c)' is on the command line.
-
-If the name given for one of the mutually exclusive sets is of the form
-`tt(LPAR())var(name)tt(RPAR())' then only one value from each set will ever
-be completed; more formally, all specifications are mutually
-exclusive to all other specifications in the same set. This is
-useful for defining multiple sets of options which are mutually
-exclusive and in which the options are aliases for each other. For
-example:
-
-example(_arguments \
- -a -b \
- - '(compress)' \
- {-c,--compress}'[compress]' \
- - '(uncompress)' \
- {-d,--decompress}'[decompress]')
+As for groups, the name of a set may appear in exclusion lists, either
+alone or preceding a normal option or argument specification.
-As the completion code has to parse the command line separately for each
-set this form of argument is slow and should only be used when necessary.
+The completion code has to parse the command line separately for each
+set. This can be slow so sets should only be used when necessary.
A useful alternative is often an option specification with rest-arguments
(as in `tt(-foo:*:...)'); here the option tt(-foo) swallows up all
remaining arguments as described by the var(optarg) definitions.
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index 12aa8950f..7bf95351f 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -934,7 +934,7 @@ struct caopt {
Caarg args; /* option arguments */
int active; /* still allowed on command line */
int num; /* it's the num'th option */
- char *set; /* set name, shared */
+ char *gsname; /* group or set name, shared */
int not; /* don't complete this option (`!...') */
};
@@ -958,7 +958,7 @@ struct caarg {
int min; /* earliest possible arg pos, given optional args */
int direct; /* true if argument number was given explicitly */
int active; /* still allowed on command line */
- char *set; /* set name, shared */
+ char *gsname; /* group or set name, shared */
};
#define CAA_NORMAL 1
@@ -1096,7 +1096,7 @@ parse_caarg(int mult, int type, int num, int opt, char *oname, char **def,
ret->type = type;
ret->opt = ztrdup(oname);
ret->direct = 0;
- ret->set = set;
+ ret->gsname = set;
/* Get the description. */
@@ -1183,10 +1183,10 @@ parse_cadef(char *nam, char **args)
Cadef all, ret;
Caopt *optp;
char **orig_args = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor, **sargs;
- char *adpre, *adsuf, *axor = NULL, *doset = NULL, **setp = NULL;
+ char *adpre, *adsuf, *axor = NULL, *doset = NULL, **pendset = NULL, **curset = NULL;
char *nonarg = NULL;
int single = 0, anum = 1, xnum, flags = 0;
- int state = 0, not = 0;
+ int foreignset = 0, not = 0;
/* First string is the auto-description definition. */
@@ -1249,7 +1249,6 @@ parse_cadef(char *nam, char **args)
if (nonarg)
tokenize(nonarg = dupstring(nonarg));
-
/* Looks good. Optimistically allocate the cadef structure. */
all = ret = alloc_cadef(orig_args, single, match, nonarg, flags);
@@ -1258,36 +1257,64 @@ parse_cadef(char *nam, char **args)
/* Get the definitions. */
- for (; *args; args++) {
+ for (; *args || pendset; args++) {
+ if (!*args) {
+ /* start new set */
+ args = sargs; /* go back and repeat parse of common options */
+ doset = NULL;
+ set_cadef_opts(ret);
+ ret = ret->snext = alloc_cadef(NULL, single, match, nonarg, flags);
+ optp = &(ret->opts);
+ anum = 1;
+ foreignset = 0;
+ curset = pendset;
+ pendset = 0;
+ }
if (args[0][0] == '-' && !args[0][1] && args[1]) {
- if (!state) {
- char *p;
- int l;
-
- if (setp) /* got to first set: skip to ours */
- args = setp;
- /* else we were the first set so don't need to skip */
- p = *++args;
- l = strlen(p) - 1;
+ if ((foreignset = curset && args != curset)) {
+ if (!pendset && args > curset)
+ pendset = args; /* mark pointer to next pending set */
+ ++args;
+ } else {
+ /* Carrying on: this is the current set */
+ char *p = *++args;
+ int l = strlen(p) - 1;
+
if (*p == '(' && p[l] == ')') {
axor = p = dupstring(p + 1);
p[l - 1] = '\0';
} else
axor = NULL;
+ if (!*p) {
+ freecadef(all);
+ zwarnnam(nam, "empty set name");
+ return NULL;
+ }
ret->set = doset = tricat(p, "-", "");
- state = 1;
- } else { /* starting new set */
- setp = args;
- state = 0;
- args = sargs - 1;
- doset = NULL;
- set_cadef_opts(ret);
- ret = ret->snext = alloc_cadef(NULL, single, match, nonarg, flags);
- optp = &(ret->opts);
- anum = 1;
+ curset = args; /* needed for the first set */
}
continue;
- }
+ } else if (args[0][0] == '+' && !args[0][1] && args[1]) {
+ char *p;
+ int l;
+
+ foreignset = 0; /* group not in any set, don't want to skip it */
+ p = *++args;
+ l = strlen(p) - 1;
+ if (*p == '(' && p[l] == ')') {
+ axor = p = dupstring(p + 1);
+ p[l - 1] = '\0';
+ } else
+ axor = NULL;
+ if (!*p) {
+ freecadef(all);
+ zwarnnam(nam, "empty group name");
+ return NULL;
+ }
+ doset = tricat(p, "-", "");
+ continue;
+ } else if (foreignset) /* skipping over a different set */
+ continue;
p = dupstring(*args);
xnum = 0;
if ((not = (*p == '!')))
@@ -1499,7 +1526,7 @@ parse_cadef(char *nam, char **args)
optp = &((*optp)->next);
opt->next = NULL;
- opt->set = doset;
+ opt->gsname = doset;
opt->name = ztrdup(rembslashcolon(name));
if (descr)
opt->descr = ztrdup(descr);
@@ -1795,63 +1822,85 @@ ca_inactive(Cadef d, char **xor, int cur, int opts)
if ((xor || opts) && cur <= compcurrent) {
Caopt opt;
char *x;
- int sl = (d->set ? (int)strlen(d->set) : -1), set = 0;
/* current word could be a prefix of a longer one so only do
* exclusions for single-letter options (for option clumping) */
int single = (cur == compcurrent);
for (; (x = (opts ? "-" : *xor)); xor++) {
- set = 0;
- if (sl > 0) {
- if (strpfx(d->set, x)) {
- x += sl;
- set = 1;
- } else if (!strncmp(d->set, x, sl - 1)) {
- Caopt p;
-
- for (p = d->opts; p; p = p->next)
- if (p->set && !(single && *p->name && p->name[1] && p->name[2]))
- p->active = 0;
-
- x = ":";
- set = 1;
+ int excludeall = 0;
+ char *grp = NULL;
+ size_t grplen;
+ char *next, *sep = x;
+
+ while (*sep != '+' && *sep != '-' && *sep != ':' && *sep != '*' && !idigit(*sep)) {
+ if (!(next = strchr(sep, '-')) || !*++next) {
+ /* exclusion is just the name of a set or group */
+ excludeall = 1; /* excluding options and args */
+ sep += strlen(sep);
+ /* A trailing '-' is included in the various gsname fields but is not
+ * there for this branch. This is why we add excludeall to grplen
+ * when checking for the null in a few places below */
+ break;
}
+ sep = next;
+ }
+ if (sep > x) { /* exclusion included a set or group name */
+ grp = x;
+ grplen = sep - grp;
+ x = sep;
}
- if (x[0] == ':' && !x[1]) {
- if (set) {
+
+ if (excludeall || (x[0] == ':' && !x[1])) {
+ if (grp) {
Caarg a;
for (a = d->args; a; a = a->next)
- if (a->set)
+ if (a->gsname && !strncmp(a->gsname, grp, grplen) &&
+ !a->gsname[grplen + excludeall])
a->active = 0;
- if (d->rest && (!set || d->rest->set))
+ if (d->rest && d->rest->gsname &&
+ !strncmp(d->rest->gsname, grp, grplen) &&
+ !d->rest->gsname[grplen + excludeall])
d->rest->active = 0;
} else
d->argsactive = 0;
- } else if (x[0] == '-' && !x[1]) {
+ }
+
+ if (excludeall || (x[0] == '-' && !x[1])) {
Caopt p;
for (p = d->opts; p; p = p->next)
- if ((!set || p->set) && !(single && *p->name && p->name[1] && p->name[2]))
+ if ((!grp || (p->gsname && !strncmp(p->gsname, grp, grplen) &&
+ !p->gsname[grplen + excludeall])) &&
+ !(single && *p->name && p->name[1] && p->name[2]))
p->active = 0;
- } else if (x[0] == '*' && !x[1]) {
- if (d->rest && (!set || d->rest->set))
- d->rest->active = 0;
- } else if (idigit(x[0])) {
- int n = atoi(x);
- Caarg a = d->args;
-
- while (a && a->num < n)
- a = a->next;
+ }
- if (a && a->num == n && (!set || a->set))
- a->active = 0;
- } else if ((opt = ca_get_opt(d, x, 1, NULL)) && (!set || opt->set) &&
- !(single && *opt->name && opt->name[1] && opt->name[2]))
- opt->active = 0;
+ if (excludeall || (x[0] == '*' && !x[1])) {
+ if (d->rest && (!grp || (d->rest->gsname &&
+ !strncmp(d->rest->gsname, grp, grplen) &&
+ !d->rest->gsname[grplen + excludeall])))
+ d->rest->active = 0;
+ }
- if (opts)
- break;
+ if (!excludeall) {
+ if (idigit(x[0])) {
+ int n = atoi(x);
+ Caarg a = d->args;
+
+ while (a && a->num < n)
+ a = a->next;
+
+ if (a && a->num == n && (!grp || (a->gsname &&
+ !strncmp(a->gsname, grp, grplen))))
+ a->active = 0;
+ } else if ((opt = ca_get_opt(d, x, 1, NULL)) &&
+ (!grp || (opt->gsname && !strncmp(opt->gsname, grp, grplen))) &&
+ !(single && *opt->name && opt->name[1] && opt->name[2]))
+ opt->active = 0;
+ if (opts)
+ break;
+ }
}
}
}
@@ -2414,19 +2463,19 @@ ca_set_data(LinkList descr, LinkList act, LinkList subc,
restrict_range(ca_laststate.argbeg, ca_laststate.argend);
}
if (arg->opt) {
- buf = (char *) zhalloc((arg->set ? strlen(arg->set) : 0) +
+ buf = (char *) zhalloc((arg->gsname ? strlen(arg->gsname) : 0) +
strlen(arg->opt) + 40);
if (arg->num > 0 && arg->type < CAA_REST)
sprintf(buf, "%soption%s-%d",
- (arg->set ? arg->set : ""), arg->opt, arg->num);
+ (arg->gsname ? arg->gsname : ""), arg->opt, arg->num);
else
sprintf(buf, "%soption%s-rest",
- (arg->set ? arg->set : ""), arg->opt);
+ (arg->gsname ? arg->gsname : ""), arg->opt);
} else if (arg->num > 0) {
sprintf(nbuf, "argument-%d", arg->num);
- buf = (arg->set ? dyncat(arg->set, nbuf) : dupstring(nbuf));
+ buf = (arg->gsname ? dyncat(arg->gsname, nbuf) : dupstring(nbuf));
} else
- buf = (arg->set ? dyncat(arg->set, "argument-rest") :
+ buf = (arg->gsname ? dyncat(arg->gsname, "argument-rest") :
dupstring("argument-rest"));
addlinknode(subc, buf);
@@ -2779,7 +2828,7 @@ bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
for (s = lstate; s; s = s->snext)
for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
if (*a) {
- *p++ = (o->set ? tricat(o->set, o->name, "") :
+ *p++ = (o->gsname ? tricat(o->gsname, o->name, "") :
ztrdup(o->name));
*p++ = ca_colonlist(*a);
}
diff --git a/Test/Y03arguments.ztst b/Test/Y03arguments.ztst
index 5957bc306..25bb96b84 100644
--- a/Test/Y03arguments.ztst
+++ b/Test/Y03arguments.ztst
@@ -463,6 +463,14 @@
0:single option sets are still mutually exclusive
>line: {tst -m -x }{}
+ tst_arguments '(set-c set-g)-a' '(set)-b' -c + grp -g - set -s
+ comptest $'tst -a -b -\t'
+0:excluding a set doesn't exclude common options as part of the set
+>line: {tst -a -b -}{}
+>DESCRIPTION:{option}
+>NO:{-c}
+>NO:{-g}
+
tst_arguments '(-)-h' -a -b -c
comptest $'tst -h -\t'
0:exclude all other options
@@ -536,6 +544,75 @@ F:>DESCRIPTION:{option}
F:>NO:{-b}
F:>NO:{-c}
+ tst_arguments + grp1 -a -b - onlyset '(-a grp3--y grp2 grp4--)-m' -n + grp2 -u -v + grp3 -x -y + grp4 -0 ':rest'
+ comptest $'tst -m -\t'
+0:exclude group options
+>line: {tst -m -}{}
+>DESCRIPTION:{rest}
+>DESCRIPTION:{option}
+>NO:{-b}
+>NO:{-n}
+>NO:{-x}
+
+ tst_arguments -x + '(grp1)' -a -b + '(grp2)' -m -n
+ comptest $'tst -m -\t'
+0:single option groups are not mutually exclusive
+>line: {tst -m -}{}
+>DESCRIPTION:{option}
+>NO:{-a}
+>NO:{-b}
+>NO:{-x}
+
+ tst_arguments '(grp1 grp2-2)-a' '(grp1-*)-b' + grp1 ':first' '*:rest' + grp2 ':second'
+ comptest $'tst -a \t\eb\C-w\C-e x y \t'
+0:exclude rest args listed within a group
+>line: {tst -a -b }{}
+>line: {tst -b x y -a }{}
+
+ tst_arguments '(grp--m)-a' + grp '-m:value:(a b c)' + agrp '-m:other:(1 2 3)'
+ comptest $'tst -m \t\C-w-a -m \t'
+0:two forms of same option in different groups
+>line: {tst -m }{}
+>DESCRIPTION:{value}
+>NO:{a}
+>NO:{b}
+>NO:{c}
+>line: {tst -a -m }{}
+>DESCRIPTION:{other}
+>NO:{1}
+>NO:{2}
+>NO:{3}
+F:should offer both sets of arguments in first case
+
+ tst_arguments '(grp--m)-x' + '(grp)' -a -b -c ':val:(1 2 3)'
+ comptest $'tst 1 -\t'
+0:normal argument excludes options in internally mutually exclusive group
+>line: {tst 1 -x }{}
+
+ tst_arguments -s -a - set1 -c -s - set2 -c -t + grp -d
+ comptest $'tst -s\t -\t'
+0:mix sets, groups and option stacking
+>line: {tst -s}{}
+>DESCRIPTION:{option}
+>NO:{-a}
+>NO:{-c}
+>NO:{-d}
+>NO:{-t}
+>line: {tst -s -}{}
+>DESCRIPTION:{option}
+>NO:{-a}
+>NO:{-c}
+>NO:{-d}
+F:shouldn't offer -t in the first case (with stacked options)
+
+ tst_arguments -s '(set-a set--b grp-a grp--b grp-)-a' - set-a -s - set--b -t + grp-a -g + grp--b -h + grp- -i
+ comptest $'tst -a\t'
+0:sets and groups with - in their name
+>line: {tst -a}{}
+>DESCRIPTION:{option}
+>NO:{-h}
+>NO:{-t}
+
tst_arguments --abc --aah :arg:
comptesteval 'setopt bashautolist automenu'
comptest $'tst --a\t\t\t'