[sisyphus] [#5235] zsh completion for rpm

Alexey Tourbin =?iso-8859-1?q?at_=CE=C1_altlinux=2Eru?=
Сб Окт 23 07:05:33 MSD 2004


Здравствуйте.
Я научился программировать на zsh. :)

Предлагаю протестировать новую (значительно переделанную) дополнялку
для rpm.  Теперь будут дополняться команды rpmbuild, rpmqurey и др.

Как проще всего подключить дополнялку:

$ mkdir -p ~/fpath
$ cp -a _rpm ~/fpath
$ echo 'typeset -U fpath' >> ~/.zshenv
$ echo 'fpath=(~/fpath $fpath)' >> ~/.zshenv
$ . ~/.zshenv
$ rm -rf ~/.zcomp*
$ compinit

Чтобы включился кэш:

$ zstyle ':completion::complete:*' use-cache 1
$ echo "!!" >> ~/.zshrc

Тогда список установленных пакетов сохранится в ~/.zcomcache/,
кэш автоматически обновляется при изменении базы rpm и/или apt.

Пример дополнения:

$ rpm --eval %co<TAB>
%compress_docs  %configure
$ rpm --eval %con<TAB>
$ rpm --eval %configure

Known bugs: --target как следует не дополняется.

Как оно теперь будет работать.  В массивах rpmb_opts0, rpmq_opts0 и др.
лежат опции командной строки, которые идентифицируют соответствующий режим.
Т.е. в rpmb_opts0 находятся опции типа (-bc, -bb, -ba), в rpmq_opts0 --
опции типа (-qa, -qi).  В массивах rpmb_opts1, rpmq_opts1 лежат
дополнительные опции для соответствующего режима, например
--short-circut для rpmb и --lastchange для rpmq.

Смысл в том, что при использовании специализированных команд (rpmbuild,
rpmquery) соответствующие опции opts0 и opts1 можно будет скармливать в
любом порядке.  При использовании rpm сначала нужно будет задать режим с
помощью opts0, а затем уже подключится соответствующий opts1.

Т.е. можно будет дополнять 
$ rpmbuild --short-circuit -ba ...
но не
$ rpm --short-circuit -ba ...
а только
$ rpm -ba --short-circuit ...

Это ограничение можно как-то обойти, но я пока не знаю как.
rpmpopt -- хитрая штука.  Например

$ rpm -qg Editors
и
$ rpm -gq Editors

работают одинаково хорошо, также как и (уже в другом режиме)

$ rpm -Vg Editors
$ rpm -gV Editors

а в других случаях -g не работает.

Кстати группы тоже дополняются:

$ rpm -qg D<TAB>
$ rpm -qg De<TAB>
$ rpm -qg Development/
$ rpm -qg Development/<TAB>
C                 Functional        KDE\ and\ QT      Objective-C       Ruby
C++               GNOME\ and\ GTK+  Kernel            Other             Scheme
Databases         Haskell           Lisp              Perl              Tcl
Debuggers         Java              ML                Python

Жду замечаний.
----------- следующая часть -----------
#compdef rpm rpmb rpmd rpme rpmi rpmk rpmq rpmt=rpmb rpmu=rpmi rpmv rpmbuild=rpmb rpmdb=rpmd rpmquery=rpmq rpmsign=rpmk rpmverify=rpmv

# 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
#  tags
#    complete a tag name
#  capability
#    complete a capability
#  relocate
#    complete a `old=new' pair of paths

