summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--Completion/Base/.distfiles6
-rw-r--r--Completion/Base/_cache_invalid21
-rw-r--r--Completion/Base/_retrieve_cache31
-rw-r--r--Completion/Base/_store_cache36
-rw-r--r--Completion/Builtins/_zstyle2
-rw-r--r--Completion/Linux/_rpm435
-rw-r--r--Completion/User/_perl_modules108
-rw-r--r--Doc/Zsh/compsys.yo72
9 files changed, 474 insertions, 244 deletions
diff --git a/ChangeLog b/ChangeLog
index 231256fc5..f8311a2ad 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2000-08-02  Adam Spiers  <adam@spiers.net>
+
+	* 12486: Completion/Base/{.distfiles,_cache_invalid,_retrieve_cache,
+	_store_cache}, Completion/Builtins/_zstyle, Completion/Linux/_rpm,
+	Completion/User/_perl_modules, Doc/Zsh/compsys.yo: new completion
+	caching layer
+
 2000-08-02  Sven Wischnowsky  <wischnow@zsh.org>
 
 	* 12483: Completion/Base/_arguments, Completion/Core/_complete,
diff --git a/Completion/Base/.distfiles b/Completion/Base/.distfiles
index a906bfc76..0cbe1f97f 100644
--- a/Completion/Base/.distfiles
+++ b/Completion/Base/.distfiles
@@ -1,7 +1,7 @@
 DISTFILES_SRC='
     .distfiles 
-    _arg_compile _arguments _brace_parameter _combination
+    _arg_compile _arguments _brace_parameter _cache_invalid _combination
     _command_names _condition _default _describe _equal _first _in_vared
-    _jobs _math _parameter _precommand _redirect _regex_arguments _subscript
-    _tilde _value _values
+    _jobs _math _parameter _precommand _redirect _regex_arguments
+    _retrieve_cache _store_cache _subscript _tilde _value _values
 '
