github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/completions/zsh/_podman (about)

     1  #compdef podman
     2  
     3  # To get zsh to reread this file: unset -f _podman;rm -f ~/.zcompdump;compinit
     4  
     5  # On rereads, reset cache. (Not that the caching works, but some day it might)
     6  unset -m '_podman_*'
     7  
     8  ###############################################################################
     9  # BEGIN 'podman help' parsers -- for options, subcommands, and usage
    10  
    11  # Run 'podman XX --help', set _podman_commands to a formatted list of cmds
    12  _read_podman_commands() {
    13      local line
    14  
    15      # Cache: the intention here is to run each 'podman help' only once.
    16      # Unfortunately it doesn't seem to actually be working: even though
    17      # I can see the var_ref in my shell, it's not visible here.
    18      local _var_ref=_podman_commands_"${*// /_}"
    19      typeset -ga _podman_commands
    20      _podman_commands=(${(P)_var_ref})
    21      (( $#_podman_commands )) && return
    22  
    23      _call_program podman podman "$@" --help |\
    24          sed -n -e '0,/^Available Commands/d' -e '/^[A-Z]/q;p' |\
    25          sed -e 's/^ \+\([^ ]\+\) \+/\1:/' |\
    26          egrep . | while read line; do
    27          _podman_commands+=($line)
    28      done
    29  
    30      eval "typeset -ga $_var_ref"
    31      eval "$_var_ref=(\$_podman_commands)"
    32  }
    33  
    34  # Run 'podman XX --help', set _podman_flag_list to a formatted list
    35  # of flag options for XX
    36  _read_podman_flags() {
    37      local line
    38  
    39      local _var_ref=_podman_flags_"${*// /_}"
    40      eval "typeset -ga ${_var_ref}"
    41      typeset -ga _podman_flag_list
    42      _podman_flag_list=(${(P)_var_ref})
    43      (( $#_podman_flag_list )) && return
    44  
    45      # Extract the Flags; strip leading whitespace; pack '-f, --foo'
    46      # as '-f,--foo' (no space); then add '=' to '--foo string'.
    47      # The result will be, e.g. '-f,--foo=string  Description of Option'
    48      _call_program podman podman "$@" --help |\
    49          sed -n -e '0,/^Flags:/d' -e '/^$/q;p' |\
    50          grep '^ \+-' |\
    51          sed -e 's/^ *//' -e 's/^\(-.,\) --/\1--/' |\
    52          sed -e 's/^\(-[^ ]\+\) \([^ ]\+\)  /\1=\2  /' |\
    53          while read flags desc;do
    54              # flags like --foo=string: split into --foo & string
    55              local -a tmpa
    56              local optval=
    57              tmpa=(${(s.=.)flags})
    58              if [ -n "$tmpa[2]" ]; then
    59                  flags=$tmpa[1]
    60                  optval=$tmpa[2]
    61              fi
    62  
    63              # 'podman attach --detach-keys' includes ']' in help msg
    64              desc=${desc//\]/\\]}
    65  
    66              for flag in ${(s:,:)flags}; do
    67                  if [ -n "$optval" ]; then
    68                      _podman_flag_list+=("${flag}[$desc]:$(_podman_find_helper ${flags} ${optval} ${desc})")
    69                  else
    70                      _podman_flag_list+=("${flag}[$desc]")
    71                  fi
    72              done
    73          done
    74  
    75      eval "typeset -ga $_var_ref=(\$_podman_flag_list)"
    76  }
    77  
    78  # Run 'podman XXX --help', set _podman_usage to the line after "Usage:"
    79  _read_podman_usage() {
    80      local _var_ref=_podman_usage_"${*// /_}"
    81      typeset -ga _podman_usage
    82      _podman_usage=${(P)_var_ref}
    83      (( $#_podman_usage )) && return
    84  
    85      _podman_usage=$(_call_program podman podman "$@" --help |\
    86                          grep -A1 'Usage:'|\
    87                          tail -1 |\
    88                          sed -e 's/^ *//')
    89  
    90      eval "typeset -ga $_var_ref"
    91      eval "$_var_ref=\$_podman_usage"
    92  }
    93  
    94  # END   'podman help' parsers
    95  ###############################################################################
    96  # BEGIN custom helpers for individual option arguments
    97  
    98  # Find a zsh helper for a given flag or command-line option
    99  _podman_find_helper() {
   100      local flags=$1
   101      local optval=$2
   102      local desc=$3
   103      local helper=
   104  
   105      # Yes, this is a lot of hardcoding. IMHO it's still better than
   106      # hardcoding every possible podman option.
   107      # FIXME: there are many more options that could use helpers.
   108      if expr "$desc" : ".*[Dd]irectory" >/dev/null; then
   109          optval="directory"
   110          helper="_files -/"
   111      elif expr "$desc" : ".*[Pp]ath" >/dev/null; then
   112          optval="path"
   113          helper=_files
   114      # For messages like 'restart policy ("always"|"no"|"on-failure")
   115      elif optlist=$(expr "$desc" : '.*(\(\"[^\\)]\+|[^\\)]\+\"\))' 2>/dev/null); then
   116          optval=${${flags##--}//-/ }       # "--log-level" => "log level"
   117          optlist=${optlist//\"/}           # "a"|"b"|"c"   => a|b|c
   118          optlist=${optlist//\|/ }          # a|b|c         => a b c
   119          # FIXME: how to present values _in order_, not sorted alphabetically?
   120          helper="($optlist)"
   121      fi
   122      echo "$optval:$helper"
   123  }
   124  
   125  # END   custom helpers for individual option arguments
   126  ###############################################################################
   127  # BEGIN helpers for command-line args (containers, images)
   128  
   129  __podman_helper_generic() {
   130      local expl line
   131      local -a results
   132  
   133      local foo1=$1; shift
   134      local name=$2; shift
   135  
   136      _call_program $foo1 podman "$@" |\
   137          while read line; do
   138              results+=(${=line})
   139          done
   140  
   141      _wanted $foo1 expl $name compadd ${(u)results}
   142  }
   143  
   144  _podman_helper_image() {
   145      __podman_helper_generic podman-images 'images' \
   146          images --format '{{.ID}}\ {{.Repository}}:{{.Tag}}'
   147  }
   148  
   149  # FIXME: at some point, distinguish between running & stopped containers
   150  _podman_helper_container() {
   151      __podman_helper_generic podman-containers 'containers' \
   152          ps -a --format '{{.Names}}\ {{.ID}}'
   153  }
   154  
   155  _podman_helper_pod() {
   156      __podman_helper_generic podman-pods 'pods' pod list --format '{{.Name}}'
   157  }
   158  
   159  _podman_helper_volume() {
   160      __podman_helper_generic podman-volumes 'volumes' volume ls --format '{{.Name}}'
   161  }
   162  
   163  # Combinations. This one seen in diff & inspect
   164  _podman_helper_container-or-image() {
   165      _podman_helper_image
   166      _podman_helper_container
   167  }
   168  
   169  # Seen in generate-kube
   170  _podman_helper_container-or-pod() {
   171      _podman_helper_container
   172      _podman_helper_pod
   173  }
   174  
   175  # For top and pod-top
   176  _podman_helper_format-descriptors() {
   177      __podman_helper_generic top-format-descriptors 'format descriptors' \
   178          top --list-descriptors
   179  }
   180  
   181  # for push, login/logout, and trust
   182  # FIXME: some day, use this to define a helper for IMAGE-PATH (in 'pull')
   183  _podman_helper_registry() {
   184      local expl
   185      local -a registries
   186  
   187      # Suggestions for improvement more than welcome.
   188      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
   189          registries+=($line)
   190      done
   191  
   192      if (( $#registries )); then
   193          _wanted podman-registry expl "registry" compadd ${(u)registries}
   194      else
   195          _hosts
   196      fi
   197  }
   198  
   199  # END   helpers for command-line args
   200  ###############################################################################
   201  # BEGIN figure out completion helpers for a given (sub)command
   202  
   203  # Read Usage string for this subcommand, set up helpers for its subargs
   204  _set_up_podman_args() {
   205      _read_podman_usage "$@"
   206  
   207      typeset -ga _podman_args=()
   208      # E.g. 'podman exec [flags] CONTAINER [...' -> 'CONTAINER [....'
   209      local usage_rhs=$(expr "$_podman_usage" : ".*\[flags\] \+\(.*\)")
   210  
   211      # e.g. podman pod ps which takes no further args
   212      if [ -z "$usage_rhs" ]; then
   213          return
   214      fi
   215  
   216      # podman diff & inspect accept 'CONTAINER | IMAGE'; make into one keyword.
   217      usage_rhs=${usage_rhs// | /-OR-}
   218  
   219      # Arg parsing. There are three possibilities in Usage messages:
   220      #
   221      #   [IMAGE]            - optional image arg (zero or one)
   222      #    IMAGE             - exactly one image arg
   223      #    IMAGE [IMAGE...]  - one or more image args
   224      # and, theoretically:
   225      #   [IMAGE...]         - zero or more? Haven't seen it in practice. Defer.
   226      #
   227      # For completion purposes, we only need to provide two options:
   228      # one, or more than one? That is: continue offering completion
   229      # suggestions after the first one? For that, we make two passes;
   230      # in the first, mark an option as either '' (only one) or
   231  
   232      # Parse each command-line arg seen in usage message
   233      local word
   234      local -A _seen=()
   235      for word in ${=usage_rhs}; do
   236          local unbracketed=$(expr "$word" : "\[\(.*\)\]")
   237  
   238          if [ -n "$unbracketed" ]; then
   239              # Remove all dots; assume(!?) that they'll all be at the end
   240              unbracketed=${unbracketed//./}
   241  
   242              if (( $_seen[$unbracketed] )); then
   243                  # Is this the same word as the previous arg?
   244                  if expr "$_podman_args[-1]" : ":$unbracketed:" >/dev/null; then
   245                      # Yes. Make it '*:...' instead of ':...', indicating >1
   246                      _podman_args[-1]="*$_podman_args[-1]"
   247                  fi
   248                  continue
   249              fi
   250  
   251              word=$unbracketed
   252          fi
   253  
   254          # As of 2019-03 all such instances are '[COMMAND [ARG...]]' and are,
   255          # of course, at the end of the line. We can't offer completion for
   256          # these, because the container will have different commands than
   257          # the host system... but try anyway.
   258          if [ "$word" = '[COMMAND' ]; then
   259              # e.g. podman create, exec, run
   260              _podman_args+=(
   261                  ":command: _command_names -e"
   262                  "*::arguments: _normal"
   263              )
   264              return
   265          fi
   266  
   267          # Look for an existing helper, e.g. IMAGE -> _podman_helper_image
   268          local helper="_podman_helper_${(L)word}"
   269          if (( $+functions[$helper] )); then
   270              :
   271          else
   272              # No defined helper. Reset, but check for known expressions.
   273              helper=
   274              case "$word" in
   275                  KUBEFILE)   helper='_files -g "*.y(|a)ml(-.)"' ;;
   276                  PATH)       helper='_files'                    ;;
   277              esac
   278          fi
   279  
   280          # Another special case: 'top' actually takes multiple options
   281          local multi=
   282          if [ "$word" = "FORMAT-DESCRIPTORS" ]; then
   283              multi='*'
   284          fi
   285          _podman_args+=("$multi:${(L)word}:$helper")
   286          _seen[$word]=1
   287      done
   288  }
   289  
   290  # For an endpoint command, i.e. not a subcommand.
   291  _podman_terminus() {
   292      typeset -A opt_args
   293      typeset -ga _podman_flag_list
   294      typeset -ga _podman_args
   295      integer ret=1
   296  
   297      # Find out what args it takes (e.g. image(s), container(s)) and see
   298      # if we have helpers for them.
   299      _set_up_podman_args "$@"
   300      _arguments -C $_podman_flag_list $_podman_args && ret=0
   301  
   302      return ret
   303  }
   304  
   305  # END   figure out completion helpers for a given (sub)command
   306  ################################################################################
   307  # BEGIN actual entry point
   308  
   309  # This is the main entry point; it's also where we (recursively) come in
   310  # to handle nested subcommands such as 'podman container' or even 3-level
   311  # ones like 'podman generate kube'. Nesting is complicated, so here's
   312  # my best understanding as of 2019-03-12:
   313  #
   314  # Easy first: when you do "podman <TAB>" zsh calls us, we run 'podman --help',
   315  # figure out the global options and subcommands, and run the magic _arguments
   316  # command. That will offer those options/subcommands, and complete, etc.
   317  #
   318  # Where it gets harder is when you do "podman container mount <TAB>".
   319  # zsh first calls us with words=(podman container mount) but we don't
   320  # want all that full context yet! We want to go a piece at a time,
   321  # handling 'container' first, then 'mount'; ending up with our
   322  # final 'podman container mount --help' giving us suitable flags
   323  # and no subcommands; from which we determine that it's a terminus
   324  # and jump to a function that handles non-subcommand arguments.
   325  #
   326  # This is the closest I've yet come to understanding zsh completion;
   327  # it is still incomplete and may in fact be incorrect. But it works
   328  # better than anything I've played with so far.
   329  _podman_subcommand() {
   330      local curcontext="$curcontext" state line
   331      typeset -A opt_args
   332      integer ret=1
   333  
   334      # Run 'podman --help' / 'podman system --help' for our context (initially
   335      # empty, then possibly under subcommands); from those, get a list of
   336      # flags appropriate for this context and, if applicable, subcommands.
   337      _read_podman_flags "$@"
   338      _read_podman_commands "$@"
   339  
   340      # Now, is this a sub-subcommand, or does it have args?
   341      if (( $#_podman_commands )); then
   342          # Subcommands required (podman, podman system, etc)
   343          local cmd=${words// /_}
   344          _arguments -C $_podman_flag_list \
   345                     "(-): :->command" \
   346                     "(-)*:: :->option-or-argument" \
   347              && ret=0
   348  
   349          case $state in
   350              (command)
   351                  _describe -t command "podman $* command" _podman_commands && ret=0
   352                  ;;
   353              (option-or-argument)
   354                  # I think this is when we have a _completed_ subcommand.
   355                  # Recurse back into here, offering options for that subcommand.
   356                  curcontext=${curcontext%:*:*}:podman-${words[1]}:
   357                  _podman_subcommand "$@" ${words[1]} && ret=0
   358              ;;
   359          esac
   360      else
   361          # At a terminus, i.e. podman info, podman history; find out
   362          # what args it takes.
   363          _podman_terminus "$@" && ret=0
   364      fi
   365  
   366      return ret
   367  }
   368  
   369  _podman() {
   370      _podman_subcommand
   371  }
   372  
   373  # Local Variables:
   374  #  mode: shell-script
   375  #  sh-indentation: 4
   376  #  indent-tabs-mode: nil
   377  #  sh-basic-offset: 4
   378  # End:
   379  # vim: ft=zsh sw=4 ts=4 et