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

Re: Zsh OpenStack completions



Hi,

I updated the patch quickly to address some of your comments but based
on Eric's reply it sounds like we also need to discuss should we
include these completions in zsh upstream in the first place.

On 2016-09-07 02:37, Daniel Shahaf wrote:
> Marko Myllynen wrote on Tue, Sep 06, 2016 at 15:39:02 +0300:
>>
>> 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
>> 2) old skool clients which provide the "bash-completion" command
>> 3) an oddball, swift, slightly different than 2)
> 
> It would be useful to have this information in a comment above the
> declaration of the corresponding arrays.
> 
> More generally: having block-level comments would be useful.  See for
> example the computil.c part of 39173.  Without them the code would be
> harder to work on in the future.

Fair enough, added some comments.

>> (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.)
> 
> I assume you refer to this line? —
> 
>   for word in ${words:1}; do
>     [[ $_cache_openstack_clnt_cmds[$service] != ${${${_cache_openstack_clnt_cmds[$service]#$word }% $word}/ $word } ]] && cmd=$word && break
>   done

Yup.

> Or you could use the (w) flag:
> .
>     % s="foo foobar"   
>     % print -r -- $s[(wI)foo]
>     1
>     % print -r -- $s[(wI)bar]
>     0

Ok, this works now:

    local s=${_cache_openstack_clnt_cmds[$service]}
    [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break

Seems that the additional variable is needed here.

>> 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).
> 
> I'm not sure what's the problem here: whether it's needing to write code
> to permit select options to be specified multiple times or having to
> enumerate all options that may be multiply-specified.

Both :) For example, openstack has ~310 commands, neutron ~260
commands, nova ~210 commands, and so forth - at least I don't have the
cycles to go through all of them and their options manually (which
should happen at least partially twice a year, after each release).

Trying to automagically parse the information is not straightforward,
for example glance help image-create says:

  --property <key=value>
                        Arbitrary property to associate with image. May be
                        used multiple times.

While openstack help project create says:

  --property <key=value>
                        Add a property to <name> (repeat option to set
                        multiple properties)

And neutron help subnet-create says:

  --dns-nameserver DNS_NAMESERVER
                        DNS name server for this subnet (This option can be
                        repeated).


So there are the strings "repeat" and "multiple" which might serve as
a hint. But we don't know a) whether those are used everywhere for
options that can be used multiple times or b) whether they would
introduce false-positives.

Updated patch below.

---
 Completion/Unix/Command/_openstack | 160 +++++++++++++++++++++++++++++++++++++
 1 file changed, 160 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..9fc2760
--- /dev/null
+++ b/Completion/Unix/Command/_openstack
@@ -0,0 +1,160 @@
+#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
+
+#
+# We support three different client categories:
+#  1) Clients with new style complete command where output is 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) Clients with old style bash-completion command which does
+#     not separate options and commands:
+#
+#    --tenant_id floatingip-delete bgp-peer-delete --default-prefixlen net-create [...]
+#
+#  3) Swift, slightly different from 2)
+#
+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 )
+
+# Python clients take quite some time to start up and some (openstack(1))
+# even go over the network for completions so we cache things pretty hard
+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
+# Only openstack(1) requires parameters to provide completion info
+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
+  # --os-tenant-id --os-tenant-name are deprecated but still widely used
+  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
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && 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
+      local s=${_cache_openstack_clnt_cmds_subcmds[$service$cmd]}
+      [[ $s[(wI)$word] -gt 0 ]] && 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
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && 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
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && 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