diff --git a/Completion/Base/_cache_invalid b/Completion/Base/_cache_invalid
new file mode 100644
index 000000000..e55381439
--- /dev/null
+++ b/Completion/Base/_cache_invalid
@@ -0,0 +1,21 @@
+#autoload
+#
+# Function to decide whether a completions cache needs rebuilding
+
+local _cache_ident _cache_dir _cache_path _cache_policy
+_cache_ident="$1"
+
+# If the cache is disabled, we never want to rebuild it, so pretend
+# it's valid.
+zstyle -t ":completion:${curcontext}:" use-cache || return 1
+
+zstyle -s ":completion:${curcontext}:" cache-path _cache_dir
+: ${_cache_dir:=${ZDOTDIR:-$HOME}/.zcompcache}
+_cache_path="$_cache_dir/$_cache_ident"
+
+# See whether the caching policy says that the cache needs rebuilding
+# (the policy will return 0 if it does).
+zstyle -s ":completion:${curcontext}:" cache-policy _cache_policy
+[[ -n "$_cache_policy" ]] && "$_cache_policy" "$_cache_path" && return 0
+
+return 1
diff --git a/Completion/Base/_retrieve_cache b/Completion/Base/_retrieve_cache
new file mode 100644
index 000000000..6a82cd48b
--- /dev/null
+++ b/Completion/Base/_retrieve_cache
@@ -0,0 +1,31 @@
+#autoload
+#
+# Retrieval component of completions caching layer
+
+local _cache_ident _cache_dir _cache_path _cache_policy
+_cache_ident="$1"
+
+if zstyle -t ":completion:${curcontext}:" use-cache; then
+  # Decide which directory to retrieve cache from, and ensure it exists
+  zstyle -s ":completion:${curcontext}:" cache-path _cache_dir
+  : ${_cache_dir:=${ZDOTDIR:-HOME}/.zcompcache}
+  if [[ ! -d "$_cache_dir" ]]; then
+    [[ -e "$_cache_dir" ]] &&
+      _message "cache-dir ($_cache_dir) isn't a directory\!"
+    return 1
+  fi
+
+  _cache_path="$_cache_dir/$_cache_ident"
+
+  if [[ -e "$_cache_path" ]]; then
+    _cache_invalid "$_cache_ident" && return 1
+
+    . "$_cache_path"
+    return 0
+  else
+    return 1
+  fi
+else
+  return 1
+fi
+
diff --git a/Completion/Base/_store_cache b/Completion/Base/_store_cache
new file mode 100644
index 000000000..2fe7dfcb6
--- /dev/null
+++ b/Completion/Base/_store_cache
@@ -0,0 +1,36 @@
+#autoload
+#
+# Storage component of completions caching layer
+
+local _cache_ident
+_cache_ident="$1"
+
+if zstyle -t ":completion:${curcontext}:" use-cache; then
+  # Decide which directory to cache to, and ensure it exists
+  zstyle -s ":completion:${curcontext}:" cache-path _cache_dir
+  : ${_cache_dir:=${ZDOTDIR:-$HOME}/.zcompcache}
+  if [[ ! -d "$_cache_dir" ]]; then
+    if [[ -e "$_cache_dir" ]]; then
+      _message "cache-dir style points to a non-directory\!"
+    else
+      mkdir -p "$_cache_dir"
+      if [[ ! -d "$_cache_dir" ]]; then
+        _message "Couldn't create cache-dir $_cache_dir"
+        return 1
+      fi
+    fi
+  fi
+
+  shift
+  for var; do
+    case ${(Pt)var} in
+    (*readonly*) ;;
+    (*(association|array)*) print -r "$var=( ${(kv@Pqq)^^var} )";;
+    (*)                     print -r "$var=${(Pqq)^^var}";;
+    esac
+  done >! "$_cache_dir/$_cache_ident"
+else
+  return 1
+fi
+
+return 0
diff --git a/Completion/Builtins/_zstyle b/Completion/Builtins/_zstyle
index fefa8af51..abfec06d3 100644
--- a/Completion/Builtins/_zstyle
+++ b/Completion/Builtins/_zstyle
@@ -15,6 +15,7 @@ styles=(
   assign-list            c:
   auto-description	 c:
   break-keys             c:
+  cache-path		 'c:_wanted directories expl directory _path_files -/'
   command                c:command
   completer		 c:completer
   completions		 c:bool
@@ -80,6 +81,7 @@ styles=(
   suffix		 c:bool
   tag-order		 c:tag
   try-to-use-pminst	 c:bool
+  use-cache		 c:bool
   use-compctl		 c:urgh
   users			 c:_users
   users-hosts		 c:user-host
diff --git a/Completion/Linux/_rpm b/Completion/Linux/_rpm
index e047af6d6..11bb370b8 100644
--- a/Completion/Linux/_rpm
+++ b/Completion/Linux/_rpm
@@ -41,213 +41,238 @@
 
 # Used by `_arguments', made local here.
 
-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.
-
-  _funcall ret _rpm_$state && return ret
-
-  # Copy the state and reset `state', to simplify the test above.
-
-  lstate="$state"
+_rpm () {
+  local curcontext="$curcontext" state lstate line nm="$compstate[nmatches]"
+  typeset -A opt_args
+  
   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[@]}" "${pathopts[@]}" \
-      --no{deps,md5,files} \
-      '*:RPM package:->package' && 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\(.\*\|\)' )
+  
+  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.
+  
+    _funcall 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[@]}" "${pathopts[@]}" \
+        --no{deps,md5,files} \
+        '*:RPM package:->package' && 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,rmsource,sign,test} \
+        '--target:specify a build target:->target'\
+        '--buildroot:build root directory:_files -/' \
+        '--buildarch:architecture for which to build:->target' \
+        '--buildos:ositecture for which to build:' \
+        '--timecheck:time check (seconds):' "$tmp[1]" && ret=0
+      ;;
+    sigcheck)
+      _arguments -s \
+        '(-K)--checksig' '(--checksig)-K' \
+        "${commonopts[@]}" \
+        --no{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 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 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 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
+}
 
-    _arguments -s \
-      "${commonopts[@]}" "${pathopts[@]}" \
-      --{short-circuit,clean,rmsource,sign,test} \
-      '--target:specify a build target:->target'\
-      '--buildroot:build root directory:_files -/' \
-      '--buildarch:architecture for which to build:->target' \
-      '--buildos:ositecture for which to build:' \
-      '--timecheck:time check (seconds):' "$tmp[1]" && ret=0
-    ;;
-  sigcheck)
-    _arguments -s \
-      '(-K)--checksig' '(--checksig)-K' \
-      "${commonopts[@]}" \
-      --no{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 target rpm --showrc 2> /dev/null |grep 'compatible archs'|sed 's/.*: //') && ret=0
-    ;;
-  package_or_file)
-    state=package_file
-    ;&
-  package)
-    _wanted packages expl 'RPM package' \
-        compadd -M 'r:|-=* r:|=*' - $(_call packages rpm -qa 2> /dev/null) && 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 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
+# 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
 
