diff options
-rw-r--r-- | command_manager.py | 6 | ||||
-rw-r--r-- | completion.py | 127 | ||||
-rw-r--r-- | main.py | 9 |
3 files changed, 141 insertions, 1 deletions
diff --git a/command_manager.py b/command_manager.py index b63b41c..9194b09 100644 --- a/command_manager.py +++ b/command_manager.py @@ -38,6 +38,9 @@ class CommandManager(PluginManager): .format(commands)) self._commands = self._resolve(flt_mgr.filters, commands) + def __iter__(self): + return self._commands.itervalues() + @staticmethod def _resolve(filters, commands): for cmd_name, cmd_cls in commands.items(): @@ -63,6 +66,9 @@ class CommandManager(PluginManager): def commands(self): return self._commands.copy() + def completion(self, completion): + return completion(iter(self)) + def __call__(self, parser, args=None): """Follow up of the entry point, facade to particular commands""" ec = EC.EXIT_SUCCESS 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ý <jpokorny @at@ Red Hat .dot. com>" + +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]) @@ -13,6 +13,7 @@ from optparse import OptionParser, \ from . import version_text, description_text from .command_manager import CommandManager +from .completion import Completion from .error import EC from .format_manager import FormatManager from .filter_manager import FilterManager @@ -152,11 +153,17 @@ def run(argv=None, *args): logging.basicConfig(level=opts.loglevel) # what if not the first use? cm = CommandManager(FilterManager(FormatManager())) - if not opts.help and (opts.list or not args): + if not opts.help and (opts.list or opts.completion or not args): ind = ' ' * parser.formatter.indent_increment cmds = "Available commands (cmd):\n{0}".format(cm.cmds(ind=ind)) if opts.list: print cmds + elif opts.completion: + print cm.completion( + Completion.get_completion(opts.completion, + prog, + opts_common, opts_main, opts_nonmain) + ) else: print parser.format_customized_help( usage="%prog [<global option> ...] [<cmd> [<cmd option ...>]]", |