#compdef rpm

# This uses `_arguments' in a state-machine kind of way. These states
# have names and before executing the default action for such a state
# we try to call a function with the name `_rpm_<state>'. If such a
# function exists, we return with its return status immediately. This
# allows users to override the default completions by simply defining
# these functions.
# The states (and possible values for the `<state>' above) are:
#
#  query
#    complete for `rpm -q' query
#  verify
#    complete for `rpm --verify'
#  install
#    complete for `rpm -i' or `rpm --install'
#  upgrade
#    complete for `rpm -U' or `rpm --upgrade'
#  uninstall
#    complete for `rpm -e' or `rpm --erase'
#  build_b
#    complete for `rpm -bx' (the stage `x' is already completed)
#  build_t
#    complete for `rpm -tx' (the stage `x' is already completed)
#  sigcheck
#    complete for `rpm --sigcheck'
#  rebuild
#    complete for `rpm --rebuild'
#  package
#    complete a RPM package name
#  package_file
#    complete a RPM package file name
#  package_or_file
#    the previous two together
#  tags
#    complete a tag name
#  capability
#    complete a capability
#  relocate
#    complete a `old=new' pair of paths

# Used by `_arguments', made local here.

_rpm () {
  local curcontext="$curcontext" state lstate line nm="$compstate[nmatches]"
  typeset -A opt_args
  
  state=''
  
  local ret=1
  local -a tmp expl commonopts packageopts
  commonopts=(
    '*-v[verbose mode]'
    '--rcfile:resource file:_files'
    '--ftpproxy:FTP proxy server:_hosts'
    '--ftpport:FTP port number:'
    '--httpproxy:HTTP proxy server:_hosts'
    '--httpport:HTTP port number:'
  )
  packageopts=(
    '-a[query all packages]'
    '-p[query uninstalled package file]:*:RPM package file:->package_file'
    '-f[specify file to query owner of]:file:_files'
    '--triggeredby:RPM package:->package'
    '--whatprovides:RPM capability:->capability'
    '--whatrequires:RPM capability:->capability'
  )
  pathopts=(
    '--root:RPM root directory:_files -/'
    '--dbpath:RPM database path:_files -/'
  )
  
  # Do simple completions or get the first state.
  
  _arguments -C -s \
    '--help[print help message]' \
    '--version[print version number]' \
    "${commonopts[@]}" \
    '-q+[query mode]:*:query:->query' \
    --{querytags,initdb,showrc} \
    '--pipe:pipe command:_command_names -e' \
    -{V,y}'[verify mode]:*:verify:->verify' \
    '--verify[verify mode]:*:verify:->verify' \
    '--setperms[set file permissions]:*:package:->setattrs' \
    '--setugids[set file owner/group]:*:package:->setattrs' \
    '(--install)-i+[install mode]:*:install:->install' \
    '(-i)--install:*:install:->install' \
    '(--upgrade)-U+[upgrade mode]:*:upgrade:->upgrade' \
    '(-U)--upgrade:*:upgrade:->upgrade' \
    '(--freshen)-F+[freshen mode]:*:upgrade:->upgrade' \
    '(-F)--freshen:*:upgrade:->upgrade' \
    '(--erase)-e+[uninstall mode]:*:uninstall:->uninstall' \
    '(-e)--erase:*:uninstall:->uninstall' \
    '-b+[build mode (spec file)]:build stage:((p\:execute\ \%prep\ stage l\:do\ a\ list\ check c\:execute\ build\ stage i\:execute\ install\ stage b\:build\ a\ binary\ package a\:build\ binary\ and\ source\ packages)):*:build:->build_b' \
    '(-b)-t+[build mode (tar file)]:build stage:((p\:execute\ \%prep\ stage l\:do\ a\ list\ check c\:execute\ build\ stage i\:execute\ install\ stage b\:build\ a\ binary\ package a\:build\ binary\ and\ source\ packages)):*:build:->build_t' \
    --{resign,addsign}':*:RPM package:->package_file' \
    '--rmsource:*:spec file:->spec_file' \
    --{rebuild,recompile}':*:Src RPM files:->package_src' \
    '(--checksig)-K+[signature check mode]:*:sigcheck:->sigcheck' \
    '(-K)--checksig:*:sigcheck:->sigcheck' \
    '--rebuilddb:*:rebuild:->rebuild' && ret=0
  
  # As long as we have a state name...
  
  while [[ -n "$state" ]]; do
  
    # First try to call a user-defined function.
  
    _call_function ret _rpm_$state && return ret
  
    # Copy the state and reset `state', to simplify the test above.
  
    lstate="$state"
    state=''
    tmp=()
  
    # Dispatch...
  
    case "$lstate" in
    query)
      # --dump requires on of -{l,c,d}
      # --triggers requires --script
      _arguments -s \
        -q "${commonopts[@]}" "${packageopts[@]}" "${pathopts[@]}" \
        '--queryformat:RPM query format:->tags' \
        '-i[display package information]' \
        '--changelog[display change log]' \
        '-l[display package file list]' \
        '-s[show file states]' \
        '-d[documentation files only]' \
        '-c[configuration files only]' \
        '--dump[show all information]' \
        --provides \
        -{R,-requires}'[list dependencies]' \
        '--scripts[show (un)install scripts]' \
        '--triggers[show trigger scripts]' \
        '*:RPM package:->package_or_file' && ret=0
      ;;
    setattrs)
      _arguments -s --set{perm,ugids} "${packageopts[@]}" && ret = 0
      ;;
    verify)
      _arguments -s \
        '(-y --verify)-V' '(-V --verify)-y' '(-y -V)--verify' \
        "${commonopts[@]}" "${packageopts[@]}" "${pathopts[@]}" \
        --no{deps,md5,files} \
        '*:RPM package:->package_or_file' && ret=0
      ;;
    upgrade)
      tmp=( '(--upgrade)-U' '(-U)--upgrade' '(--force)--oldpackage' )
      ;&
    install)
      (( $#tmp )) || tmp=( '(--install)-i' '(-i)--install' )
      _arguments -s "$tmp[@]" \
        "${commonopts[@]}" "${pathopts[@]}" \
        '--excludepath:exclude files in following path:_files -/' \
        '--relocate:relocate:->relocate' \
        '--prefix:package prefix directory:_files -/' \
        '(-h)--hash' '(--hash)-h' \
        '(--replacepkgs --replacefiles --oldpackage)--force' \
        '(--force)--'{replacefiles,replacepkgs} \
        --{badreloc,excludedocs,allfiles,ignorearch,ignoreos,includedocs,justdb,nodeps,noorder,noscripts,notriggers,percent,test} \
        '*:pkg file:->package_file' && ret=0
      ;;
    uninstall)
      _arguments -s \
        '(-e)--erase' '(--erase)-e' \
        "${commonopts[@]}" "${pathopts[@]}" \
        --{allmatches,justdb,nodeps,noorder,noscripts,notriggers} \
        '*:RPM package:->package' && ret=0
      ;;
    build_b)
      tmp=( '*:spec file:_files -g \*.spec' )
      ;&
    build_t)
      (( $#tmp )) || tmp=( '*:tar file:_files -g \*.\(\#i\)tar\(.\*\|\)' )
  
      _arguments -s \
        "${commonopts[@]}" "${pathopts[@]}" \
        --{short-circuit,clean,nobuild,rmsource,sign,test} \
        '--target:specify a build target:->target'\
        '--buildroot:build root directory:_files -/' \
        '--buildarch:architecture for which to build:->target' \
        '--buildos:operating system for which to build:' \
        '--timecheck:time check (seconds):' "$tmp[1]" && ret=0
      ;;
    sigcheck)
      _arguments -s \
        '(-K)--checksig' '(--checksig)-K' \
        "${commonopts[@]}" \
        --no{gpg,pgp,md5} \
        '*:RPM package file:->package_file' && ret=0
      ;;
    rebuild)
      _arguments -s \
        "${commonopts[@]}" "${pathopts[@]}" \
        '*:RPM source package file:->package_file' && ret=0
      ;;
    target)
      _wanted target expl 'Target platforms' \
          compadd $(_call_program target rpm --showrc 2> /dev/null |grep 'compatible archs'|sed 's/.*: //') && ret=0
      ;;
    package_or_file)
      state=package_file
      ;&
    package)
      if ( [[ ${+_rpms} -eq 0 ]] || _cache_invalid RPMs ) &&
         ! _retrieve_cache RPMs;
      then
        _rpms=( $(_call_program packages rpm -qa 2>/dev/null) )
        _store_cache RPMs _rpms
      fi
      _wanted packages expl 'RPM package' \
          compadd -M 'r:|-=* r:|=*' - "$_rpms[@]" && ret=0
      ;;
    spec_file)
      _wanted specfiles expl 'spec file' \
          _files -g \*.spec && ret=0
      ;;
    package_file)
      _wanted files expl 'RPM package file' \
          _files -g '*.(#i)rpm' && ret=0
      if [[ -prefix 1 (f|ht)tp:// ]]; then
        _wanted urls expl 'URL of RPM package file' \
            _urls -f -g '*.(#i)rpm' "${expl[@]}" && ret=0
      else
        _wanted urls expl 'URL of RPM package file' \
            compadd -S '' "${expl[@]}" ftp:// http:// && ret=0
      fi
      ;;
    package_src)
      _files -g \*.src\(\#i\).rpm
     ;&
    tags)
      if compset -P '*%*\{'; then
        _wanted tags expl 'RPM tag' \
            compadd -M 'm:{a-z}={A-Z}' -S '\}' - \
                    "${(@)${(@f)$(_call_program tags rpm --querytags 2> /dev/null)}#RPMTAG_}" && ret=0
      else
        _message 'RPM format'
      fi
      ;;
    capability)
      _message 'RPM capability'
      ;;
    relocate)
      if compset -P '*='; then
        _description directories expl 'new path'
      else
        _description directories expl 'old path'
      fi
  
      _files "$expl[@]" -/ && ret=0
      ;;
    esac
  
    [[ ret -eq 0 || $nm -ne $compstate[nmatches] ]] && return 0
  done
  
  return ret
}

# set a sensible default caching policy
local update_policy
zstyle -s ":completion:*:*:rpm:*" cache-policy update_policy
if [[ -z "$update_policy" ]]; then
  zstyle ":completion:*:*:rpm:*" cache-policy _rpms_caching_policy
fi

_rpms_caching_policy () {
  # rebuild if cache is more than a week old
  oldp=( "$1"(mw+1) )
  (( $#oldp )) && return 0

  pkg_indices=( /var/lib/rpm/{packages.rpm,Packages}(N) )
  for pkg_index in $pkg_indices; do
    [[ "$pkg_index" -nt "$1" ]] && return 0
  done
}

_rpm "$@"
