-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcompletion.py
156 lines (134 loc) · 5.15 KB
/
completion.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# -*- coding: UTF-8 -*-
# Copyright 2017 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 functools import reduce
from os.path import basename
from .utils import args2sgpl
from .utils_2to3 import basestring, reduce_uu
from .utils_func import bifilter_unpack
from .utils_prog import cli_undecor
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, aliases):
return ''
def __call__(self, commands):
cmds, aliases = bifilter_unpack(
lambda name, obj: not isinstance(obj, basestring),
commands
)
handles, scripts = reduce_uu(
lambda acc_handle, acc_script, cmd_name, cmd:
(lambda handle, script: (
acc_handle + [(cmd_name, handle)],
acc_script + [script]
))(*self.handle_script(cmd)),
cmds,
([], [])
)
scripts = [self.scripts_prologue(), ] + scripts
scripts.append(self.scripts_epilogue(handles, aliases))
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):
# XXX include expert options?
def __init__(self, prog, *args):
prog = basename(prog)
super(BashCompletion, self).__init__(prog, *args)
self._name = cli_undecor(prog)
@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 (enquoted!) 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() # XXX not accurate without other opts
main = """\
local opts="{0}"
[[ "$1" =~ -.* ]] && compgen -W "${{opts}}" -- $1"""\
.format(
'\n'.join(reduce(lambda a, b:
a + [' '.join(i + j for i in b[0] for j in (('', '=')
if b[1].get('action', 'store')
in ('callback', 'store')
else ('', )
))], opts, []))
).splitlines()
handle = cli_undecor(handle)
return handle, self._format_function(handle, main)
def scripts_epilogue(self, handles, aliases):
handle = self._namespaced_identifier(self._name)
opts_common, opts_main, opts_nonmain = tuple(
'\n'.join(reduce(lambda a, b:
a + [' '.join(i + j for i in b[0] for j in (('', '=')
if b[1].get('action', 'store')
in ('callback', 'store')
else ('', )
))], o, []))
for o in (self._opts_common, self._opts_main, self._opts_nonmain)
)
alias_case = ' ' + '\n '.join(
'{0}) cur="{1}";;'.format(alias, to) for alias, to in aliases
)
# usage of self._name: see self._namespaced_identifier
main = \
r"""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
# handle aliases
case ${{cur}} in
{5}
esac
fnc=_{0}_${{cur/-/_}}
declare -f ${{fnc}} >/dev/null \
&& COMPREPLY+=( $(${{fnc}} $2) ) \
|| continue
[[ "$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, '\n'.join(a for a, _ in (aliases + handles)),
opts_common, opts_main, opts_nonmain, alias_case
).splitlines()
epilogue = "complete -o default -F {0} {1}".format(handle, self._prog)
return '\n\n'.join([self._format_function(handle, main), epilogue])