zsh-workers
 help / color / mirror / code / Atom feed
* Zsh OpenStack completions
@ 2016-09-06 12:39 Marko Myllynen
  2016-09-06 23:23 ` Eric Cook
  2016-09-06 23:37 ` Daniel Shahaf
  0 siblings, 2 replies; 8+ messages in thread
From: Marko Myllynen @ 2016-09-06 12:39 UTC (permalink / raw)
  To: zsh workers

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Zsh OpenStack completions
  2016-09-06 12:39 Zsh OpenStack completions Marko Myllynen
@ 2016-09-06 23:23 ` Eric Cook
  2016-09-07  7:12   ` Marko Myllynen
  2016-09-06 23:37 ` Daniel Shahaf
  1 sibling, 1 reply; 8+ messages in thread
From: Eric Cook @ 2016-09-06 23:23 UTC (permalink / raw)
  To: zsh-workers

On 09/06/2016 08:39 AM, Marko Myllynen wrote:
> Hi,
> 
> Below is a patch to add completions for several OpenStack related
> command line clients, including the new common client, openstack(1).
> 

Adding shell completion for openstack seems like something that is
better fit committing to their respective project(s). Since it's a
very active moving target I would imagine by the completer becoming
dated fairly quickly. Committing it there would also (possibly) have
more eyeballs that actually use openstack to help out too.

_systemd would be the most recent example of this. We had a completer
distributed with zsh. It wasn't kept up to date and people started to
maintain completers in the systemd git repo. It actually caused
confusion in #zsh for a few people when they noticed some commands wasn't
showing all of the options expected on some systems and not others. Since
two completers for systemd's utilities were in $fpath where as another
OS decided to delete _systemd from zsh's source when packaging it.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Zsh OpenStack completions
  2016-09-06 12:39 Zsh OpenStack completions Marko Myllynen
  2016-09-06 23:23 ` Eric Cook
@ 2016-09-06 23:37 ` Daniel Shahaf
  2016-09-07  9:31   ` Marko Myllynen
  1 sibling, 1 reply; 8+ messages in thread
From: Daniel Shahaf @ 2016-09-06 23:37 UTC (permalink / raw)
  To: Marko Myllynen; +Cc: zsh workers

Marko Myllynen wrote on Tue, Sep 06, 2016 at 15:39:02 +0300:
> 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
> 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.

> 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.

A brilliant example of the pendulum tendency to take us all back to the
dumb terminals era of the 70's :-)