_rpm () {
  local curcontext="$curcontext" state lstate line nm="$compstate[nmatches]"
  typeset -A opt_args
  local ret=1
  local -a tmp expl

  local -a common_opts
  common_opts=(
    -{\?,-help}'[print help information]'
    '--version[print the version of rpm being used]'
    '(-v --verbose)--quiet[provide less detailed output]'
    '(--quiet)*'-{v,-verbose}'[provide more detailed output]'
    '--define[define macro <name> with value <body>]:'
    '--eval[print macro expansion]:macro:->macros'
    -{r,-root}'[use <dir> as the top level directory]:rpm root directory:_files -/'
    '--dbpath[use <dir> as the directory for the database]:rpm database path:_files -/'
    '--macros[read <file:...> instead of default macro file(s)]:resource file:_files'
    '--rcfile[read <file:...> instead of default rpmrc file(s)]:resource file:_files'
    '--showrc[display final rpmrc and macro configuration]'
  
    '--ftpproxy:ftp proxy server:_hosts'
    '--ftpport:ftp port number'
    '--httpproxy:http proxy server:_hosts'
    '--httpport:http port number'
    '--pipe:pipe command:->command'
  )

  local -a rpmb_opts0 rpmb_opts1
  rpmb_opts0=(
    '(-t)-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' \
    '(-b -t --recompile)--rebuild[build binary package from source package]:*:source rpm file:->build_src'
    '(-b -t --rebuild)--recompile[build through %install from source package]:*:source rpm file:->build_src'
  )
  rpmb_opts1=(
    --{with,enable}'[enable configure option for build]:'
    --{without,disable}'[disable configure option for build]:'
    '--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:'
    '--short-circuit[skip straight to specified stage]'
    --{clean,nobuild,rmsource,sign,test}
  )

  # package selection options of which only one can be used
  local -a selectopts sopts
  selectopts=(
    -{a,-all}'[query all packages]'
    -{f,-file}'[query packages that own specified files]'
    -{p,-package}'[query uninstalled packages]'
    -{g,-group}'[query packages in one of specified groups]'
    --fileid --hdrid --pkgid --tid --querybynumber
    --triggeredby --whatprovides --whatrequires
  )
  sopts=${selectopts%\[*}\ --specfile
  selectopts=(
    "(* $sopts)"${selectopts[1,2]}
    "($sopts)"${selectopts[3,-1]}
    '(-a --all)*: :->package-select'
  )

  local -a rpmdb_opts0 rpmdb_opts1
  rpmd_opts0=(
    '--initdb[initialize database]'
    '--rebuilddb[rebuild database]'
  )
  rpmd_opts1=(
    # none so far
  )

  local -a rpme_opts0 rpme_opts1
  rpme_opts0=( '(-e --erase)'-{e+,-erase}'[uninstall mode]:*:uninstall:->uninstall' )
  rpme_opts1=( --{allmatches,justdb,repackage,test} --no{deps,scripts,preun,postun,trigger{s,un,postun}} )

  local -a rpmi_opts0 rpmi_opts1
  rpmi_opts0=(
    '(-i --install)'-{i+,-install}'[install mode]:*:install:->install'
    '(-U --upgrade)'-{U+,-upgrade}'[upgrade mode]:*:upgrade:->upgrade'
    '(-F --freshen)'-{F+,-freshen}'[freshen mode]:*:upgrade:->upgrade'
  )
  rpmi_opts1=(
    '--excludepath:file to exclude:_files -/'
    '--relocate:relocate:->relocate'
    '--prefix:package prefix directory:_files -/'
    '(-h --hash)'-{h,-hash}
    '(--replacepkgs --replacefiles --oldpackage)--force'
    '(--force)--'{replacefiles,replacepkgs}
    --{aid,allfiles,badreloc,excludedocs,ignorearch,ignoreos,ignoresize,includedocs,justdb,percent,repackage,test}
    --np{digest,signature,deps,suggest,order,pre,post,preun,postun,trigger{s,in,un,postun}}
    '(--nopre --nopost --nopreun --nopostun)--noscripts'
  )

  local -a rpmk_opts0 rpmk_opts1
  rpmk_opts0=( 
    '(-K --checksig)'-{K+,-checksig}'[signature check mode]:*:sigcheck:->sigcheck'
    '(--resign --addsign)'--{resign,addsign}':*:package:->package_file'
  )
  rpmk_opts1=( --no{gpg,pgp,md5,signature,digest} )

  local -a rpmq_opts0 rpmq_opts1
  rpmq_opts0=( -{q+,-query}'[query mode]:*:query:->query' --querytags )
  rpmq_opts1=(
      # --dump requires one of -{l,c,d}
      # --triggers requires --script
    "${(@)selectopts}"
    "($sopts)--specfile[query specified spec file as if it were a package]"
    '(-i --info)'{-i,--info}'[display package information]'
    '--changelog[display change log]'
    '--lastchange[display last changelog entry]'
    '(-s --state -l --list --filesbypkg)'{-l,--list}'[display package file list]'
    '(-s --state -l --list --filesbypkg)'{-s,--state}'[show file states]'
    '(-s --state -l --list)--filesbypkg[list files with package names]'
    -{d,-docfiles}'[documentation files only]'
    -{c,-configfiles}'[configuration files only]'
    '--dump[show all information]'
    '--provides[show capabilities provided]'
    \*--{qf,queryformat}'[specify format for package information]:rpm query format:->tags'
    -{R,-requires}'[list dependencies]'
    '--scripts[show (un)install scripts]'
    {--triggers,--triggerscripts}'[show trigger scripts]'
  )

  local -a rpmv_opts0 rpmv_opts1
  rpmv_opts0=( '(-V -y --verify)'{-V+,-y+,--verify}'[verify mode]:*:verify:->verify' )
  rpmv_opts1=( "${(@)selectopts}" --no{deps,files,scripts,digest,signature,linkto,md5,size,user,group,mtime,mode,rdev} )

  case "$service" in
  rpmq)
    state=query
    ;&
  rpmv)
    state=verify
    ;&
  rpm?)
    local service_opts0="${service}_opts0" service_opts1="${service}_opts1"
    _arguments -C -s "${(@)common_opts}" "${(P@)service_opts0}" "${(P@)service_opts1}" && ret=0
    ;;
  rpm)
    _arguments -C -s "${(@)common_opts}" "${(@)rpmb_opts0}" "${(@)rpmd_opts0}" "${(@)rpme_opts0}" \
      "${(@)rpmi_opts0}" "${(@)rpmk_opts0}" "${(@)rpmq_opts0}" "${(@)rpmv_opts0}" \
      '--setperms[set file permissions]:*:package:->setattrs' \
      '--setugids[set file owner/group]:*:package:->setattrs' \
        && ret=0
    ;;
  esac

  not_opts() {
    local var="$1"
    print -l -- "\!${(@)^${(@)${(@)${(@)${(P@)var%%[[:]*}#*)}%[+-]}#\*}}"
  }

  # 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)
      _arguments -s "${(@)common_opts}" "${(f@)$(not_opts rpmq_opts0)}" "${(@)rpmq_opts1}" && ret=0
      ;;
    setattrs)
      _arguments -s --set{perm,ugids} "${selectopts[@]}" && ret = 0
      ;;
    verify)
      _arguments -s "${(@)common_opts}" "${(f@)$(not_opts rpmv_opts0)}" "${(@)rpmv_opts1}" \
      ;;
    install|upgrade)
      _arguments -s "${(@)common_opts}" "${(f@)$(not_opts rpmi_opts0)}" "${(@)rpmi_opts1}" \
	'*:package file:->package_file' && ret=0
      ;;
    uninstall)
      _arguments -s "${(@)common_opts}" "${(f@)$(not_opts rpme_opts0)}" "${(@)rpme_opts1}" \
	'*:package:->package' && ret=0
      ;;
    build_b)
      _arguments -s "${(@)common_opts}" "${(f@)$(not_opts rpmb_opts0)}" "${(@)rpmb_opts1}" \
        '*:spec file:_files -g "*.spec(-.)"' && ret=0
      ;;
    build_t)
      _arguments -s "${(@)common_opts}" "${(f@)$(not_opts rpmb_opts0)}" "${(@)rpmb_opts1}" \
        '*:tar file:_files -g "*.(#i)(tar|tar.gz|tar.bz2|tgz)(-.)"' && ret=0
      ;;
    build_src)
      _arguments -s "${(@)common_opts}" "${(f@)$(not_opts rpmb_opts0)}" "${(@)rpmb_opts1}" \
        '*:source package file:_files -g "(#i)*.src.rpm(-.)"' && ret=0
      ;;
    sigcheck)
      _arguments -s "${(@)common_opts}" "${(f@)$(not_opts rpmk_opts0)}" "${(@)rpmk_opts1}" \
	'*:package file:->package_file' && ret=0
      ;;
    package-select)
      case "${opt_args[(i)${sopts// /|}]}" in
	-f|--file) _files ;;
	-p|--package) state=package_file ;;
	-g|--group) state=groups ;;
	--fileid|--pkgid) _message -e md5 md5 ;;
	--hdrid) _message -e sha1 sha1 ;;
	--querybynumber) _message -e value number ;;
	--what*) state=capabilities ;;
	--specfile) state=spec_files ;;
	*) state=package ;;
     esac
    ;;
    macros)
      local -a macros
      local mfile
      for mfile in {/usr/lib/rpm/{,*/}macros,/etc/rpm/macros{,.d/*},~/.rpmmacros}(-.N); do
        macros+=( ${${(M)${(f)"$(<$mfile)"}:#%[^\{]*}%%[[:blank:]]*} )
      done
      if zstyle -t ":completion:${curcontext}:macros" prefix-hidden; then
	macros=( ${macros#%} )
	_wanted macros expl macro compadd -p '%' -a - macros
      else
	_wanted macros expl macro compadd -a - macros
      fi
      ;;
    command)
      compset -q
      _normal
      ;;
    target)
      if [[ ${+_rpm_targets} -eq 0 ]]; then
	typeset -gaU _rpm_targets
	_rpm_targets=( ${${(M)${(f)"$(_call_programs targets rpm --showrc)"}:#compatible archs*}##*: } )
      fi
      _wanted targets expl 'target platform' compadd -a - _rpm_targets && ret=0
      ;;
    groups)
      if [[ ${+_rpm_groups} -eq 0 ]]; then
        typeset -gaU _rpm_groups
        _rpm_groups=( "${(f)$(</usr/lib/rpm/GROUPS)}" )
      fi
      _wanted groups expl 'group' _multi_parts / _rpm_groups && ret=0
    ;;
    package)
      _wanted packages expl 'package' \
	  _rpm_packages installed && ret=0
      ;;
    spec_files)
      _wanted specfiles expl 'spec file' \
	  _files -g '*.spec(-.)' && ret=0
      ;;
    package_file)
      _wanted files expl '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
      ;;
    tags)
      local -a suf
      if compset -P "*%*${${QIPREFIX:+{}:-\{}"; then
        compset -S '(|\\)}*' || suf=( -qS ${${QIPREFIX:+\}}:-\\\}} )
	_wanted tags expl 'rpm tag' compadd -M 'm:{a-z}={A-Z}' "$suf[@]" - \
	    "${(L@)${(@f)$(_call_program tags rpm --querytags 2>/dev/null)}#RPMTAG_}" && ret=0
      else
	_message -e formats 'rpm query format'
      fi
      ;;
    capabilities)
      _wanted capabilities expl capability compadd \
	  ${(f)"$(_call_program capabilities rpm -qa --queryformat '%\{requirename}\\n' 2>/dev/null)"}
      ;;
    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
}

_rpm "$@"
----------- следующая часть -----------
Было удалено вложение не в текстовом формате...
Имя     : =?iso-8859-1?q?=CF=D4=D3=D5=D4=D3=D4=D7=D5=C5=D4?=
Тип     : application/pgp-signature
Размер  : 189 байтов
Описание: =?iso-8859-1?q?=CF=D4=D3=D5=D4=D3=D4=D7=D5=C5=D4?=
Url     : <http://lists.altlinux.org/pipermail/sisyphus/attachments/20041023/f2c65615/attachment-0003.bin>


Подробная информация о списке рассылки Sisyphus