From dc10559ef1236ec285ff6bb45fe6292a436e3693 Mon Sep 17 00:00:00 2001 From: Jan Pokorný Date: Wed, 26 Feb 2014 14:34:17 +0100 Subject: completion: way to get bash (and perhaps others) autocompletions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently it provides WIP standalone bash completion, but should be fairly easy to implement support for, e.g. bash-completion dependent version, zsh-specific one, etc. Signed-off-by: Jan Pokorný --- completion.py | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 completion.py (limited to 'completion.py') diff --git a/completion.py b/completion.py new file mode 100644 index 0000000..c187188 --- /dev/null +++ b/completion.py @@ -0,0 +1,127 @@ +# -*- coding: UTF-8 -*- +# Copyright 2014 Red Hat, Inc. +# Part of clufter project +# Licensed under GPLv2 (a copy included | http://gnu.org/licenses/gpl-2.0.txt) +"""Shell completion formatters""" +__author__ = "Jan Pokorný " + +from os.path import basename + +from .utils import args2sgpl + + +class Completion(object): + mapping = {} + + def __init__(self, prog, opts_common, opts_main, opts_nonmain): + self._prog = prog + self._opts_common = opts_common + self._opts_main = opts_main + self._opts_nonmain = opts_nonmain + + def scripts_prologue(self): + return '' + + def handle_script(self, command): + raise RuntimeError('subclasses ought to override this') + + def scripts_epilogue(self, handles): + return '' + + def __call__(self, commands): + handles, scripts = reduce( + lambda (acc_handle, acc_script), cmd: + (lambda handle, script: ( + acc_handle + [(cmd.__class__.__name__, handle)], + acc_script + [script] + ))(*self.handle_script(cmd)), + commands, + ([], []) + ) + scripts = [self.scripts_prologue(), ] + scripts + scripts.append(self.scripts_epilogue(handles)) + return '\n\n'.join(scripts) + + @classmethod + def deco(basecls, which): + def deco(cls): + basecls.mapping[which] = cls + return cls + return deco + + @classmethod + def get_completion(cls, which, *args): + return cls.mapping[which](*args) + + +@Completion.deco("bash") +class BashCompletion(Completion): + def __init__(self, prog, *args): + prog = basename(prog) + super(BashCompletion, self).__init__(prog, *args) + self._name = prog.replace('-', '_') + + @staticmethod + def _namespaced_identifier(namespace, name=None): + return '_'.join(filter(lambda x: x is not None, ('', namespace, name))) + + @staticmethod + def _format_function(name, bodylines): + bodylines = args2sgpl(bodylines) + return ("{0}() {{\n\t{1}\n}}" + .format(name, '\n\t'.join(bodylines).rstrip('\t'))) + + @staticmethod + def scripts_prologue(): + return """\ +# bash completion start +# add me to ~/.profile persistently or eval on-the-fly in bash""" + + def handle_script(self, cmd): + clsname = cmd.__class__.__name__ + handle = self._namespaced_identifier(self._name, clsname) + _, opts = cmd.parser_desc_opts + main = """\ +local opts="{0}" + +[[ "$1" =~ -.* ]] \\ + && compgen -W "${{opts}}" -- $1"""\ + .format( + ' '.join(reduce(lambda a, b: a + list(b[0]), opts, [])) + ).splitlines() + + return handle, self._format_function(handle, main) + + def scripts_epilogue(self, handles): + handle = self._namespaced_identifier(self._name) + handles_dict = dict(handles) + opts = self._opts_common, self._opts_main, self._opts_nonmain + main = """\ +local commands="{1}" +local opts_common="{2}" +local opts_main="{3}" +local opts_nonmain="{4}" + +local cur fnc i=${{COMP_CWORD}} +while true; do + test ${{i}} -eq 0 && break || let i-=1 + cur=${{COMP_WORDS[${{i}}]}} + [[ "${{cur}}" =~ -.* ]] && continue + fnc=_main_boostrap_${{cur}} + declare -f ${{fnc}} >/dev/null && COMPREPLY+=( $(${{fnc}} $2) ) + [[ "$2" =~ -.* ]] \\ + && COMPREPLY+=( $(compgen -W "${{opts_common}} ${{opts_nonmain}}" -- $2) ) + return +done + +case "$2" in +-*) COMPREPLY=( $(compgen -W "${{opts_common}} ${{opts_main}}" -- $2) );; +*) COMPREPLY=( $(compgen -W "${{commands}}" -- $2) );; +esac""" .format( + self._name, + ' '.join(handles_dict.keys()), + *(' '.join(reduce(lambda a, b: a + list(b[0]), o, [])) + for o in opts) + ).splitlines() + epilogue = "complete -o default -F {0} {1}".format(handle, self._prog) + return '\n\n'.join([self._format_function(handle, main), epilogue]) -- cgit