Merge pull request #2637 from edsantiago/zsh_completion

zsh completion
This commit is contained in:
OpenShift Merge Robot
2019-03-15 07:01:28 -07:00
committed by GitHub
3 changed files with 390 additions and 0 deletions

View File

@ -27,6 +27,7 @@ CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker)
OCI_RUNTIME ?= ""
BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions
ZSHINSTALLDIR=${PREFIX}/share/zsh/site-functions
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/containers/libpod/vendor | grep -v e2e | grep -v system )
@ -247,6 +248,8 @@ install.config:
install.completions:
install ${SELINUXOPT} -d -m 755 ${BASHINSTALLDIR}
install ${SELINUXOPT} -m 644 completions/bash/podman ${BASHINSTALLDIR}
install ${SELINUXOPT} -d -m 755 ${ZSHINSTALLDIR}
install ${SELINUXOPT} -m 644 completions/zsh/_podman ${ZSHINSTALLDIR}
install.cni:
install ${SELINUXOPT} -d -m 755 ${ETCDIR}/cni/net.d/
@ -332,6 +335,7 @@ API.md: cmd/podman/varlink/io.podman.varlink
validate.completions: completions/bash/podman
. completions/bash/podman
if [ -x /bin/zsh ]; then /bin/zsh completions/zsh/_podman; fi
validate: gofmt .gitvalidation validate.completions

385
completions/zsh/_podman Normal file
View File

