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

Zsh OpenStack completions



Hi,

Below is a patch to add completions for several OpenStack related
command line clients, including the new common client, openstack(1).

There are three categories of clients:

1) newer clients which provide per-command sub-commands and options
  - output is a Bash function, the relevant part being something like:

  cmds='alarm alarm-history capabilities complete help'
  cmds_alarm='create delete list show update'
  cmds_alarm_history='search show'
  cmds_alarm_history_search='-h --help -f --format -c --column --max-width --noindent --quote --query'

2) old skool clients which provide the "bash-completion" command
  - output is something like (doesn't separate client/command opts):

--tenant_id floatingip-delete bgp-peer-delete --default-prefixlen net-create [...]

  - command options are available with "$client help $command"

3) an oddball, swift, slightly different than 2)

We cache things pretty aggressively as something like "neutron help"
takes ~0.75s on my laptop (compared to 0.002 of "ls --help") and
openstack(1) even goes over the network for the list of completions.

The code is client/command agnostic, the only special case is the
"help" command which is available with clients in the categories 1)
and 2) and is very helpful for most users.

I suspect I'll get a response suggesting to use something else that
_values in if/else - I couldn't see more elegant way to achieve the
current behavior but if you have any ideas, please provide a quick
example. (Also, searching for (sub)commands from the associative
array doesn't look very nice but I'm not sure how to do that better
while still making sure that e.g. net-list and net-list-on-dhcp-agent
are not being confused - perhaps I missed a flag to aid in this.)

There's one case which I'm not sure there's a perfect solution without
hard-coding / special-casing lots of commands/options: some commands
of some clients accept some options more than once - for example, for
"openstack project create" the "--property" option could be repeated
(to provide several key=value pairs). So if trying to stay generic,
we'd need to accept several options in all cases (which is mostly
incorrect) or accept options only once and force the user to type
the option if wanted more than once. I've chosen the latter as
it would seem that in most cases there's some typing needed anyway
for repetitive options (like key=value pairs, DNS servers, IP
addresses, etc), so while not perfect this doesn't strike me too
cumbersome.

Due to sheer volume of different commands and options I haven't tested
all the possible combinations but I've tested each listed client and
at least few commands / options and haven't see any issue. The clients
included are based on the list mentioned in the links and available
on RH OSP 9 (Mitaka). I'm aware that the list might not be complete
compared to what is available so if you have a chance to verify any
additional clients, please consider amending the list of clients. I'll
revisit the list later this year once RH OSP 10 (Newton) is out.

Here's a quick screenshot:

% cinder -<TAB>
--bypass-url              --os-project-domain-id    --os-username           
--debug                   --os-project-domain-name  --os-volume-api-version 
--endpoint-type           --os-project-id           --profile               
--insecure                --os-project-name         --retries               
--os-auth-strategy        --os-region-name          --service-name          
--os-auth-system          --os-tenant-id            --service-type          
--os-auth-url             --os-tenant-name          --timeout               
--os-cacert               --os-token                --version               
--os-cert                 --os-url                  --volume-service-name   
--os-endpoint-type        --os-user-domain-id       -d                      
--os-key                  --os-user-domain-name                             
--os-password             --os-user-id                                      
% nova <TAB>
zsh: do you wish to see all 209 possibilities (105 lines)? n
% neutron ne<TAB>
net-create                net-gateway-disconnect    net-list                
net-delete                net-gateway-list          net-list-on-dhcp-agent  
net-external-list         net-gateway-show          net-show                
net-gateway-connect       net-gateway-update        net-update              
net-gateway-create        net-ip-availability-list                          
net-gateway-delete        net-ip-availability-show                          
% neutron net-create <TAB>
--admin-state-down           --provider:physical_network
--availability-zone-hint     --provider:segmentation_id 
--column                     --qos-policy               
--dns-domain                 --request-format           
--format                     --shared                   
--help                       --tenant-id                
--max-width                  --vlan-transparent         
--noindent                   -c                         
--prefix                     -f                         
--provider:network_type      -h                         
% aodh he<TAB>
% aodh help al<TAB>
alarm alarm-history
% aodh help alarm <TAB>
create delete list show update

