Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: completion regex handling



One of my projects for a while has been to work out how to use
_regex_arguments.  Reading the documentation and the code didn't help.
Only when I saw the examples in the function did it start to make sense.

I wrote a function _regex_words to make it easier to handle word
arguments.  This replaces _arguments but doesn't have anything like the
range of possibilities.

I then wrote a completion for the "ip" command.  I've put it in the Unix
directory tree since I think it's usable elsewhere than Linux.  It works
quite well, and I was pleasantly surprised to find that it all fitted
together quite smoothly.

There are a few problems.  I couldn't work out how to add separators
other than space to a command added from within a regex argument
specifier.  It should have been possible to use _values -s, but for some
reason that didn't work.  _regex_words takes an argument -t to give a
different terminator, but it's not documented since it doesn't work
properly yet.

The main problem, however, is the verboseness of the generated function.
The original _ip is quite compact given what it does.  However, the
function generated by _regex_arguments isn't, due to repetition.

One form of repetition is that rules need to be repeated whether they
can be used.  This can probably be fixed by adding a facility for named
rules to the system.  That ought not to be too difficult.

Another problem is that the same tag and description need to be repated
for each separate completion, including all words that can be completed
at the same point, as these are used for grouping.  This would need some
syntax to supply a default tag and description.

The _ip completion itself is mostly there, but could do with tweaking in
a number of ways.