@ -0,0 +1,385 @@
#compdef podman
# To get zsh to reread this file: unset -f _podman;rm -f ~/.zcompdump;compinit
# On rereads, reset cache. (Not that the cacheing works, but some day it might)
unset -m '_podman_*'
###############################################################################
# BEGIN 'podman help' parsers -- for options, subcommands, and usage
# Run 'podman XX --help', set _podman_commands to a formatted list of cmds
_read_podman_commands() {
local line
# Cache: the intention here is to run each 'podman help' only once.
# Unfortunately it doesn't seem to actually be working: even though
# I can see the var_ref in my shell, it's not visible here.
local _var_ref=_podman_commands_"${*// /_}"
typeset -ga _podman_commands
_podman_commands=(${(P)_var_ref})
(( $#_podman_commands )) && return
_call_program podman podman "$@" --help |\
sed -n -e '0,/^Available Commands/d' -e '/^[A-Z]/q;p' |\
sed -e 's/^ \+\([^ ]\+\) \+/\1:/' |\
egrep . | while read line; do
_podman_commands+=($line)
done
eval "typeset -ga $_var_ref"
eval "$_var_ref=(\$_podman_commands)"
}
# Run 'podman XX --help', set _podman_flag_list to a formatted list
# of flag options for XX
_read_podman_flags() {
local line
local _var_ref=_podman_flags_"${*// /_}"
eval "typeset -ga ${_var_ref}"
typeset -ga _podman_flag_list
_podman_flag_list=(${(P)_var_ref})
(( $#_podman_flag_list )) && return
# Extract the Flags; strip leading whitespace; pack '-f, --foo'
# as '-f,--foo' (no space); then add '=' to '--foo string'.
# The result will be, e.g. '-f,--foo=string Description of Option'
_call_program podman podman "$@" --help |\
sed -n -e '0,/^Flags:/d' -e '/^$/q;p' |\
sed -e 's/^ *//' -e 's/^\(-.,\) --/\1--/' |\
sed -e 's/^\(-[^ ]\+\) \([^ ]\+\) /\1=\2 /' |\
while read flags desc;do
# flags like --foo=string: split into --foo & string
local -a tmpa
local optval=
tmpa=(${(s.=.)flags})
if [ -n "$tmpa[2]" ]; then
flags=$tmpa[1]
optval=$tmpa[2]
fi
# 'podman attach --detach-keys' includes ']' in help msg
desc=${desc//\]/\\]}
for flag in ${(s:,:)flags}; do
if [ -n "$optval" ]; then
_podman_flag_list+=("${flag}[$desc]:$(_podman_find_helper ${flags} ${optval} ${desc})")
else
_podman_flag_list+=("${flag}[$desc]")
fi
done
done
eval "typeset -ga $_var_ref=(\$_podman_flag_list)"
}
# Run 'podman XXX --help', set _podman_usage to the line after "Usage:"
_read_podman_usage() {
local _var_ref=_podman_usage_"${*// /_}"
typeset -ga _podman_usage
_podman_usage=${(P)_var_ref}
(( $#_podman_usage )) && return
_podman_usage=$(_call_program podman podman "$@" --help |\
grep -A1 'Usage:'|\
tail -1 |\
sed -e 's/^ *//')
eval "typeset -ga $_var_ref"
eval "$_var_ref=\$_podman_usage"
}
# END 'podman help' parsers
###############################################################################
# BEGIN custom helpers for individual option arguments
# Find a zsh helper for a given flag or command-line option
_podman_find_helper() {
local flags=$1
local optval=$2
local desc=$3
local helper=
# Yes, this is a lot of hardcoding. IMHO it's still better than
# hardcoding every possible podman option.
# FIXME: there are many more options that could use helpers.
if expr "$desc" : ".*[Dd]irectory" >/dev/null; then
optval="directory"
helper="_files -/"
elif expr "$desc" : ".*[Pp]ath" >/dev/null; then
optval="path"
helper=_files
elif [ "$flags" = "--cgroup-manager" ]; then
optval="cgroup manager"
helper="(cgroupfs systemd)"
elif [ "$flags" = "--log-level" ]; then
optval="log level"
# 'Log messages above specified level: debug, ... (default "...")'
# Strip off the description and all 'default' strings
desc=${desc/Log*:/} # debug, info, ... (default "...")
desc=${(S)desc//\(*\)/} # debug, info, ... or panic
desc=${desc//,/} # debug info ... or panic
desc=${desc// or / } # debug info ... panic
desc=${desc// / } # collapse multiple spaces
# FIXME: how to present values _in order_, not sorted alphabetically?
helper="($desc)"
fi
echo "$optval:$helper"
}
# END custom helpers for individual option arguments
###############################################################################
# BEGIN helpers for command-line args (containers, images)
__podman_helper_generic() {
local expl line
local -a results
local foo1=$1; shift
local name=$2; shift
_call_program $foo1 podman "$@" |\
while read line; do
results+=(${=line})
done
_wanted $foo1 expl $name compadd ${(u)results}
}
_podman_helper_image() {
__podman_helper_generic podman-images 'images' \
images --format '{{.ID}}\ {{.Repository}}:{{.Tag}}'
}
# FIXME: at some point, distinguish between running & stopped containers
_podman_helper_container() {
__podman_helper_generic podman-containers 'containers' \
ps -a --format '{{.Names}}\ {{.ID}}'
}
_podman_helper_pod() {
__podman_helper_generic podman-pods 'pods' pod list --format '{{.Name}}'
}
_podman_helper_volume() {
__podman_helper_generic podman-volumes 'volumes' volume ls --format '{{.Name}}'
}
# Combinations. This one seen in diff & inspect
_podman_helper_container-or-image() {
_podman_helper_image
_podman_helper_container
}
# Seen in generate-kube
_podman_helper_container-or-pod() {
_podman_helper_container
_podman_helper_pod
}
# For top and pod-top
_podman_helper_format-descriptors() {
__podman_helper_generic top-format-descriptors 'format descriptors' \
top --list-descriptors
}
# for push, login/logout, and trust
# FIXME: some day, use this to define a helper for IMAGE-PATH (in 'pull')
_podman_helper_registry() {
local expl
local -a registries
# Suggestions for improvement more than welcome.
python3 -c 'from configparser import ConfigParser;cp=ConfigParser();cp.read("/etc/containers/registries.conf");registries=eval(cp.get("registries.search","registries"));[print(r) for r in registries]' 2>/dev/null | while read line; do
registries+=($line)
done
if (( $#registries )); then
_wanted podman-registry expl "registry" compadd ${(u)registries}
else
_hosts
fi
}
# END helpers for command-line args
###############################################################################
# BEGIN figure out completion helpers for a given (sub)command
# Read Usage string for this subcommand, set up helpers for its subargs
_set_up_podman_args() {
_read_podman_usage "$@"
typeset -ga _podman_args=()
# E.g. 'podman exec [flags] CONTAINER [...' -> 'CONTAINER [....'
local usage_rhs=$(expr "$_podman_usage" : ".*\[flags\] \+\(.*\)")
# e.g. podman pod ps which takes no further args
if [ -z "$usage_rhs" ]; then
return
fi
# podman diff & inspect accept 'CONTAINER | IMAGE'; make into one keyword.
usage_rhs=${usage_rhs// | /-OR-}
# Arg parsing. There are three possibilities in Usage messages:
#
# [IMAGE] - optional image arg (zero or one)
# IMAGE - exactly one image arg
# IMAGE [IMAGE...] - one or more image args
# and, theoretically:
# [IMAGE...] - zero or more? Haven't seen it in practice. Defer.
#
# For completion purposes, we only need to provide two options:
# one, or more than one? That is: continue offering completion
# suggestions after the first one? For that, we make two passes;
# in the first, mark an option as either '' (only one) or
# Parse each command-line arg seen in usage message
local word
local -A _seen=()
for word in ${=usage_rhs}; do
local unbracketed=$(expr "$word" : "\[\(.*\)\]")
if [ -n "$unbracketed" ]; then
# Remove all dots; assume(!?) that they'll all be at the end
unbracketed=${unbracketed//./}
if (( $_seen[$unbracketed] )); then
# Is this the same word as the previous arg?
if expr "$_podman_args[-1]" : ":$unbracketed:" >/dev/null; then
# Yes. Make it '*:...' instead of ':...', indicating >1
_podman_args[-1]="*$_podman_args[-1]"
fi
continue
fi
word=$unbracketed
fi
# As of 2019-03 all such instances are '[COMMAND [ARG...]]' and are,
# of course, at the end of the line. We can't offer completion for
# these, because the container will have different commands than
# the host system... but try anyway.
if [ "$word" = '[COMMAND' ]; then
# e.g. podman create, exec, run
_podman_args+=(
":command: _command_names -e"
"*::arguments: _normal"
)
return
fi
# Look for an existing helper, e.g. IMAGE -> _podman_helper_image
local helper="_podman_helper_${(L)word}"
if (( $+functions[$helper] )); then
:
else
# No defined helper. Reset, but check for known expressions.
helper=
case "$word" in
KUBEFILE) helper='_files -g "*.y(|a)ml(-.)"' ;;
PATH) helper='_files' ;;
esac
fi
# Another special case: 'top' actually takes multiple options
local multi=
if [ "$word" = "FORMAT-DESCRIPTORS" ]; then
multi='*'
fi
_podman_args+=("$multi:${(L)word}:$helper")
_seen[$word]=1
done
}
# For an endpoint command, i.e. not a subcommand.
_podman_terminus() {
typeset -A opt_args
typeset -ga _podman_flag_list
typeset -ga _podman_args
integer ret=1
# Find out what args it takes (e.g. image(s), container(s)) and see
# if we have helpers for them.
_set_up_podman_args "$@"
_arguments -C $_podman_flag_list $_podman_args && ret=0
return ret
}
# END figure out completion helpers for a given (sub)command
################################################################################
# BEGIN actual entry point
# This is the main entry point; it's also where we (recursively) come in
# to handle nested subcommands such as 'podman container' or even 3-level
# ones like 'podman generate kube'. Nesting is complicated, so here's
# my best understanding as of 2019-03-12:
#
# Easy first: when you do "podman <TAB>" zsh calls us, we run 'podman --help',
# figure out the global options and subcommands, and run the magic _arguments
# command. That will offer those options/subcommands, and complete, etc.
#
# Where it gets harder is when you do "podman container mount <TAB>".
# zsh first calls us with words=(podman container mount) but we don't
# want all that full context yet! We want to go a piece at a time,
# handling 'container' first, then 'mount'; ending up with our
# final 'podman container mount --help' giving us suitable flags
# and no subcommands; from which we determine that it's a terminus
# and jump to a function that handles non-subcommand arguments.
#
# This is the closest I've yet come to understanding zsh completion;
# it is still incomplete and may in fact be incorrect. But it works
# better than anything I've played with so far.
_podman_subcommand() {
local curcontext="$curcontext" state line
typeset -A opt_args
integer ret=1
# Run 'podman --help' / 'podman system --help' for our context (initially
# empty, then possibly under subcommands); from those, get a list of
# flags appropriate for this context and, if applicable, subcommands.
_read_podman_flags "$@"
_read_podman_commands "$@"
# Now, is this a sub-subcommand, or does it have args?
if (( $#_podman_commands )); then
# Subcommands required (podman, podman system, etc)
local cmd=${words// /_}
_arguments -C $_podman_flag_list \
"(-): :->command" \
"(-)*:: :->option-or-argument" \
&& ret=0
case $state in
(command)
_describe -t command "podman $* command" _podman_commands && ret=0
;;
(option-or-argument)
# I think this is when we have a _completed_ subcommand.
# Recurse back into here, offering options for that subcommand.
curcontext=${curcontext%:*:*}:podman-${words[1]}:
_podman_subcommand "$@" ${words[1]} && ret=0
;;
esac
else
# At a terminus, i.e. podman info, podman history; find out
# what args it takes.
_podman_terminus "$@" && ret=0
fi
return ret
}
_podman() {
_podman_subcommand
}
# Local Variables:
# mode: shell-script
# sh-indentation: 4
# indent-tabs-mode: nil
# sh-basic-offset: 4
# End:
# vim: ft=zsh sw=4 ts=4 et

View File

@ -472,6 +472,7 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath}
%{_mandir}/man1/*.1*
%{_mandir}/man5/*.5*
%{_datadir}/bash-completion/completions/*
%{_datadir}/zsh/site-functions/*
%{_libexecdir}/%{name}/conmon
%config(noreplace) %{_sysconfdir}/cni/net.d/87-%{name}-bridge.conflist
%{_datadir}/containers/%{repo}.conf