mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-09-28 06:49:49 +02:00
44100: zparseopts: Add -F option, completion, tests; improve documentation
* Enable zparseopts to perform basic usage validation (aborting on an unrecognised option-like parameter) * Officially document the resolution of ambiguous option specs
This commit is contained in:
parent
36290f3e8e
commit
632023acc2
@ -1,3 +1,9 @@
|
||||
2019-03-12 dana <dana@dana.is>
|
||||
|
||||
* 44100: Completion/Zsh/Command/_zparseopts, Doc/Zsh/mod_zutil.yo,
|
||||
Src/Modules/zutil.c, Test/V12zparseopts.ztst: Add `zparseopts -F`,
|
||||
completion, tests, documentation
|
||||
|
||||
2019-03-08 Jun-ichi Takimoto <takimoto-j@kba.biglobe.ne.jp>
|
||||
|
||||
* 44101: Completion/Unix/Command/_vim: better support for nvim
|
||||
|
37
Completion/Zsh/Command/_zparseopts
Normal file
37
Completion/Zsh/Command/_zparseopts
Normal file
@ -0,0 +1,37 @@
|
||||
#compdef zparseopts
|
||||
|
||||
local ret=1
|
||||
local -a context line state state_descr alts opts
|
||||
local -A opt_args
|
||||
|
||||
_arguments -A '-*' : \
|
||||
'-a+[specify array in which to store parsed options]:array:_parameters -g "*array*~*readonly*"' \
|
||||
'-A+[specify association in which to store parsed options]:association:_parameters -g "*association*~*readonly*"' \
|
||||
'-D[remove parsed options from positional parameters]' \
|
||||
"-E[don't stop parsing at first parameter not described by specs]" \
|
||||
'-F[abort parsing and print error at first option-like parameter not described by specs]' \
|
||||
'-K[preserve contents of arrays/associations when specs are not matched]' \
|
||||
'-M[enable mapping among equivalent options with opt1=opt2 spec form]' \
|
||||
'(-)-[end zparseopts options; specs follow]' \
|
||||
'*: :->spec' \
|
||||
&& ret=0
|
||||
|
||||
[[ $state == spec ]] &&
|
||||
if compset -P '*='; then
|
||||
alts=()
|
||||
(( $+opt_args[-M] )) && {
|
||||
opts=( $line )
|
||||
[[ $opts[1] == (-|--) ]] && shift opts
|
||||
opts=( ${(@)opts%%(+|)(:|:-|::|)(=*|)} )
|
||||
opts=( ${(@)opts:#${words[CURRENT]%%=*}} )
|
||||
alts+=( "spec-opt-names:spec option name:(${(j< >)${(@q+)opts}})" )
|
||||
}
|
||||
alts+=( 'parameters:array:_parameters -g "*array*~*readonly*"' )
|
||||
_alternative $alts && ret=0
|
||||
else
|
||||
# Not great, but close enough for now
|
||||
compset -S '=*'
|
||||
_message -e spec-opts 'spec option (name[+][:|:-|::])' && ret=0
|
||||
fi
|
||||
|
||||
return ret
|
@ -180,7 +180,7 @@ item(tt(zregexparse))(
|
||||
This implements some internals of the tt(_regex_arguments) function.
|
||||
)
|
||||
findex(zparseopts)
|
||||
item(tt(zparseopts) [ tt(-D) tt(-K) tt(-M) tt(-E) ] [ tt(-a) var(array) ] [ tt(-A) var(assoc) ] [ tt(-) ] var(spec) ...)(
|
||||
item(tt(zparseopts) [ tt(-D) tt(-E) tt(-F) tt(-K) tt(-M) ] [ tt(-a) var(array) ] [ tt(-A) var(assoc) ] [ tt(-) ] var(spec) ...)(
|
||||
This builtin simplifies the parsing of options in positional parameters,
|
||||
i.e. the set of arguments given by tt($*). Each var(spec) describes one
|
||||
option and must be of the form `var(opt)[tt(=)var(array)]'. If an option
|
||||
@ -195,7 +195,7 @@ Note that it is an error to give any var(spec) without an
|
||||
Unless the tt(-E) option is given, parsing stops at the first string
|
||||
that isn't described by one of the var(spec)s. Even with tt(-E),
|
||||
parsing always stops at a positional parameter equal to `tt(-)' or
|
||||
`tt(-)tt(-)'.
|
||||
`tt(-)tt(-)'. See also tt(-F).
|
||||
|
||||
The var(opt) description must be one of the following. Any of the special
|
||||
characters can appear in the option name provided it is preceded by a
|
||||
@ -234,9 +234,23 @@ first colon.
|
||||
)
|
||||
enditem()
|
||||
|
||||
In all cases, option-arguments must appear either immediately following the
|
||||
option in the same positional parameter or in the next one. Even an optional
|
||||
argument may appear in the next parameter, unless it begins with a `tt(-)'.
|
||||
There is no special handling of `tt(=)' as with GNU-style argument parsers;
|
||||
given the var(spec) `tt(-foo:)', the positional parameter `tt(-)tt(-foo=bar)'
|
||||
is parsed as `tt(-)tt(-foo)' with an argument of `tt(=bar)'.
|
||||
|
||||
When the names of two options that take no arguments overlap, the longest one
|
||||
wins, so that parsing for the var(spec)s `tt(-foo -foobar)' (for example) is
|
||||
unambiguous. However, due to the aforementioned handling of option-arguments,
|
||||
ambiguities may arise when at least one overlapping var(spec) takes an
|
||||
argument, as in `tt(-foo: -foobar)'. In that case, the last matching
|
||||
var(spec) wins.
|
||||
|
||||
The options of tt(zparseopts) itself cannot be stacked because, for
|
||||
example, the stack `tt(-DEK)' is indistinguishable from a var(spec) for
|
||||
the GNU-style long option `tt(--DEK)'. The options of tt(zparseopts)
|
||||
the GNU-style long option `tt(-)tt(-DEK)'. The options of tt(zparseopts)
|
||||
itself are:
|
||||
|
||||
startitem()
|
||||
@ -252,8 +266,29 @@ as the values.
|
||||
item(tt(-D))(
|
||||
If this option is given, all options found are removed from the positional
|
||||
parameters of the calling shell or shell function, up to but not including
|
||||
any not described by the var(spec)s. This is similar to using the tt(shift)
|
||||
builtin.
|
||||
any not described by the var(spec)s. If the first such parameter is `tt(-)'
|
||||
or `tt(-)tt(-)', it is removed as well. This is similar to using the
|
||||
tt(shift) builtin.
|
||||
)
|
||||
item(tt(-E))(
|
||||
This changes the parsing rules to em(not) stop at the first string
|
||||
that isn't described by one of the var(spec)s. It can be used to test
|
||||
for or (if used together with tt(-D)) extract options and their
|
||||
arguments, ignoring all other options and arguments that may be in the
|
||||
positional parameters. As indicated above, parsing still stops at the
|
||||
first `tt(-)' or `tt(-)tt(-)' not described by a var(spec), but it is not
|
||||
removed when used with tt(-D).
|
||||
)
|
||||
item(tt(-F))(
|
||||
If this option is given, tt(zparseopts) immediately stops at the first
|
||||
option-like parameter not described by one of the var(spec)s, prints an
|
||||
error message, and returns status 1. Removal (tt(-D)) and extraction
|
||||
(tt(-E)) are not performed, and option arrays are not updated. This
|
||||
provides basic validation for the given options.
|
||||
|
||||
Note that the appearance in the positional parameters of an option without
|
||||
its required argument always aborts parsing and returns an error as described
|
||||
above regardless of whether this option is used.
|
||||
)
|
||||
item(tt(-K))(
|
||||
With this option, the arrays specified with the tt(-a) option and with the
|
||||
@ -272,13 +307,6 @@ is found, the values are stored as usual. This changes only the way the
|
||||
values are stored, not the way tt($*) is parsed, so results may be
|
||||
unpredictable if the `var(name)tt(+)' specifier is used inconsistently.
|
||||
)
|
||||
item(tt(-E))(
|
||||
This changes the parsing rules to em(not) stop at the first string
|
||||
that isn't described by one of the var(spec)s. It can be used to test
|
||||
for or (if used together with tt(-D)) extract options and their
|
||||
arguments, ignoring all other options and arguments that may be in the
|
||||
positional parameters.
|
||||
)
|
||||
enditem()
|
||||
|
||||
For example,
|
||||
|
@ -1644,7 +1644,7 @@ static int
|
||||
bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
||||
{
|
||||
char *o, *p, *n, **pp, **aval, **ap, *assoc = NULL, **cp, **np;
|
||||
int del = 0, flags = 0, extract = 0, keep = 0;
|
||||
int del = 0, flags = 0, extract = 0, fail = 0, keep = 0;
|
||||
Zoptdesc sopts[256], d;
|
||||
Zoptarr a, defarr = NULL;
|
||||
Zoptval v;
|
||||
@ -1681,6 +1681,14 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
||||
}
|
||||
extract = 1;
|
||||
break;
|
||||
case 'F':
|
||||
if (o[2]) {
|
||||
args--;
|
||||
o = NULL;
|
||||
break;
|
||||
}
|
||||
fail = 1;
|
||||
break;
|
||||
case 'K':
|
||||
if (o[2]) {
|
||||
args--;
|
||||
@ -1843,6 +1851,10 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
||||
if (!(d = lookup_opt(o + 1))) {
|
||||
while (*++o) {
|
||||
if (!(d = sopts[STOUC(*o)])) {
|
||||
if (fail) {
|
||||
zwarnnam(nam, "bad option: %c", *o);
|
||||
return 1;
|
||||
}
|
||||
o = NULL;
|
||||
break;
|
||||
}
|
||||
|
172
Test/V12zparseopts.ztst
Normal file
172
Test/V12zparseopts.ztst
Normal file
@ -0,0 +1,172 @@
|
||||
# Test zparseopts from the zsh/zutil module
|
||||
|
||||
%prep
|
||||
|
||||
if zmodload zsh/zutil 2> /dev/null; then
|
||||
# Produce a string representing an associative array ordered by its keys
|
||||
order_assoc() {
|
||||
local -a _arr
|
||||
for 2 in "${(@kP)1}"; do
|
||||
_arr+=( "${(q-)2} ${(q-)${(P)1}[$2]}" )
|
||||
done
|
||||
print -r - ${(j< >)${(@o)_arr}}
|
||||
}
|
||||
else
|
||||
ZTST_unimplemented="can't load the zsh/zutil module for testing"
|
||||
fi
|
||||
|
||||
%test
|
||||
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -a optv - a b: c:- z
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} -ab1 -c -d -e -z
|
||||
0:zparseopts -a
|
||||
>ret: 0, optv: -a -b 1 -c-d, argv: -ab1 -c -d -e -z
|
||||
|
||||
() {
|
||||
local -A opts
|
||||
zparseopts -A opts - a b: c:- z
|
||||
print -r - ret: $?, opts: "$( order_assoc opts )", argv: $argv
|
||||
} -ab1 -c -d -e -z
|
||||
0:zparseopts -A
|
||||
>ret: 0, opts: -a '' -b 1 -c -d, argv: -ab1 -c -d -e -z
|
||||
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -D -a optv - a b: c:- z
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} -ab1 -c -d -e -z
|
||||
0:zparseopts -D
|
||||
>ret: 0, optv: -a -b 1 -c-d, argv: -e -z
|
||||
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -E -a optv - a b: c:- z
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} -ab1 -c -d -e -z
|
||||
0:zparseopts -E
|
||||
>ret: 0, optv: -a -b 1 -c-d -z, argv: -ab1 -c -d -e -z
|
||||
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -D -E -a optv - a b: c:- z
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} -ab1 -c -d -e -z
|
||||
0:zparseopts -D -E
|
||||
>ret: 0, optv: -a -b 1 -c-d -z, argv: -e
|
||||
|
||||
for 1 in '-a -x -z' '-ax -z' '-a --x -z'; do
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -D -E -F -a optv - a b: c:- z
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} $=1
|
||||
done
|
||||
0:zparseopts -F
|
||||
?(anon):zparseopts:2: bad option: x
|
||||
>ret: 1, optv: , argv: -a -x -z
|
||||
?(anon):zparseopts:2: bad option: x
|
||||
>ret: 1, optv: , argv: -ax -z
|
||||
?(anon):zparseopts:2: bad option: -
|
||||
>ret: 1, optv: , argv: -a --x -z
|
||||
|
||||
for 1 in '-a 1 2 3' '1 2 3'; do
|
||||
() {
|
||||
local -a optv=( -x -y -z )
|
||||
zparseopts -D -K -a optv - a b: c:- z
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} $=1
|
||||
done
|
||||
0:zparseopts -K -a
|
||||
>ret: 0, optv: -a, argv: 1 2 3
|
||||
>ret: 0, optv: -x -y -z, argv: 1 2 3
|
||||
|
||||
for 1 in '-a 1 2 3' '1 2 3'; do
|
||||
() {
|
||||
local -A opts=( -b 1 -z '' )
|
||||
zparseopts -D -K -A opts - a b: c:- z
|
||||
print -r - ret: $?, opts: "$( order_assoc opts )", argv: $argv
|
||||
} $=1
|
||||
done
|
||||
0:zparseopts -K -A
|
||||
>ret: 0, opts: -a '' -b 1 -z '', argv: 1 2 3
|
||||
>ret: 0, opts: -b 1 -z '', argv: 1 2 3
|
||||
|
||||
() {
|
||||
local -a optv
|
||||
local -A opts
|
||||
zparseopts -D -M -a optv -A opts - a:=-aaa -aaa:
|
||||
print -r - ret: $?, optv: $optv, opts: "$( order_assoc opts )", argv: $argv
|
||||
} --aaa foo -a bar 1 2 3
|
||||
0:zparseopts -M
|
||||
>ret: 0, optv: --aaa bar, opts: --aaa bar, argv: 1 2 3
|
||||
|
||||
() {
|
||||
local -a optv aa ab
|
||||
zparseopts -a optv - a=aa b:=ab c:- z
|
||||
print -r - ret: $?, optv: $optv, aa: $aa, ab: $ab, argv: $argv
|
||||
} -ab1 -c -d
|
||||
0:multiple arrays
|
||||
>ret: 0, optv: -c-d, aa: -a, ab: -b 1, argv: -ab1 -c -d
|
||||
|
||||
for 1 in '-a - -b - - -b' '-a -- -b -- -- -b' '-a 1 -b - - -b'; do
|
||||
# -D alone strips - out
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -D -F -a optv - a b: c:- z
|
||||
print -r - '(-D )' ret: $?, optv: $optv, argv: $argv
|
||||
} $=1
|
||||
# -D -E leaves - in
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -D -E -F -a optv - a b: c:- z
|
||||
print -r - '(-D -E)' ret: $?, optv: $optv, argv: $argv
|
||||
} $=1
|
||||
done
|
||||
0:-/-- handling
|
||||
>(-D ) ret: 0, optv: -a, argv: -b - - -b
|
||||
>(-D -E) ret: 0, optv: -a, argv: - -b - - -b
|
||||
>(-D ) ret: 0, optv: -a, argv: -b -- -- -b
|
||||
>(-D -E) ret: 0, optv: -a, argv: -- -b -- -- -b
|
||||
>(-D ) ret: 0, optv: -a, argv: 1 -b - - -b
|
||||
>(-D -E) ret: 0, optv: -a -b -, argv: 1 - -b
|
||||
|
||||
# Escaping should always work, but it's optional on the first character
|
||||
for specs in '\+ \: \= \\' '+ : = \'; do
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -D -a optv - $=specs
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} -+:=\\ 1 2 3
|
||||
done
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -D -a optv - '-\:\:\::'
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} --:::foo 1 2 3
|
||||
0:special characters in option names
|
||||
>ret: 0, optv: -+ -: -= -\, argv: 1 2 3
|
||||
>ret: 0, optv: -+ -: -= -\, argv: 1 2 3
|
||||
>ret: 0, optv: --::: foo, argv: 1 2 3
|
||||
|
||||
for specs in '-foo: -foobar' '-foobar -foo:'; do
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -a optv - $=specs
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} --foobar 1 2 3
|
||||
done
|
||||
0:overlapping option specs (scan order)
|
||||
>ret: 0, optv: --foobar, argv: --foobar 1 2 3
|
||||
>ret: 0, optv: --foo bar, argv: --foobar 1 2 3
|
||||
|
||||
() {
|
||||
local -a optv
|
||||
zparseopts -a optv - a b: c:- z
|
||||
print -r - ret: $?, optv: $optv, argv: $argv
|
||||
} -ab1 -c
|
||||
0:missing optarg
|
||||
?(anon):zparseopts:2: missing argument for option: c
|
||||
>ret: 1, optv: , argv: -ab1 -c
|
Loading…
Reference in New Issue
Block a user