summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--completions/zsh/_podman385
-rw-r--r--contrib/spec/podman.spec.in1
3 files changed, 390 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 782fa4e62..03e21cbe1 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/completions/zsh/_podman b/completions/zsh/_podman
new file mode 100644
index 000000000..530649c0c
--- /dev/null
+++ b/completions/zsh/_podman
@@ -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
diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in
index 703b942b6..3324ee8f9 100644
--- a/contrib/spec/podman.spec.in
+++ b/contrib/spec/podman.spec.in
@@ -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