Controlg: tags -1 + patch Here is a preliminary patch that uses code based on bpython's importcompletion to enumerate all modules. It also adds caching for the list of modules and handles submodules better. The caching policy should be improved, but I couldn't think of a clever way to determine whether it should be updated or not.
Regards -- Sebastian Ramacher
diff --git a/Completion/Unix/Command/_python b/Completion/Unix/Command/_python index ca1ed37..1005667 100644 --- a/Completion/Unix/Command/_python +++ b/Completion/Unix/Command/_python @@ -1,4 +1,36 @@ -#compdef python +#compdef python python3 python2 python2.7 python2.6 python3.2 python3.3 +# +# 2012-28-10: Add proper completion for Python modules. +# Instead of importing all modules, the new code iterates over all folders +# found in sys.path and enumerates the modules with imp. This prevents any +# negative side effects that importing a module could have, e.g. a call to +# sys.exit. +# +# Additionally, the result is cached. +# +# The code is based on bpython's importcomplete which was written by Andreas +# Stuehrk and is covered by the following copyright and license: +# +# Copyright (c) 2009-2011 Andreas Stuehrk +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + # Python 2.6 # Python 3.0 @@ -8,6 +40,116 @@ typeset -A opt_args local -a args +_python_modules () { + zstyle -s ":completion:${curcontext}:" cache-policy update_policy + if [[ -z "$update_policy" ]]; then + zstyle ":completion:${curcontext}:" cache-policy \ + _python_modules_caching_policy + fi + + local -a python_modules + if _cache_invalid $python_modules || ! _retrieve_cache ${python_modules#_}; + then + local prog + prog="$(cat <<EOF +import imp +import os +import sys +import warnings +try: + from warnings import catch_warnings +except ImportError: + import contextlib + @contextlib.contextmanager + def catch_warnings(): + filters = warnings.filters + warnings.filters = list(filters) + try: + yield + finally: + warnings.filters = filters + +py3 = (sys.version_info[0] == 3) + +def find_modules(path): + if not os.path.isdir(path): + return + + try: + filenames = os.listdir(path) + except EnvironmentError: + filenames = [] + for name in filenames: + if not any(name.endswith(suffix[0]) for suffix in imp.get_suffixes()): + if '.' in name: + continue + elif os.path.isdir(os.path.join(path, name)): + continue + for suffix in imp.get_suffixes(): + if name.endswith(suffix[0]): + name = name[:-len(suffix[0])] + break + if py3 and name == "badsyntax_pep3120": + continue + try: + with catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + fo, pathname, _ = imp.find_module(name, [path]) + except (ImportError, SyntaxError): + continue + except UnicodeEncodeError: + continue + else: + if fo is not None: + fo.close() + else: + # Yay, package + for subname in find_modules(pathname): + if subname != '__init__': + yield '%s.%s' % (name, subname) + yield name + +def find_all_modules(path=None): + modules = set() + if path is None: + modules.update(sys.builtin_module_names) + path = sys.path + + for p in path: + if not p: + p = os.curdir + for module in find_modules(p): + modules.add(module) + + for m in modules: + print(m) + +find_all_modules() +EOF)" + + python_modules=( + ${(f)"$(_call_program modules $words[1] -c '$prog')"} + ) + _store_cache ${python_modules#_} $python_modules + fi + + _wanted modules expl module \ + _multi_parts '.' python_modules && return +} + +_python_modules_caching_policy () { + local -a oldp + + # rebuild if cache is more than a week old + oldp=( "$1"(mw+1) ) + (( $#oldp )) && return 0 + + # TODO: rebuild if any of the modules have changed + + return 1 +} + + if _pick_variant python3=Python\ 3 python2 --version; then args=( '(-bb)-b[issue warnings about str(bytes_instance), str(bytearray_instance) and comparing bytes/bytearray with str]' @@ -43,12 +185,7 @@ _arguments -C -s -S "$args[@]" \ '*::script argument: _normal' && return if [[ "$state" = modules ]]; then - local -a modules - modules=( - ${${=${(f)"$(_call_program modules $words[1] -c \ - 'from\ pydoc\ import\ help\;\ help\(\"modules\"\)')"}[2,-3]}:#\(package\)} - ) - _wanted modules expl module compadd -a modules && return + _call_function ret _python_modules && return ret fi return 1
signature.asc
Description: Digital signature