diff options
author | Paul Holzinger <paul.holzinger@web.de> | 2020-11-08 21:50:51 +0100 |
---|---|---|
committer | Paul Holzinger <paul.holzinger@web.de> | 2020-11-12 11:40:29 +0100 |
commit | ae3816614de1c2a0c9ab9cd05afebc5b1dda6429 (patch) | |
tree | 11deb2f5b2bb87d1869f7258b8f2e9cc46b57e00 /completions/zsh/_podman | |
parent | b5d1d89a377e38762a75c2042dcc50a370ba6707 (diff) | |
download | podman-ae3816614de1c2a0c9ab9cd05afebc5b1dda6429.tar.gz podman-ae3816614de1c2a0c9ab9cd05afebc5b1dda6429.tar.bz2 podman-ae3816614de1c2a0c9ab9cd05afebc5b1dda6429.zip |
Install the new shell completion logic
Add a new make target (completion) to generate the shell
completion scripts. This will generate the scripts for bash,
zsh and fish for both podman and podman-remote with `podman completion`.
The scripts are put into the completions directory and can be
installed system wide with `sudo make install.completions`.
This commit replaces the current handwritten scripts for bash and zsh.
The `validate.completion` target has been adjusted to make sure nobody
edits these scripts directly.
Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
Diffstat (limited to 'completions/zsh/_podman')
-rw-r--r-- | completions/zsh/_podman | 502 |
1 files changed, 152 insertions, 350 deletions
diff --git a/completions/zsh/_podman b/completions/zsh/_podman index b65c3dbb8..b25e9cb08 100644 --- a/completions/zsh/_podman +++ b/completions/zsh/_podman @@ -1,379 +1,181 @@ -#compdef podman +#compdef _podman podman -# To get zsh to reread this file: unset -f _podman;rm -f ~/.zcompdump;compinit +# zsh completion for podman -*- shell-script -*- -# On rereads, reset cache. (Not that the caching 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_option_list to a formatted list -# of option options for XX -_read_podman_options() { - local line - - local _var_ref=_podman_options_"${*// /_}" - eval "typeset -ga ${_var_ref}" - typeset -ga _podman_option_list - _podman_option_list=(${(P)_var_ref}) - (( $#_podman_option_list )) && return - - # Extract the Options; 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,/^Options:/d' -e '/^$/q;p' |\ - grep '^ \+-' |\ - sed -e 's/^ *//' -e 's/^\(-.,\) --/\1--/' |\ - sed -e 's/^\(-[^ ]\+\) \([^ ]\+\) /\1=\2 /' |\ - while read options desc;do - # options like --foo=string: split into --foo & string - local -a tmpa - local optval= - tmpa=(${(s.=.)options}) - if [ -n "$tmpa[2]" ]; then - options=$tmpa[1] - optval=$tmpa[2] - fi - - # 'podman attach --detach-keys' includes ']' in help msg - desc=${desc//\]/\\]} - - for option in ${(s:,:)options}; do - if [ -n "$optval" ]; then - _podman_option_list+=("${option}[$desc]:$(_podman_find_helper ${options} ${optval} ${desc})") - else - _podman_option_list+=("${option}[$desc]") - fi - done - done - - eval "typeset -ga $_var_ref=(\$_podman_option_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 option or command-line option -_podman_find_helper() { - local options=$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 - # For messages like 'restart policy ("always"|"no"|"on-failure") - elif optlist=$(expr "$desc" : '.*(\(\"[^\\)]\+|[^\\)]\+\"\))' 2>/dev/null); then - optval=${${options##--}//-/ } # "--log-level" => "log level" - optlist=${optlist//\"/} # "a"|"b"|"c" => a|b|c - optlist=${optlist//\|/ } # a|b|c => a b c - # FIXME: how to present values _in order_, not sorted alphabetically? - helper="($optlist)" +__podman_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" 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 +_podman() +{ + local shellCompDirectiveError=1 + local shellCompDirectiveNoSpace=2 + local shellCompDirectiveNoFileComp=4 + local shellCompDirectiveFilterFileExt=8 + local shellCompDirectiveFilterDirs=16 + local shellCompDirectiveLegacyCustomComp=32 + local shellCompDirectiveLegacyCustomArgsComp=64 + + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace + local -a completions + + __podman_debug "\n========= starting completion logic ==========" + __podman_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __podman_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __podman_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., podman -n=<TAB>) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi - # 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 + # Prepare the command to obtain completions + requestComp="${words[1]} __complete ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __podman_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi - if (( $#registries )); then - _wanted podman-registry expl "registry" compadd ${(u)registries} + __podman_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __podman_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%s\n" "${out[@]}") + __podman_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} else - _hosts + # There is no directive specified. Leave $out as is. + __podman_debug "No directive found. Setting do default" + directive=0 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 "$@" + __podman_debug "directive: ${directive}" + __podman_debug "completions: ${out}" + __podman_debug "flagPrefix: ${flagPrefix}" - typeset -ga _podman_args=() - # E.g. 'podman exec [options] CONTAINER [...' -> 'CONTAINER [....' - local usage_rhs=$(expr "$_podman_usage" : ".*\[options\] \+\(.*\)") - - # e.g. podman pod ps which takes no further args - if [ -z "$usage_rhs" ]; then + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __podman_debug "Completion received error. Ignoring completions." return fi - # podman diff & inspect accept 'CONTAINER | IMAGE'; make into one keyword. - usage_rhs=${usage_rhs// | /-OR-} + while IFS='\n' read -r comp; do + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} - # 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 + local tab=$(printf '\t') + comp=${comp//$tab/:} - # Parse each command-line arg seen in usage message - local word - local -A _seen=() - for word in ${=usage_rhs}; do - local unbracketed=$(expr "$word" : "\[\(.*\)\]") + __podman_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%s\n" "${out[@]}") - if [ -n "$unbracketed" ]; then - # Remove all dots; assume(!?) that they'll all be at the end - unbracketed=${unbracketed//./} + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + __podman_debug "Activating nospace." + noSpace="-S ''" + fi - 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 + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" fi - - word=$unbracketed + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __podman_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subDir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __podman_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __podman_debug "Listing directories in ." 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 + local result + _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 fi + return $result + else + __podman_debug "Calling _describe" + if eval _describe "completions" completions $flagPrefix $noSpace; then + __podman_debug "_describe found some completions" - # Look for an existing helper, e.g. IMAGE -> _podman_helper_image - local helper="_podman_helper_${(L)word}" - if (( $+functions[$helper] )); then - : + # Return the success of having called _describe + return 0 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='*' + __podman_debug "_describe did not find completions." + __podman_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __podman_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __podman_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi 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_option_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_option_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 options -# 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 - # options appropriate for this context and, if applicable, subcommands. - _read_podman_options "$@" - _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_option_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 -} +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_podman" ]; then + _podman +fi -# 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 +# This file is generated with "podman completion"; see: podman-completion(1) |