> (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

You could get around the triple % # / check by adding a space before and
after the string, i.e., let the associative array's value be
    " foo bar baz "
and then just grep for " foo " (with the spaces).

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

Or you could call use a different variable name for each command, i.e.,
«typeset -a _cache_openstack_magnum_subcommands» and so on.  You'd then
use the (P) parameter expansion flag to access the variable:
.
    cachevar=_cache_openstack_${service}_subcommands
    typeset -a $cachevar
    print -rl -- ${(P)cachevar}
    compadd -a -- $cachevar

You may also need to change ${word} to ${(b)word} inside the condition
to prevent $word from being interpreted as a pattern.  I know I said
that [offlist] above virsh too, I haven't made the change there yet but
I think I will later.

> 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.

If it's the former: _arguments solves that for you:
.
    _f() _arguments : '*-x:foo:_hosts'
    compdef _f f
    f -x bar -<TAB><TAB>

If it's the latter: the alternative to hardcoding a list would be to
parse that information from the --help or --completion output.  This may
mean getting openstack upstream to emit that information therein.

(Tangentially: I wish there were a standard format for parseable help;
basically a variation of --help that emitted information that
(a) were machine parseable, (b) could be compiled into a zsh completion
function.)

> 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.

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

I'll go through the diff later.

Cheers,

Daniel


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Zsh OpenStack completions
  2016-09-06 23:23 ` Eric Cook
@ 2016-09-07  7:12   ` Marko Myllynen
  0 siblings, 0 replies; 8+ messages in thread
From: Marko Myllynen @ 2016-09-07  7:12 UTC (permalink / raw)
  To: zsh-workers

Hi,

On 2016-09-07 02:23, Eric Cook wrote:
> On 09/06/2016 08:39 AM, Marko Myllynen wrote:
>>
>> Below is a patch to add completions for several OpenStack related
>> command line clients, including the new common client, openstack(1).
> 
> Adding shell completion for openstack seems like something that is
> better fit committing to their respective project(s). Since it's a
> very active moving target I would imagine by the completer becoming
> dated fairly quickly. Committing it there would also (possibly) have
> more eyeballs that actually use openstack to help out too.

Yes, it is *very* fast moving target, that's why the implementation is
client/command agnostic - I'm unaware of any client ever switching from
the old style bash-completion command to the new style complete command
so that's the reason I think this should be relatively stable approach;
I'm sure nobody here wants to / can keep track of the each client
supported already (more perhaps to come). Dealing with over dozen
OpenStack projects to get completions included (and then later making
sure downstream packagers include them as well) also sounds like quite a
lot of work.

> _systemd would be the most recent example of this. We had a completer
> distributed with zsh. It wasn't kept up to date and people started to
> maintain completers in the systemd git repo. It actually caused
> confusion in #zsh for a few people when they noticed some commands wasn't
> showing all of the options expected on some systems and not others. Since
> two completers for systemd's utilities were in $fpath where as another
> OS decided to delete _systemd from zsh's source when packaging it.

I think _systemd completions are a good fit for systemd upstream (like
_pcp completions for PCP upstream which I contributed some time earlier
this year). However, given the fact that there's nothing OpenStack
client/command specific here except for the bash-completion vs complete
vs swift divide I think it would be feasible to have _openstack in zsh
upstream.

Thanks,

-- 
Marko Myllynen


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Zsh OpenStack completions
  2016-09-06 23:37 ` Daniel Shahaf
@ 2016-09-07  9:31   ` Marko Myllynen
  2016-09-09  6:38     ` Marko Myllynen
  0 siblings, 1 reply; 8+ messages in thread
From: Marko Myllynen @ 2016-09-07  9:31 UTC (permalink / raw)
  To: Daniel Shahaf; +Cc: zsh workers

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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Zsh OpenStack completions
  2016-09-07  9:31   ` Marko Myllynen
@ 2016-09-09  6:38     ` Marko Myllynen
  2016-09-19  6:24       ` Marko Myllynen
  0 siblings, 1 reply; 8+ messages in thread
From: Marko Myllynen @ 2016-09-09  6:38 UTC (permalink / raw)
  To: Daniel Shahaf; +Cc: zsh workers

Hi,

On 2016-09-07 12:31, Marko Myllynen wrote:
> 
> 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.

Here's an updated patch based on initial review comments from Daniel,
most notably more comments added.

---
 Completion/Unix/Command/_openstack | 189 +++++++++++++++++++++++++++++++++++++
 1 file changed, 189 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..ce333a2
--- /dev/null
+++ b/Completion/Unix/Command/_openstack
@@ -0,0 +1,189 @@
+#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
+
+# New style clients
+if [[ -n ${clnts_compl_new[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    # Populate caches - clnt_outputs is command raw output used later
+    _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
+  # Determine the command
+  for word in ${words:1}; do
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break
+  done
+  # Populate the subcommands cache
+  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
+  # Determine the subcommand
+  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
+    # Populate the subcommand options cache
+    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
+  # Special treatment for the help command
+  if [[ $cmd == help ]]; then
+      if [[ $words[CURRENT-1] == $cmd && $words[CURRENT] != -* ]]; then
+        # Offer commands
+        [[ -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
+        # Offer subcommands
+        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
+        # Handle help<TAB> properly
+        _values -w option help && ret=0
+      fi
+  # Client options
+  elif [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  # Commands
+  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
+  # Subcommands
+  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
+  # Subcommand options
+  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
+
+# Old style clients
+elif [[ -n ${clnts_compl_old[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    # Populate caches
+    _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
+  # Determine the command
+  for word in ${words:1}; do
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break
+  done
+  # Populate the command options cache
+  # 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
+  # Special treatment for the help command
+  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
+  # Client options
+  elif [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  # Commands
+  elif [[ -z $cmd ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+  # Command options
+  else
+    [[ -n $_cache_openstack_clnt_cmds_opts[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_opts[$service$cmd]//\:/\\\:} && ret=0
+  fi
+
+# Swift like clients
+elif [[ -n ${clnts_swift_like[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    # Populate caches - clnt_outputs is command raw output used later
+    _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
+  # Determine the command
+  for word in ${words:1}; do
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break
+  done
+  # Populate the command options cache
+  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
+  # Client options
+  if [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  # Commands
+  elif [[ -z $cmd ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+  # Command options
+  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


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Zsh OpenStack completions
  2016-09-09  6:38     ` Marko Myllynen
@ 2016-09-19  6:24       ` Marko Myllynen
  2016-09-27  5:43         ` Marko Myllynen
  0 siblings, 1 reply; 8+ messages in thread
From: Marko Myllynen @ 2016-09-19  6:24 UTC (permalink / raw)
  To: zsh workers

Hi,

On 2016-09-09 09:38, Marko Myllynen wrote:
> On 2016-09-07 12:31, Marko Myllynen wrote:
>>
>> 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.
> 
> Here's an updated patch based on initial review comments from Daniel,
> most notably more comments added.
> 
> ---
>  Completion/Unix/Command/_openstack | 189 +++++++++++++++++++++++++++++++++++++
>  1 file changed, 189 insertions(+)
>  create mode 100644 Completion/Unix/Command/_openstack

There were not further comments on this, how should we proceed? I think
the lower level technical aspects should be pretty much ok now but does
this look like something that would be appropriate for zsh upstream?

Thanks,

-- 
Marko Myllynen


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Zsh OpenStack completions
  2016-09-19  6:24       ` Marko Myllynen
@ 2016-09-27  5:43         ` Marko Myllynen
  0 siblings, 0 replies; 8+ messages in thread
From: Marko Myllynen @ 2016-09-27  5:43 UTC (permalink / raw)
  To: zsh workers

Hi,

On 2016-09-19 09:24, Marko Myllynen wrote:
> On 2016-09-09 09:38, Marko Myllynen wrote:
>> On 2016-09-07 12:31, Marko Myllynen wrote:
>>>
>>> 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.
>>
>> Here's an updated patch based on initial review comments from Daniel,
>> most notably more comments added.
> 
> There were not further comments on this, how should we proceed? I think
> the lower level technical aspects should be pretty much ok now but does
> this look like something that would be appropriate for zsh upstream?

Below is an updated patch which, based on Oliver's suggestion,
makes the subcommand option completion conditional based on:
{ ! zstyle -T ":completion:${curcontext}:options" prefix-needed || [[ -prefix - ]] }

---
 Completion/Unix/Command/_openstack | 192 +++++++++++++++++++++++++++++++++++++
 1 file changed, 192 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..39fa30c
--- /dev/null
+++ b/Completion/Unix/Command/_openstack
@@ -0,0 +1,192 @@
+#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
+
+# New style clients
+if [[ -n ${clnts_compl_new[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    # Populate caches - clnt_outputs is command raw output used later
+    _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
+  # Determine the command
+  for word in ${words:1}; do
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break
+  done
+  # Populate the subcommand cache
+  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
+  # Determine the subcommand
+  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
+    # Populate subcommand option cache
+    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
+  # Special treatment for the help command
+  if [[ $cmd == help ]]; then
+      if [[ $words[CURRENT-1] == $cmd && $words[CURRENT] != -* ]]; then
+        # Offer commands
+        [[ -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
+        # Offer subcommands
+        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
+        # Handle help<TAB> properly
+        _values -w option help && ret=0
+      fi
+  # Client options
+  elif [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  # Commands
+  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
+  # Subcommands
+  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
+  # Subcommand options
+  else
+    { ! zstyle -T ":completion:${curcontext}:options" prefix-needed || [[ -prefix - ]] } && \
+      [[ -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
+
+# Old style clients
+elif [[ -n ${clnts_compl_old[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    # Populate caches
+    _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
+  # Determine the command
+  for word in ${words:1}; do
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break
+  done
+  # Populate command option cache
+  # Mostly no options for help, prevent consecutive calls with help here
+  if [[ -n $cmd && $cmd != help && -z $_cache_openstack_clnt_cmds_opts[$service$cmd] ]]; then
+    _cache_openstack_clnt_cmds_opts[$service$cmd]=${${${(M)${${${${=${(f)"$($service help $cmd 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}
+  fi
+  # Special treatment for the help command
+  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
+  # Client options
+  elif [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  # Commands
+  elif [[ -z $cmd ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+  # Command options
+  else
+    { ! zstyle -T ":completion:${curcontext}:options" prefix-needed || [[ -prefix - ]] } && \
+      [[ -n $_cache_openstack_clnt_cmds_opts[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_opts[$service$cmd]//\:/\\\:} && ret=0
+  fi
+
+# Swift like clients
+elif [[ -n ${clnts_swift_like[(r)$service]} ]]; then
+  if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
+    # Populate caches - clnt_outputs is command raw output used later
+    _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
+  # Determine the command
+  for word in ${words:1}; do
+    local s=${_cache_openstack_clnt_cmds[$service]}
+    [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break
+  done
+  # Populate command option cache
+  if [[ -n $cmd && -z $_cache_openstack_clnt_cmds_opts[$service$cmd] ]]; then
+    _cache_openstack_clnt_cmds_opts[$service$cmd]=${${${(M)${${${${=${(f)"$($service $cmd --help 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}
+  fi
+  # Client options
+  if [[ -z $cmd && $words[CURRENT] == -* ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
+  # Commands
+  elif [[ -z $cmd ]]; then
+    _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
+  # Command options
+  else
+    { ! zstyle -T ":completion:${curcontext}:options" prefix-needed || [[ -prefix - ]] } && \
+      [[ -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


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2016-09-27  5:43 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-09-06 12:39 Zsh OpenStack completions Marko Myllynen
2016-09-06 23:23 ` Eric Cook
2016-09-07  7:12   ` Marko Myllynen
2016-09-06 23:37 ` Daniel Shahaf
2016-09-07  9:31   ` Marko Myllynen
2016-09-09  6:38     ` Marko Myllynen
2016-09-19  6:24       ` Marko Myllynen
2016-09-27  5:43         ` Marko Myllynen

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).