summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--command_manager.py6
-rw-r--r--completion.py127
-rw-r--r--main.py9
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])
diff --git a/main.py b/main.py
index d510348..3b1d0ee 100644
--- a/main.py
+++ b/main.py
@@ -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 ...>]]",