-    _files "$expl[@]" -/ && ret=0
-    ;;
-  esac
+_rpms_caching_policy () {
+  # rebuild if cache is more than a week old
+  oldp=( "$1"(Nmw+1) )
+  (( $#oldp )) && return 0
 
-  [[ ret -eq 0 || $nm -ne $compstate[nmatches] ]] && return 0
-done
+  [[ /var/lib/rpm/packages.rpm -nt "$1" ]]
+}
 
-return ret
+_rpm "$@"
diff --git a/Completion/User/_perl_modules b/Completion/User/_perl_modules
index 88efdd395..117933022 100644
--- a/Completion/User/_perl_modules
+++ b/Completion/User/_perl_modules
@@ -17,49 +17,85 @@
 #   algorithm (the zsh code does almost the same, but only misses
 #   modules which don't begin with an uppercase letter).
 
-local opts
-zparseopts -D -a opts S: q
-
-if [[ ${+_perl_modules} -eq 0 ]]; then
-  if zstyle -t ":completion:${curcontext}:modules" try-to-use-pminst \
-     && (( ${+commands[pminst]} )); then
-    _perl_modules=( $(pminst) )
-  else
-    local inc libdir new_pms
-    if (( ${+commands[perl]} )); then
-      inc=( $( perl -e 'print "@INC"' ) )
+_perl_modules () {
+  local opts
+  zparseopts -D -a opts S: q
+  
+  # Set a sensible default caching policy.  This has to be done inside
+  # this function otherwise we wouldn't know the context for the style.
+  local update_policy
+  zstyle -s ":completion:${curcontext}:" cache-policy update_policy
+  if [[ -z "$update_policy" ]]; then
+    zstyle ":completion:${curcontext}:" cache-policy \
+      _perl_modules_caching_policy
+  fi
+  
+  if ( [[ ${+_perl_modules} -eq 0 ]] || _cache_invalid perl_modules ) &&
+     ! _retrieve_cache perl_modules;
+  then
+    if zstyle -t ":completion:${curcontext}:modules" try-to-use-pminst &&
+       (( ${+commands[pminst]} ));
+    then
+      _perl_modules=( $(pminst) )
     else
-      # If perl isn't there, one wonders why the user's trying to
-      # complete Perl modules.  Maybe her $path is wrong?
-      _message "Didn't find perl on \$PATH; guessing @INC ..."
-
-      setopt localoptions extendedglob
-      inc=( /usr/lib/perl5{,/{site_perl/,}<5->.([0-9]##)}(N) 
-            ${(s.:.)PERL5LIB} )
+      local inc libdir new_pms
+      if (( ${+commands[perl]} )); then
+        inc=( $( perl -e 'print "@INC"' ) )
+      else
+        # If perl isn't there, one wonders why the user's trying to
+        # complete Perl modules.  Maybe her $path is wrong?
+        _message "Didn't find perl on \$PATH; guessing @INC ..."
+  
+        setopt localoptions extendedglob
+        inc=( /usr/lib/perl5{,/{site_perl/,}<5->.([0-9]##)}(N) 
+              ${(s.:.)PERL5LIB} )
+      fi
+  
+      typeset -agU _perl_modules  # _perl_modules is global, no duplicates
+      _perl_modules=( )
+  
+      for libdir in $inc; do
+        # Ignore cwd - could be too expensive e.g. if we're near /
+        if [[ $libdir == '.' ]]; then break; fi
+  
+        # Find all modules
+        if [[ -d $libdir && -x $libdir ]]; then
+        cd $libdir
+        new_pms=( {[A-Z]*/***/,}*.pm~*blib*(N) )
+        cd $OLDPWD
+        fi
+  
+        # Convert to Perl nomenclature
+        new_pms=( ${new_pms:r:fs#/#::#} )
+  
+        _perl_modules=( $new_pms $_perl_modules )
+      done
     fi
+  
+    _store_cache perl_modules _perl_modules
+  fi
+  
+  local expl
+  
+  _wanted modules expl 'Perl modules' compadd "$opts[@]" -a _perl_modules
+}
 
-    typeset -agU _perl_modules  # _perl_modules is global, no duplicates
-    _perl_modules=( )
-
-    for libdir in $inc; do
-      # Ignore cwd - could be too expensive e.g. if we're near /
-      if [[ $libdir == '.' ]]; then break; fi
+_perl_modules_caching_policy () {
+  local _perllocals
 
-      # Find all modules
-      if [[ -d $libdir && -x $libdir ]]; then
-      cd $libdir
-      new_pms=( {[A-Z]*/***/,}*.pm~*blib*(N) )
-      cd $OLDPWD
-      fi
+  # rebuild if cache is more than a week old
+  oldp=( "$1"(Nmw+1) )
+  (( $#oldp )) && return 0
 
-      # Convert to Perl nomenclature
-      new_pms=( ${new_pms:r:fs#/#::#} )
+  _perllocals=( /usr/lib/perl5/**/perllocal.pod(N) )
 
-      _perl_modules=( $new_pms $_perl_modules )
+  if (( $#_perllocals )); then
+    for pod in $_perllocals; do
+      [[ "$pod" -nt "$1" ]] && return 0
     done
   fi
-fi
 
-local expl
+  return 1
+}
 
-_wanted modules expl 'Perl modules' compadd "$opts[@]" -a _perl_modules
+_perl_modules "$@"
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index cb0d4880a..9a0c1a57d 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -914,6 +914,13 @@ should be a pattern and all keys matching this pattern will cause the
 widget to stop incremental completion without the key having any
 further effect.
 )
+kindex(cache-path, completion style)
+item(tt(cache-path))(
+This style defines the path where any cache files containing dumped
+completion data are stored.  Defaults to `tt($DOTDIR/.zcompcache)', or
+`tt($HOME/.zcompcache)' if tt($DOTDIR) is not defined.  The completion
+layer will not be used unless the tt(use-cache) style is set.
+)
 kindex(command, completion style)
 item(tt(command))(
 In many places, completion functions need to call external commands to
@@ -2083,6 +2090,13 @@ sensible default behavior that causes arguments (whether normal command
 arguments or arguments of options) to be completed before option names for
 most commands.
 )
+kindex(use-cache, completion style)
+item(tt(use-cache))(
+If this is set, the completion caching layer is activated for any completions
+which use it (via the tt(_store_cache), tt(_retrieve_cache), and
+tt(_cache_invalid) functions).  The directory containing the cache
+files can be changed with the tt(cache-path) style.
+)
 kindex(use-compctl, completion style)
 item(tt(use-compctl))(
 If this style is set to a string em(not) equal to tt(false), tt(0),
@@ -3189,6 +3203,22 @@ arguments. The first one describes the first argument as a
 be completed. The last description says that all other arguments are
 `var(page numbers)' but does not give possible completions.
 )
+findex(_cache_invalid)
+item(tt(_cache_invalid) var(cache_identifier))(
+This function returns 0 if the completions cache corresponding to the
+given cache identifier needs rebuilding.  It determines this by
+looking up the tt(cache-policy) style for the current context, and
+if it exists, runs the function of the same name, supplying the full
+path to the relevant cache file as the only argument.
+
+Example:
+
+example(_example_caching_policy () {
+    # rebuild if cache is more than a week old
+    oldp=( "$1"(Nmw+1) )
+    (( $#oldp ))
+})
+)
 findex(_call)
 item(tt(_call) var(tag) var(string) ...)(
 This function is used in places where a command is called, making it
@@ -3595,6 +3625,18 @@ while _tags; do
   (( ret )) || break
 done)
 )
+findex(_retrieve_cache)
+item(tt(_retrieve_cache) var(cache_identifier))(
+This function retrieves completion information from the file given by
+var(cache_identifier), stored in a directory specified by the
+tt(cache-path) style (defaults to tt(~/.zsh/cache)).  The return value
+is zero if retrieval was successful.  It will only attempt retrieval
+if the tt(use-cache) style is set, so you can call this function
+without worrying about whether the user wanted to use the caching
+layer.
+
+See tt(_store_cache) below for more details.
+)
 findex(_sep_parts)
 item(tt(_sep_parts))(
 This function is passed alternating arrays and separators as arguments.
@@ -3718,6 +3760,36 @@ separate set. With the tt(-m) option, the arguments are treated in the
 same way as the the values for the tt(tag-order) style (except for the 
 `tt(!...)', `tt(-)' and `tt(foo())' forms).
 )
+findex(_store_cache)
+item(tt(_store_cache) var(cache_identifier) var(vars) ...)(
+This function, when combined with tt(_retrieve_cache) and
+tt(_cache_invalid), makes it easy to implement a caching layer for
+your completion functions.  If a completion function needs to perform
+a costly operation in order to generate data which is used to
+calculate completions, you can store that data in variables, and use
+this function to dump the values of those variables to a file.  Then,
+if they are needed in subsequent shell invocations, they can be
+retrieved quickly from that file via tt(_retrieve_cache), avoiding the
+needly for repeating the costly operation.
+
+The var(cache_identifier) specifies the file which the data should be
+dumped to, and is stored in a directory specified by the
+tt(cache-path) style (defaults to tt(~/.zsh/cache)).  The remaining
+var(vars) arguments are the variables to dump to the file.
+
+The return value is zero if storage was successful.  The function will
+only attempt storage if the tt(use-cache) style is set, so you can
+call this function without worrying about whether the user wanted to
+use the caching layer.
+
+If your completion function avoids calling _retrieve_cache when it
+already has the completion data in the environment, it should probably
+at least call tt(_cache_invalid) to check whether this data and the
+data cached on disk is still valid.
+
+See the _perl_modules completion function for a simple example of
+usage of this caching layer.
+)
 findex(_tags)
 item(tt(_tags) [ tt(-C) var(name) [ var(tags) ... ] ])(
 If called with arguments, these are taken as the names of the tags for