Index: Completion/Base/Utility/_regex_words
===================================================================
RCS file: Completion/Base/Utility/_regex_words
diff -N Completion/Base/Utility/_regex_words
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ Completion/Base/Utility/_regex_words	25 Feb 2007 22:28:51 -0000
@@ -0,0 +1,44 @@
+#autoload
+
+local opt OPTARG
+local term=$'\0'
+
+while getopts "t:" opt; do
+  case $opt in
+    (t)
+    term=$OPTARG
+    ;;
+
+    (*)
+    return 1
+    ;;
+  esac
+done
+shift $(( OPTIND - 1 ))
+
+local tag=$1
+local desc=$2
+shift 2
+
+reply=(\()
+
+integer i
+local -a wds
+
+for (( i = 1; i <= $#; i++ )); do
+  wds=(${(s.:.)argv[i]})
+  reply+=(/${wds[1]//\**/"[^$term]#"}"$term"/)
+  if [[ $term = $'\0' ]]; then
+    reply+=(":${tag}:${desc}:(( ${wds[1]//\*}:${wds[2]//(#m)[: \(\)]/\\$MATCH} ))")
+  else
+    # HERE: we should add the terminator instead of a space, but
+    # there doesn't appear to be an easy way of doing that.
+    reply+=(":${tag}:${desc}:(( ${wds[1]//\*}${term//(#m)[: \(\)]/\\$MATCH}:${wds[2]//(#m)[: \(\)]/\\$MATCH} ))")
+  fi
+  eval "reply+=($wds[3])"
+  if (( $i == $# )); then
+    reply+=(\))
+  else
+    reply+=(\|)
+  fi
+done
Index: Completion/Unix/Command/_ip
===================================================================
RCS file: Completion/Unix/Command/_ip
diff -N Completion/Unix/Command/_ip
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ Completion/Unix/Command/_ip	25 Feb 2007 22:28:52 -0000
@@ -0,0 +1,497 @@
+#compdef ip
+
+# This is based on iproute2-ss061214.
+# The manual and help text summary is not consistent with the main
+# manual text.  I have based this on the main manual text.
+
+#
+# Values encoding simple types
+#
+local -a subcmd_dev
+subcmd_dev=(/$'[[:alpha:]]##[[:digit:]]##\0'/ ':interfaces:network interface:_net_interfaces')
+
+local -a subcmd_onoff
+subcmd_onoff=(/$'(on|off)\0'/ ':onoff:state (on or off):(on off)')
+
+local -a subcmd_string
+subcmd_string=(/$'[^\0]#\0'/ ':string:arbitrary string:( )')
+
+local -a subcmd_number
+subcmd_number=(/$'[0-9]##\0'/ ':number:decimal number:( )')
+
+local xp='[[:xdigit:]][[:xdigit:]]'
+local -a subcmd_lladdr
+subcmd_lladdr=(/"${xp}:${xp}:${xp}:${xp}:${xp}:${xp}"$'\0'/ 
+':lladdress:link layer (MAC) address:( )')
+
+local -a subcmd_ipaddr
+subcmd_ipaddr=(
+  /$'(<->(.<->(.<->(.<->|)|)|)|[:[:xdigit]]#:[:[:xdigit:]]#)(|/<->)\0'/
+  ':ipaddress:IP address (v4 or v6) with optional /<network-prefix-length>:( )'
+)
+
+local -a subcmd_scope
+_regex_words scope "IP address scope" \
+  'global:address globally valid' \
+  'site:address valid for site (IPv6)' \
+  'link:address valid for single link' \
+  'host:address valid for this host'
+subcmd_scope=("$reply[@]")
+
+local -a subcmd_nud
+_regex_words nud "Neighbour Unreachability Detection state" \
+  'permanent:valid forever' \
+  'noarp:valid, not validated, removed if expired' \
+  'reachable:valid until reachability timeout' \
+  'stale:valid but suspicious'
+subcmd_nud=("$reply[@]")
+
+local -a subcmd_rttype
+_regex_words route-type "Route type" \
+  'unicast:real point-to-point route' \
+  'unreachable:generate Host Unreachable messages' \
+  'blackhole:silently discard' \
+  'prohibit:generate Communication Administratively Prohibited messages' \
+  'local:Loopback route' \
+  'broadcast:destinations are broadcast addresses' \
+  'throw:used with policy rules, generate Net Unreachable if no route' \
+  'nat:Network Address Translation route' \
+  'anycast:anycast addresses, not implemented' \
+  'multicast:multicast routing, not present in normal tables'
+subcmd_rttype=("$reply[@]")  
+
+local -a subcmd_route
+# Route type is optional in route.
+# There's an overall default but then the whole thing is missed
+# out, and it's marked as optional anyway.
+subcmd_route=("(" $subcmd_rttype "|" ")" $subcmd_ipaddr)
+
+local -a subcmd_tos
+_regex_words tos "type of service" \
+  'lowdelay:low latency' \
+  'throughput:high bulk throughput' \
+  'reliability:high reliability'
+subcmd_tos=("(" "$reply[@]" "|"
+  /$'(|0x)[[:xdigit:]]##\0'/ ":hex-number:8-bit hex number:( )" ")")
+
+local -a subcmd_lockmtu
+subcmd_lockmtu=("(" /$'lock\0'/ ":lock:lock:(lock)" "|" ")" $subcmd_number )
+
+local -a subcmd_nexthop
+_regex_words nexthop 'nexthop route keyword' \
+    'via:specify nexthop router:$subcmd_ipaddr' \
+    'dev:sepcify output device:$subcmd_dev' \
+    'weight:specify relative quality of route:$subcmd_number'
+subcmd_nexthop=("$reply[@]" "#")
+
+local -a subcmd_rtproto
+_regex_words rtprotocol 'route protocol' \
+  'redirect:installed from ICMP redirect' \
+  'kernel:installed automatically by kernel' \
+  'boot:installed during boot sequence' \
+  'static:installed by adminstrator' \
+  'ra:installed by Router Discovery protocol'
+subcmd_rtprotocol=("$reply[@]")
+
+local -a subcmd_rttable
+_regex_words rttable 'routing table' \
+  'local:local routes' \
+  'main:main routing table' \
+  'default:default routing table' \
+  'unspec:unspecified routing table'
+subcmd_rttable=("(" "$reply[@]" "|" $subcmd_number ")")
+
+local -a subcmd_rtrealm subcmd_rtrealms
+_regex_words -t / rtrealm 'routing realm' \
+  'cosmos:everywhere'
+subcmd_rtrealms=("(" "$reply[@]" "|" /$'[0-9]##/'/
+  ':number:decimal number:( )' ")")
+
+_regex_words rtrealm 'routing realm' \
+  'cosmos:everywhere'
+subcmd_rtrealm=("(" "$reply[@]" "|" $subcmd_number ")")
+subcmd_rtrealms+=($subcmd_rtrealm)
+
+local -a subcmd_rtselector
+_regex_words rtselmod 'routing selector modifier' \
+  'root:select minimum route prefix length to match' \
+  'match:select maximum route prefix length to match' \
+  'exact:select exact prefix length to match'
+subcmd_rtselector=("(" "$reply[@]" "|" ")" $subcmd_ipaddr)
+
+local -a subcmd_family
+_regex_words family 'protocol family' \
+  'inet:IPv4' \
+  'inet6:IPv6' \
+  'link:local, no networking protocol'
+subcmd_family=("$reply[@]")
+
+local -a subcmd_ruletypes
+_regex_words ruletype 'rule type' \
+  'unicast:rule applies to a route' \
+  'blackhole:rule silently drops packet' \
+  'unreachable:rule generates Network Unreachable messages' \
+  'prohibit:rule generates Communication Administratively Prohibited messages' \
+  'nat:rule prescribes translation of source IP address'
+subcmd_ruletypes=("$reply[@]")
+
+local -a subcmd_tunnelmode
+_regex_words tunnelmode 'tunnel mode' \
+  'ipip:IPv4 in IPv4 tunnel' \
+  'sit:Simple Internet Transition - IPv6 in IPv4 tunnel' \
+  'gre:Generic Route Encapsulation - IPv4/IPv6 in IPv4 tunnel'
+subcmd_tunnelmode=("$reply[@]")
+
+local -a subcmd_files
+subcmd_files=(/$'[^\0]##\0'/ ':file:file name:_files')
+
+
+#
+# The ip top-level commands.  First link
+#
+local -a link_set_cmds
+_regex_words \
+  link-set-commands 'link set commands' \
+  'dev:specify device:$subcmd_dev' \
+  'u*p:change state to up' \
+  'do*wn:change state do down' \
+  'ar*p:change ARP flag on device:$subcmd_onoff' \
+  'mu*lticast:change MULTICAST flag on device:$subcmd_onoff' \
+  'dy*namic:change DYNAMIC flag on device:$subcmd_onoff' \
+  'n*ame:change name of device:$subcmd_string' \
+  'txq*ueuelen:specify length of transmit queue:$subcmd_number' \
+  'txql*en:specify length of transmit queue:$subcmd_number' \
+  'm*tu:specify maximum transmit unit:$subcmd_number' \
+  'ad*dress:specify unicast link layer (MAC) address:$subcmd_lladdr' \
+  'br*oadcast:specify broadcast link layer (MAC) address:$subcmd_lladdr' \
+  'brd:specify broadcast link layer (MAC) address:$subcmd_lladdr' \
+  'p*eer:specify peer link layer (MAC) address:$subcmd_lladdr'
+# can complete interface with no dev, subcommands can repeat...
+link_set_cmds=("(" $subcmd_dev "|" ")" "$reply[@]" "#" )
+
+local -a link_show_cmds
+_regex_words link-show-commands 'link show commands' \
+  'dev:specify device:$subcmd_dev' \
+  'up:limit display to running devices'
+link_show_cmds=("(" $subcmd_dev "|" ")" "$reply[@]" "#" )
+
+local -a link_cmds
+_regex_words \
+  link-commands "link command" \
+  'se*t:change device attributes:$link_set_cmds' \
+  'sh*ow:display device attributes:$link_show_cmds'
+link_cmds=("$reply[@]")
+
+
+#
+# addr
+#
+local -a addr_add_cmds
+# TODO: broadcast can take + or =
+_regex_words addr-add-commands "addr add commands" \
+  'dev:specify device:$subcmd_dev' \
+  'lo*cal:specify local IP address:$subcmd_ipaddr' \
+  'p*eer:specify peer IP address (point-to-point):$subcmd_ipaddr' \
+  'b*roadcast:specify broadcast IP address:$subcmd_ipaddr' \
+  'la*bel:specify tag for device:$subcmd_string' \
+  's*cope:specify scope for address:$subcmd_scope'
+# can complete IP address with no keyword
+addr_add_cmds=("(" $subcmd_ipaddr "|" ")" "$reply[@]" "#" )
+
+local -a addr_show_cmds
+# TODO: broadcast can take + or =
+_regex_words addr-show-commands "addr show commands" \
+  'dev:specify device:$subcmd_dev' \
+  's*cope:specify scope for address:$subcmd_scope' \
+  't*o:limit to given IP address/prefix:$subcmd_ipaddr' \
+  'la*bel:list tags matching glob patter:$subcmd_string' \
+  'dynamic:list addresses from stateless configuration (IPv6)' \
+  'permanent:list non-dynamic addresses (IPv6)' \
+  'tentative:list addresses failing duplicate address detection (IPv6)' \
+  'deprecated:list deprecated addresses (IPv6)' \
+  'primary:list only primary addresses' \
+  'secondary:list only secondary addresses'
+# can complete device with no keyword
+addr_show_cmds=("(" $subcmd_dev "|" ")" "$reply[@]" "#" )
+
+local -a addr_cmds
+_regex_words \
+  addr-commands "addr command" \
+  'a*dd:add new protocol address:$addr_add_cmds' \
+  'd*elete:delete protocol address:$addr_add_cmds' \
+  's*how:show protocol address:$addr_show_cmds' \
+  'f*lush:flush protocol address:$addr_show_cmds'
+addr_cmds=("$reply[@]")
+
+
+#
+# neigh
+#
+local -a neigh_add_cmds
+_regex_words neigh-add-commands "neighbour add command" \
+  't*o:add new neighbour IP address:$subcmd_ipaddr' \
+  'dev:specify network device:$subcmd_dev' \
+  'l*ladr:specify link layer (MAC) address or null:$subcmd_lladdr' \
+  'n*ud:specify neighbour unreachability detection state:$subcmd_nud'
+# to-address without keyword can appear first
+neigh_add_cmds=( "(" $subcmd_ipaddr "|" ")" "$reply[@]" "#")
+
+local -a neigh_del_cmds
+_regex_words neigh-add-commands "neighbour delete command" \
+  't*o:remove neighbour IP address:$subcmd_ipaddr' \
+  'dev:specify network device:$subcmd_dev'
+neigh_del_cmds=( "(" $subcmd_ipaddr "|" ")" "$reply[@]" "#")
+
+local -a neigh_show_cmds
+_regex_words neigh-show-commands "neighbour show command" \
+  't*o:select neighbours by prefix:$subcmd_ipaddr' \
+  'dev:select neighbours by device:$subcmd_dev' \
+  'u*nused:only list unused neighbours' \
+  'n*ud:only list neighbours in given state:$subcmd_nud'
+neigh_show_cmds=( "(" $subcmd_ipaddr "|" ")" "$reply[@]" "#" )
+
+local -a neigh_cmds
+_regex_words \
+  neigh-commands "neigh command" \
+  'a*dd:add new neighbour entry:$neigh_add_cmds' \
+  'c*hange:change existing neighbour entry:$neigh_add_cmds' \
+  'r*eplace:add or change neighbour entry:$neigh_add_cmds' \
+  'd*elete:delete neighbour entry:$neigh_del_cmds' \
+  's*how:list neighbour entries:$neigh_show_cmds' \
+  'f*lush:flush neighbour entries:$neigh_show_cmds'
+neigh_cmds=("$reply[@]")
+
+
+#
+# route
+#
+local -a route_add_cmds
+_regex_words route-add-commands "route add/change/replace command" \
+  'to:route destination prefix:$subcmd_route' \
+  'tos:type of service:$subcmd_tos' \
+  'ds*field:type of service:$subcmd_tos' \
+  'me*tric:preference value of route:$subcmd_number' \
+  'pre*ference:preference value of route:$subcmd_number' \
+  'ta*ble:select table by ID:$subcmd_rttable' \
+  'dev:select device:$subcmd_dev' \
+  'v*ia:select nexthop router:$subcmd_ipaddr' \
+  'sr*c:select preferred source address:$subcmd_ipaddr' \
+  're*alm:select routing realm:$subcmd_rtrealm' \
+  'mtu:select maximum transport unit:$subcmd_lockmtu' \
+  'w*indow:select maximal window in bytes:$subcmd_number' \
+  'rtt:select round trip time estimate:$subcmd_number' \
+  'rttv*ar:select initial round trip variance estimate:$subcmd_number' \
+  'ss*thresh:select initial slow start threshold estimate:$subcmd_number' \
+  'cw*nd:select clamp for congestion window (only if locked):$subcmd_number' \
+  'in*itcwnd:select max initial cwnd in MSS:$subcmd_number' \
+  'ad*vmss:select maximal segment size advertised:$subcmd_number' \
+  're*ordering:select maximal reordering for path:$subcmd_number' \
+  'ne*xthop:select nexthop of multipath route:$subcmd_nexthop' \
+  'sc*ope:select scope of destinations:$subcmd_scope' \
+  'pro*tocol:select routing protocol identifier:$subcmd_rtprotocol' \
+  'onl*ink:pretend nexthop is directly attached' \
+  'eq*ualize:allow packet by packet randomization'
+# route can appear with no "to"
+route_add_cmds=("(" $subcmd_route "|" ")" "$reply[@]" "#")
+
+local -a route_show_cmds
+_regex_words route-show-commands "route show command" \
+  'to:select route via prefix:$subcmd_rtselector' \
+  'tos:type of service:$subcmd_tos' \
+  'ta*ble:select table by ID:$subcmd_rttable' \
+  'cl*oned:list only dynamically forked routes' \
+  'ca*ched:list only dynamically forked routes' \
+  'f*rom:select route via source address prefix:$subcmd_rtselector' \
+  'p*rotocol:select routing protocol identifier:$subcmd_rtprotocol' \
+  's*cope:select scope of destinations:$subcmd_scope' \
+  'ty*pe:route type:$subcmd_rttype' \
+  'dev:select device:$subcmd_dev' \
+  'v*ia:select nexthop router:$subcmd_ipaddr' \
+  's*rc:select route via source prefix:$subcmd_ipaddr' \
+  'realm:select routing realm:$subcmd_rtrealm' \
+  'realms:select from/to routing realms:$subcmd_rtrealms'
+# route selector can appear with no "to"
+route_show_cmds=("(" $subcmd_rtselector "|" ")" "$reply[@]" "#")
+
+local -a route_get_cmds
+_regex_words route-get-commands "route get commands" \
+  'to:route destination:$subcmd_ipaddr' \
+  'f*rom:route source:$subcmd_ipaddr' \
+  'tos:select type of service:$subcmd_tos' \
+  'ds*field:type of service:$subcmd_tos' \
+  'iif:select input interface (device):$subcmd_dev' \
+  'oif:select output interface (device):$subcmd_dev' \
+  'c*onnected:use preferred address as source'
+route_get_cmds=("(" $subcmd_ipaddr "|" ")" "$reply[@]" "#")
+
+local -a route_cmds
+_regex_words \
+  route-commands "route command" \
+  'a*dd:add new network route:$route_add_cmds' \
+  'c*hange:change existing network route:$route_add_cmds' \
+  'r*eplace:add or change network route:$route_add_cmds' \
+  'd*elete:delete network route:$route_add_cmds' \
+  's*how:list network routes:$route_show_cmds' \
+  'f*lush:flush network routes:$route_show_cmds' \
+  'g*et:get a single network route:$route_get_cmds'
+route_cmds=("$reply[@]")
+
+
+#
+# rule
+#
+local -a rule_add_cmds
+_regex_words rule-add-commands 'ip rule add/delete commands' \
+  'ty*xpe:type of rule:$subcmd_ruletypes' \
+  'fr*om:select source prefix:$subcmd_ipaddr' \
+  'to:select destination prefix:$subcmd_ipaddr' \
+  'iif:select input interface (device):$subcmd_dev' \
+  'tos:select type of service:$subcmd_tos' \
+  'ds*field:select type of service:$subcmd_tos' \
+  'fw*mark:select fwmark:$subcmd_string' \
+  'pr*iority:select unique priority for rule:$subcmd_number' \
+  'ta*ble:select routing table by ID:$subcmd_rttable' \
+  'realms:select from/to routing realms:$subcmd_rtrealms' \
+  'nat:select base of IP block to translate:$subcmd_ipaddr'
+
+rule_add_cmds=("(" $subcmd_ruletypes "|" ")" "$reply[@]" "#")
+
+local -a rule_cmds
+_regex_words \
+  rule-commands "rule command" \
+  'a*dd:insert a new routing rule:$rule_add_cmds' \
+  'd*elete:delete a routing rule:$rule_add_cmds' \
+  'f*lush:flush rules and dump deleted rules' \
+  's*how:list routing rules'
+_rule_cmds=("$reply[@]")
+
+
+#
+# tunnel
+#
+local -a tunnel_add_cmds
+_regex_words tunnel-add-commands 'tunnel add/change/delete commands' \
+  'name:select tunnel device name:$subcmd_dev' \
+  'mode:select tunnel mode:$subcmd_tunnelmode' \
+  'remote:select remote endpoint address:$subcmd_ipaddr' \
+  'local:select local address:$subcmd_ipaddr' \
+  'ttl:set fixed time to live, 1 to 255 or 0 (inherit):$subcmd_number' \
+  'tos:select type of service:$subcmd_tos' \
+  'ds*field:select type of service:$subcmd_tos' \
+  'dev:select device to bind tunnel to:$subcmd_dev' \
+  'nopmtudisc:disable path maximum transport unit discovery' \
+  'ikey:set input key for GRE tunnel:$subcmd_ipaddr' \
+  'okey:set output key for GRE tunnel:$subcmd_ipaddr' \
+  'key:set bidirectional key for GRE tunnel:$subcmd_ipaddr' \
+  'icsum:enable input checksums for GRE tunnel' \
+  'ocsum:enable output checksums for GRE tunnel' \
+  'csum:enable bidirectional checksums for GRE tunnel' \
+  'iseq:serialize input packets on GRE tunnel' \
+  'oseq:serialize output packets on GRE tunnel' \
+  'seq:serialize packets bidirectionally on GRE tunnel'
+# name is default... we always complete it as an interface,
+# although that's not really right for "add".
+tunnel_add_cmds=("$reply[@]")
+
+local -a tunnel_cmds
+_regex_words \
+  tunnel-commands "tunnel command" \
+  'a*dd:add a new IP tunnel:$tunnel_add_cmds' \
+  'c*hange:change an existing IP tunnel:$tunnel_add_cmds' \
+  'd*elete:destroy an IP tunnel:$tunnel_add_cmds' \
+  's*how:list IP tunnels'
+tunnel_cmds=("$reply[@]")
+
+
+#
+# maddr
+#
+local -a maddr_add_cmds
+_regex_words maddr-add-commands "maddr add/delete command" \
+  'a*ddress:select link layer (MAC) address:$subcmd_lladdr' \
+  'dev:select device to bind multicast address to:$subcmd_dev'
+maddr_add_cmds=("(" $subcmd_ipaddr "|" ")" "$reply[@]" "#")
+
+local -a maddr_show_cmds
+_regex_words maddr-show-commands 'maddr show command' \
+  'dev:select device with multicast address(es):$subcmd_dev'
+# device is default argument but if given the "dev" is useless
+maddr_show_cmds=("(" $subcmd_dev "|" "$reply[@]" ")")
+
+local -a maddr_cmds
+_regex_words \
+  maddr-commands "maddr command" \
+  'a*dd:add multicast address:$maddr_add_cmds' \
+  'd*elete:delete multicast address:$maddr_add_cmds' \
+  's*how:list multicast addresses:$maddr_show_cmds'
+_maddr_cmds=("$reply[@]")
+
+
+#
+# mroute
+#
+local -a mroute_show_cmds
+_regex_words mroute-show-comnands "mroute show command" \
+  'to:select destination prefix:$subcmd_ipaddr' \
+  'iif:select input interface (device):$subcmd_dev' \
+  'from:select source prefix:$subcmd_ipaddr'
+mroute_show_cmds=("(" $subcmd_ipaddr "|" ")" "$reply[@]")
+
+local -a mroute_cmds
+_regex_words \
+  mroute-commands "mroute command" \
+  's*how:list multicast routing cache entries:$mroute_show_cmds'
+mroute_cmds=("$reply[@]")
+
+
+#
+# monitor
+#
+local -a monitor_cmds
+_regex_words \
+  monitor-commands "monitor command" \
+  'all:monitor all changes' \
+  'link:monitor changes to links' \
+  'address:monitor changes to addresses' \
+  'route:monitor changes to routes' \
+  'file:read rtmon-generated log:$subcmd_files'
+monitor_cmds=("$reply[@]")
+
+
+#
+# Global argument handling
+#
+
+# Arguments to _regex_arguments, built up in array $args.
+local -a args reply
+args=(
+    # Command word.  Don't care what that is.
+    /$'[^\0]#\0'/
+)
+
+_regex_words options "ip options" \
+  '-s*tatistics:output statistics' \
+  '-f*amily:select protocol family:$subcmd_family' \
+  '-4:IPv4' \
+  '-6:IPv6' \
+  '-0:link protocol, no networking' \
+  '-o*neline:output one record per line' \
+  '-r*esolve:use system resolver for DNS names'
+args+=("$reply[@]" "#")
+
+_regex_words \
+  commands "ip command" \
+  'l*ink:configure network device:$link_cmds' \
+  'a*ddr:manage protocol address:$addr_cmds' \
+  'ro*ute:manage routing table:$route_cmds' \
+  'ru*le:manage routing policy database:$rule_cmds' \
+  'n*eigh:manage neighbour/ARP tables:$neigh_cmds' \
+  't*unnel:configure tunnel:$tunnel_cmds' \
+  'ma*ddr:manage multicast addresses:$maddr_cmds' \
+  'mr*oute:manage multicast routing cache:$mroute_cmds' \
+  'mo*nitor:monitor state:$monitor_cmds'
+args+=("$reply[@]")
+
+_regex_arguments _ip "${args[@]}"
+
+_ip "$@"
Index: Doc/Zsh/compsys.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/compsys.yo,v
retrieving revision 1.196
diff -u -r1.196 compsys.yo
--- Doc/Zsh/compsys.yo	1 Dec 2006 10:23:06 -0000	1.196
+++ Doc/Zsh/compsys.yo	25 Feb 2007 22:28:54 -0000
@@ -4233,7 +4233,7 @@
 This function generates a completion function var(name) which matches
 the specifications var(spec) tt(...), a set of regular expressions as
 described below.  After running tt(_regex_arguments), the function
-var(name) should be called at the appropriate point.
+var(name) should be called as a normal completion function.
 The pattern to be matched is given by the contents of
 the tt(words) array up to the current cursor position joined together
 with null characters; no quotation is applied.
@@ -4274,6 +4274,16 @@
 The var(pattern) string `tt([])' is guaranteed never to match.
 The var(lookahead) is not stripped from the command line before the next
 pattern is examined.
+
+The argument starting with tt(:) is used in the same manner as an argument to
+tt(_alternative).
+
+A component is used as follows: var(pattern) is tested to
+see if the component already exists on the command line.  If
+it does, any following specifications are examined to find something to
+complete.  If a component is reached but no such pattern exists yet on the
+command line, the string containing the var(action) is used to generate
+matches to insert at that point.
 )
 item(tt(/)var(pattern)tt(/+) [tt(%)var(lookahead)tt(%)] [tt(-)var(guard)] [tt(:)var(tag)tt(:)var(descr)tt(:)var(action)])(
 This is similar to `tt(/)var(pattern)tt(/) ...' but the left part of the
@@ -4300,6 +4310,94 @@
 Either of the two var(spec)s can be matched.
 )
 enditem()
+
+The function tt(_regex_words) can be used as a helper function to
+generate matches for a set of alternative words possibly with
+their own arguments as a command line argument.
+
+Examples:
+
+example(_regex_arguments _tst /$'[^\0]#\0'/ \ 
+/$'[^\0]#\0'/ :'compadd aaa')
+
+This generates a function tt(_tst) that completes tt(aaa) as its only
+argument.  The var(tag) and var(description) for the action have been
+omitted for brevity (this works but is not recommended in normal use).
+The first component matches the command word, which is arbitrary; the
+second matches  any argument.  As the argument is also arbitrary, any
+following component would not depend on tt(aaa) being present.
+
+example(_regex_arguments _tst /$'[^\0]#\0'/ \ 
+/$'aaa\0'/ :'compadd aaa')
+
+This is a more typical use; it is similar, but any following patterns
+would only match if tt(aaa) was present as the first argument.
+
+example(_regex_arguments _tst /$'[^\0]#\0'/ \( \ 
+/$'aaa\0'/ :'compadd aaa' \ 
+/$'bbb\0'/ :'compadd bbb' \) \#)
+
+In this example, an indefinite number of command arguments may be
+completed.  Odd arguments are completed as tt(aaa) and even arguments
+as tt(bbb).  Completion fails unless the set of tt(aaa) and tt(bbb)
+arguments before the current one is matched correctly.
+
+example(_regex_arguments _tst /$'[^\0]#\0'/ \ 
+\( /$'aaa\0'/ :'compadd aaa' \| \ 
+/$'bbb\0'/ :'compadd bbb' \) \#)
+
+This is similar, but either tt(aaa) or tt(bbb) may be completed for
+any argument.  In this case tt(_regex_words) could be used to generate
+a suitable expression for the arguments.
+
+)
+findex(_regex_words)
+item(tt(_regex_words) var(tag) var(description) var(spec) ...)(
+This function can be used to generate arguments for the
+tt(_regex_arguments) command which may be inserted at any point where
+a set of rules is expected.  The var(tag) and var(description) give a
+standard tag and description pertaining to the current context.  Each
+var(spec) contains two or three arguments separated by a colon: note
+that there is no leading colon in this case.
+
+Each var(spec) gives one of a set of words that may be completed at
+this point, together with arguments.  It is thus roughly equivalent to
+the tt(_arguments) function when used in normal (non-regex) completion.
+
+The part of the var(spec) before the first colon is the word to be
+completed.  This may contain a tt(*); the entire word, before and after
+the tt(*) is completed, but only the text before the tt(*) is required
+for the context to be matched, so that further arguments may be
+completed after the abbreviated form.
+
+The second part of var(spec) is a description for the word being
+completed.
+
+The optional third part of the var(spec) describes how words following
+the one being completed are themselves to be completed.  It will be
+evaluated in order to avoid problems with quoting.  This means that
+typically it contains a reference to an array containing previously
+generated regex arguments.
+
+The result of the processing by tt(_regex_words) is placed in the array
+tt(reply), which should be made local to the calling function.
+If the set of words and arguments may be matched repeatedly, a tt(#)
+should be appended to the generated array at that point.
+
+For example:
+
+example(local -a reply
+_regex_words mydb-commands 'mydb commands' \ 
+  'add:add an entry to mydb:$mydb_add_cmds' \ 
+  'show:show entries in mydb'
+_regex_arguments _mydb "$reply[@]"
+_mydb "$@")
+
+This shows a completion function for a command tt(mydb) which takes
+two command arguments, tt(add) and tt(show).  tt(show) takes no arguments,
+while the arguments for tt(add) have already been prepared in an
+array tt(mydb_add_cmds), quite possibly by a previous call to
+tt(_regex_words).
 )
 findex(_requested)
 item(tt(_requested) [ tt(-x) ] [ tt(-12VJ) ] var(tag) [ var(name) var(descr) [ var(command) var(args) ... ] ])(

-- 
Peter Stephenson <p.w.stephenson@xxxxxxxxxxxx>
Web page now at http://homepage.ntlworld.com/p.w.stephenson/



Messages sorted by: Reverse Date, Date, Thread, Author