1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
#autoload
# A simple compiler for _arguments descriptions. The first argument of
# _arg_compile is the name of an array parameter in which the parse is
# returned. The remaining arguments form a series of `phrases'. Each
# `phrase' begins with one of the keywords "argument", "option", or "help"
# and consists of a series of keywords and/or values. The syntax is as
# free-form as possible, but "argument" phrases generally must appear in
# the same relative position as the corresponding argument on the command
# line to be completed, and there are some restrictions on ordering of
# keywords and values within each phrase.
#
# Anything appearing before the first phrase or after the last is passed
# through verbatim. (See TODO.) If more detailed mixing of compiled and
# uncompiled fragments is necessary, use two or more calls, either with
# different array names or by passing the output of each previous call
# through the next.
#
# In the documentation below, brackets [ ] indicate optional elements and
# braces { } indicate elements that may be repeated zero or more times.
# Except as noted, bracketed or braced elements may appear in any order
# relative to each other, but tokens within each element are ordered.
#
# argument [POS] [means MSG] [action ACT]
#
# POS may be an integer N for the Nth argument or "*" for all, and
# must appear first if it appears at all.
# MSG is a string to be displayed above the matches in a listing.
# ACT is (currently) as described in the compsys manual.
#
# option OPT [follow HOW] [explain STR] {unless XOR} \
# {[means MSG] [action ACT]} [through PAT [means MSG] [action ACT]]
#
# OPT is the option, prefixed with "*" if it may appear more than once.
# HOW refers to a following argument, and may be one of:
# "close" must appear in the same word (synonyms "join" or "-")
# "next" the argument must appear in the next word (aka "split")
# "loose" the argument may appear in the same or the next word ("+")
# "assign" as loose, but must follow an "=" in the same word ("=")
# HOW should be suffixed with a colon if the following argument is
# _not_ required to appear.
# STR is to be displayed based on compconfig[describe_options].
# XOR is another option in combination with which OPT may not appear.
# It may be ":" to disable non-option completions when OPT is present.
# MSG is a string to be displayed above the matches in a listing.
# ACT is (currently) as described in the compsys manual.
# PAT is either "*" for "all remaining words on the line" or a pattern
# that, if matched, marks the end of the arguments of this option.
# The "through PAT ..." description must be the last.
# PAT may be suffixed with one colon to narrow the $words array to
# the remainder of the command line, or with two colons to narrow
# to the words before (not including) the next that matches PAT.
#
# help PAT [means MSG] action ACT
#
# ACT is applied to any option output by --help that matches PAT.
# Do not use "help" with commands that do not support --help.
# PAT may be suffixed with a colon if the following argument is
# _not_ required to appear (this is usually inferred from --help).
# MSG is a string to be displayed above the matches in a listing.
# EXAMPLE:
# This is from _gprof in the standard distribution. Note that because of
# the brace expansion trick used in the "function name" case, no attempt
# is made to use `phrase' form; that part gets passed through unchanged.
# It could simply be moved to the _arguments call ahead of "$args[@]".
#
# _arg_compile args -s -{a,b,c,D,h,i,l,L,s,T,v,w,x,y,z} \
# -{A,C,e,E,f,F,J,n,N,O,p,P,q,Q,Z}:'function name:->funcs' \
# option -I means directory action _dir_list \
# option -d follow close means "debug level" \
# option -k means "function names" action '->pair' \
# option -m means "minimum execution count" \
# argument means executable action '_files -g \*\(\*\)' \
# argument means "profile file" action '_files -g gmon.\*' \
# help '*=name*' means "function name" action '->funcs' \
# help '*=dirs*' means "directory" action _dir_list
# _arguments "$args[@]"
# TODO:
# Verbose forms of various actions, e.g. (but not exactly)
# "state foo" becomes "->foo"
# "completion X explain Y ..." becomes "((X\:Y ...))"
# etc.
# Represent leading "*" in OPT some other way.
# Represent trailing colons in HOW and PAT some other way.
# Stricter syntax checking on HOW, sanity checks on XOR.
# Something less obscure than "unless :" would be nice.
# Warning or other syntax check for stuff after the last phrase.
emulate -L zsh
local -h argspec dspec helpspec prelude xor
local -h -A amap dmap safe
[[ -n "$1" ]] || return 1
[[ ${(tP)${1}} = *-local ]] && { print -R NAME CONFLICT: $1 1>&2; return 1 }
safe[reply]="$1"; shift
# First consume and save anything before the argument phrases
helpspec=()
prelude=()
while (($#))
do
case $1 in
(argument|help|option) break;;
(*) prelude=("$prelude[@]" "$1"); shift;;
esac
done
# Consume all the argument phrases and build the argspec array
while (($#))
do
amap=()
dspec=()
case $1 in
# argument [POS] [means MSG] [action ACT]
(argument)
shift
while (($#))
do
case $1 in
(<1->|\*) amap[position]="$1"; shift;;
(means|action) amap[$1]="$2"; shift 2;;
(argument|option|help) break;;
(*) print -R SYNTAX ERROR at "$@" 1>&2; return 1;;
esac
done
if (( $#amap ))
then
argspec=("$argspec[@]" "${amap[position]}:${amap[means]}:${amap[action]}")
fi;;
# option OPT [follow HOW] [explain STR] {unless XOR} \
# {[through PAT] [means MSG] [action ACT]}
(option)
amap[option]="$2"; shift 2
dmap=()
xor=()
while (( $# ))
do
(( ${+amap[$1]} || ${+dmap[through]} )) && break;
case $1 in
(follow)
amap[follow]="${2:s/join/-/:s/close/-/:s/next//:s/split//:s/loose/+/:s/assign/=/:s/none//}"
shift 2;;
(explain) amap[explain]="[$2]" ; shift 2;;
(unless) xor=("$xor[@]" "${(@)=2}"); shift 2;;
(through|means|action)
while (( $# ))
do
(( ${+dmap[$1]} )) && break 2
case $1 in
(through|means|action) dmap[$1]=":${2}"; shift 2;;
(argument|option|help|follow|explain|unless) break;;
(*) print -R SYNTAX ERROR at "$@" 1>&2; return 1;;
esac
done;;
(argument|option|help) break;;
(*) print -R SYNTAX ERROR at "$@" 1>&2; return 1;;
esac
if (( $#dmap ))
then
dspec=("$dspec[@]" "${dmap[through]}${dmap[means]:-:}${dmap[action]:-:}")
fi
done
if (( $#amap ))
then
argspec=("$argspec[@]" "${xor:+($xor)}${amap[option]}${amap[follow]}${amap[explain]}${dspec}")
fi;;
# help PAT [means MSG] action ACT
(help)
amap[pattern]="$2"; shift 2
while (($#))
do
(( ${+amap[$1]} )) && break;
case $1 in
(means|action) amap[$1]="$2"; shift 2;;
(argument|option|help) break;;
(*) print -R SYNTAX ERROR at "$@" 1>&2; return 1;;
esac
done
if (( $#amap ))
then
helpspec=("$helpspec[@]" "${amap[pattern]}:${amap[means]}:${amap[action]}")
fi;;
(*) break;;
esac
done
eval $safe[reply]'=( "${prelude[@]}" "${argspec[@]}" ${helpspec:+"-- ${helpspec[@]}"} "$@" )'
# print -R _arguments "${prelude[@]:q}" "${argspec[@]:q}" ${helpspec:+"-- ${helpspec[@]:q}"} "$@:q"
return 0
|