---
 Completion/Unix/Command/_openstack | 136 +++++++++++++++++++++++++++++++++++++
 1 file changed, 136 insertions(+)
 create mode 100644 Completion/Unix/Command/_openstack

diff --git a/Completion/Unix/Command/_openstack b/Completion/Unix/Command/_openstack
new file mode 100644
index 0000000..1b82bfa
--- /dev/null
+++ b/Completion/Unix/Command/_openstack
@@ -0,0 +1,136 @@
+#compdef openstack aodh barbican ceilometer cinder cloudkitty designate glance gnocchi heat ironic keystone magnum manila mistral monasca murano neutron nova sahara senlin swift trove
+
+# https://wiki.openstack.org/wiki/OpenStackClients
+# http://docs.openstack.org/user-guide/common/cli-install-openstack-command-line-clients.html
+
+local curcontext="$curcontext" state line expl ret=1
+
+local -a clnts_compl_new clnts_compl_old clnts_swift_like
+
+clnts_compl_new=( aodh barbican designate gnocchi openstack )
+clnts_compl_old=( ceilometer cinder cloudkitty glance heat ironic keystone magnum manila mistral monasca murano neutron nova sahara senlin trove )
+clnts_swift_like=( swift )
+
+if (( ! $+_cache_openstack_clnt_opts )); then
+  typeset -gA _cache_openstack_clnt_outputs
+  typeset -gA _cache_openstack_clnt_opts
+  typeset -gA _cache_openstack_clnt_cmds
+  typeset -gA _cache_openstack_clnt_cmds_opts
+  typeset -gA _cache_openstack_clnt_cmds_subcmds
+  typeset -gA _cache_openstack_clnt_cmds_subcmd_opts
+fi
+
+local -a conn_opts
+local opt arg word
+if [[ $service == openstack && -n ${words[(r)--os-*]} ]]; then
+  if (( ! $+_cache_openstack_conn_opts )); then
+    _cache_openstack_conn_opts=( ${(M)${=${(f)"$($service help 2>/dev/null)"}}:#--os-*} )
+  fi
+  for opt in ${=_cache_openstack_conn_opts} --os-tenant-id --os-tenant-name; do
+    arg=
+    for word in ${words:1}; do
+      [[ $word == $opt ]] && arg=$word && break
+    done
+    [[ -n $arg && -n ${arg##-*} ]] && conn_opts=( $conn_opts $opt $arg )
+  done
+fi
+
+if [[ -n ${clnts_compl_new[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    _cache_openstack_clnt_outputs[$service]=${:-"$($service ${(Q)conn_opts} complete 2>/dev/null)"}
+    _cache_openstack_clnt_opts[$service]=${${${${(M)${${${${=${(f)"$($service help 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}%--os-}
+    _cache_openstack_clnt_cmds[$service]=${${${${_cache_openstack_clnt_outputs[$service]}/* cmds=\'}/\'*}/complete}
+  fi
+  local cmd subcmd
+  for word in ${words:1}; do
+    [[ $_cache_openstack_clnt_cmds[$service] != ${${${_cache_openstack_clnt_cmds[$service]#$word }% $word}/ $word } ]] && cmd=$word && break
+  done
+  if [[ -n $cmd && -z $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]]; then
+      local t=cmds_${cmd//-/_}
+      _cache_openstack_clnt_cmds_subcmds[$service$cmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*}
+  fi
+  if [[ -n $cmd ]]; then
+    for word in ${words:2}; do
+      [[ $_cache_openstack_clnt_cmds_subcmds[$service$cmd] != ${${${_cache_openstack_clnt_cmds_subcmds[$service$cmd]#$word }% $word}/ $word } ]] && subcmd=$word && break
+    done
+    if [[ -n $subcmd && -z $_cache_openstack_clnt_cmds_subcmd_opts[$service$cmd$subcmd] ]]; then
+      local t=cmds_${cmd//-/_}_${subcmd//-/_}
+      _cache_openstack_clnt_cmds_subcmd_opts[$service$cmd$subcmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*}
+    fi
+  fi
+  if [[ $cmd == help ]]; then
+      if [[ $words[CURRENT-1] == $cmd && $words[CURRENT] != -* ]]; then
+        [[ -n $_cache_openstack_clnt_cmds[$service] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+      elif [[ $words[CURRENT-2] == $cmd && $words[CURRENT-1] != -* && $words[CURRENT] != -* ]]; then
+        local cmd=$words[CURRENT-1]
+        local t=cmds_${cmd//-/_}
+        [[ -z $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _cache_openstack_clnt_cmds_subcmds[$service$cmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*}
+        [[ -n $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmds[$service$cmd]} && ret=0
+      else
+        _values -w option help && ret=0
+      fi
+  elif [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  elif [[ -z $cmd ]]; then
+    if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+      _message "missing authentication options"
+    else
+      _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+    fi
+  elif [[ -z $subcmd ]]; then
+    [[ -n $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmds[$service$cmd]} && ret=0
+  else  
+    [[ -n $_cache_openstack_clnt_cmds_subcmd_opts[$service$cmd$subcmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmd_opts[$service$cmd$subcmd]//\:/\\\:} && ret=0
+  fi
+
+elif [[ -n ${clnts_compl_old[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    _cache_openstack_clnt_opts[$service]=${${${(M)${${${${=${(f)"$($service help 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}
+    _cache_openstack_clnt_cmds[$service]=${${(M)${=${(f)"$($service bash-completion 2>/dev/null)"}}:#[A-Za-z]*}/bash-completion}
+  fi
+  local cmd
+  for word in ${words:1}; do
+    [[ $_cache_openstack_clnt_cmds[$service] != ${${${_cache_openstack_clnt_cmds[$service]#$word }% $word}/ $word } ]] && cmd=$word && break
+  done
+  # Mostly no options for help, prevent consecutive calls with help here
+  if [[ -n $cmd && $cmd != help && -z $_cache_openstack_clnt_cmds_opts[$service] ]]; then
+    _cache_openstack_clnt_cmds_opts[$service$cmd]=${${${(M)${${${${=${(f)"$($service help $cmd 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}
+  fi
+  if [[ $cmd == help ]]; then
+      if [[ $words[CURRENT-1] == help && $words[CURRENT] != -* ]]; then
+        _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+      else
+        _values -w option help && ret=0
+      fi
+  elif [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  elif [[ -z $cmd ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+  else
+    [[ -n $_cache_openstack_clnt_cmds_opts[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_opts[$service$cmd]//\:/\\\:} && ret=0
+  fi
+
+elif [[ -n ${clnts_swift_like[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    _cache_openstack_clnt_outputs[$service]=${(f)"$($service --help 2>/dev/null)"}
+    _cache_openstack_clnt_opts[$service]=${${${${(M)${${${${=_cache_openstack_clnt_outputs[$service]}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}/=*}
+    _cache_openstack_clnt_cmds[$service]=${=${(M)${(M)${(f)_cache_openstack_clnt_outputs[$service]}:#    [a-z]*}/ [A-Z]*}}
+  fi
+  local cmd
+  for word in ${words:1}; do
+    [[ $_cache_openstack_clnt_cmds[$service] != ${${${_cache_openstack_clnt_cmds[$service]#$word }% $word}/ $word } ]] && cmd=$word && break
+  done
+  if [[ -n $cmd && -z $_cache_openstack_clnt_cmds_opts[$service] ]]; then
+    _cache_openstack_clnt_cmds_opts[$service$cmd]=${${${(M)${${${${=${(f)"$($service $cmd --help 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}
+  fi
+  if [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  elif [[ -z $cmd ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+  else
+    [[ -n $_cache_openstack_clnt_cmds_opts[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_opts[$service$cmd]//\:/\\\:} && ret=0
+  fi
+
+fi
+
+return ret

Thanks,

-- 
Marko Myllynen



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