3
# Copyright (C) 2009, 2010 Canonical Ltd
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32
class ZshCodeGen(object):
33
"""Generate a zsh script for given completion data."""
35
def __init__(self, data, function_name='_brz', debug=False):
37
self.function_name = function_name
52
_arguments $args[@] && ret=0
59
'global-options': self.global_options(),
60
'function_name': self.function_name})
62
def global_options(self):
64
for (long, short, help) in self.data.global_options:
66
' \'(%s%s)%s[%s]\'' % (
67
(short + ' ') if short else '',
72
return "\n".join(lines)
75
class CompletionData(object):
79
self.global_options = []
82
def all_command_aliases(self):
83
for c in self.commands:
88
class CommandData(object):
90
def __init__(self, name):
95
self.fixed_words = None
98
class PluginData(object):
100
def __init__(self, name, version=None):
103
version = breezy.plugin.plugins()[name].__version__
107
self.version = version
110
if self.version == 'unknown':
112
return '%s %s' % (self.name, self.version)
115
class OptionData(object):
117
def __init__(self, name):
119
self.registry_keys = None
120
self.error_messages = []
125
def __cmp__(self, other):
126
return cmp(self.name, other.name)
128
def __lt__(self, other):
129
return self.name < other.name
132
class DataCollector(object):
134
def __init__(self, no_plugins=False, selected_plugins=None):
135
self.data = CompletionData()
136
self.user_aliases = {}
138
self.selected_plugins = set()
139
elif selected_plugins is None:
140
self.selected_plugins = None
142
self.selected_plugins = {x.replace('-', '_')
143
for x in selected_plugins}
146
self.global_options()
151
def global_options(self):
152
for name, item in option.Option.OPTIONS.items():
153
self.data.global_options.append(
155
'-' + item.short_name() if item.short_name() else None,
159
for alias, expansion in config.GlobalConfig().get_aliases().items():
160
for token in cmdline.split(expansion):
161
if not token.startswith("-"):
162
self.user_aliases.setdefault(token, set()).add(alias)
166
for name in sorted(commands.all_command_names()):
169
def command(self, name):
170
cmd = commands.get_cmd_object(name)
171
cmd_data = CommandData(name)
173
plugin_name = cmd.plugin_name()
174
if plugin_name is not None:
175
if (self.selected_plugins is not None and
176
plugin not in self.selected_plugins):
178
plugin_data = self.data.plugins.get(plugin_name)
179
if plugin_data is None:
180
plugin_data = PluginData(plugin_name)
181
self.data.plugins[plugin_name] = plugin_data
182
cmd_data.plugin = plugin_data
183
self.data.commands.append(cmd_data)
185
# Find all aliases to the command; both cmd-defined and user-defined.
186
# We assume a user won't override one command with a different one,
187
# but will choose completely new names or add options to existing
188
# ones while maintaining the actual command name unchanged.
189
cmd_data.aliases.extend(cmd.aliases)
190
cmd_data.aliases.extend(sorted([useralias
191
for cmdalias in cmd_data.aliases
192
if cmdalias in self.user_aliases
193
for useralias in self.user_aliases[cmdalias]
194
if useralias not in cmd_data.aliases]))
197
for optname, opt in sorted(opts.items()):
198
cmd_data.options.extend(self.option(opt))
200
if 'help' == name or 'help' in cmd.aliases:
201
cmd_data.fixed_words = ('($cmds %s)' %
202
" ".join(sorted(help_topics.topic_registry.keys())))
206
def option(self, opt):
208
parser = option.get_optparser([opt])
209
parser = self.wrap_parser(optswitches, parser)
211
opt.add_option(parser, opt.short_name())
212
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
213
enum_switch = '--%s' % opt.name
214
enum_data = optswitches.get(enum_switch)
217
enum_data.registry_keys = opt.registry.keys()
218
except ImportError as e:
219
enum_data.error_messages.append(
220
"ERROR getting registry keys for '--%s': %s"
221
% (opt.name, str(e).split('\n')[0]))
222
return sorted(optswitches.values())
224
def wrap_container(self, optswitches, parser):
225
def tweaked_add_option(*opts, **attrs):
227
optswitches[name] = OptionData(name)
228
parser.add_option = tweaked_add_option
231
def wrap_parser(self, optswitches, parser):
232
orig_add_option_group = parser.add_option_group
234
def tweaked_add_option_group(*opts, **attrs):
235
return self.wrap_container(optswitches,
236
orig_add_option_group(*opts, **attrs))
237
parser.add_option_group = tweaked_add_option_group
238
return self.wrap_container(optswitches, parser)
241
def zsh_completion_function(out, function_name="_brz",
243
no_plugins=False, selected_plugins=None):
244
dc = DataCollector(no_plugins=no_plugins,
245
selected_plugins=selected_plugins)
247
cg = ZshCodeGen(data, function_name=function_name, debug=debug)
252
class cmd_zsh_completion(commands.Command):
253
__doc__ = """Generate a shell function for zsh command line completion.
255
This command generates a shell function which can be used by zsh to
256
automatically complete the currently typed command when the user presses
257
the completion key (usually tab).
259
Commonly used like this:
260
eval "`brz zsh -completion`"
264
option.Option("function-name", short_name="f", type=str, argname="name",
265
help="Name of the generated function (default: _brz)"),
266
option.Option("debug", type=None, hidden=True,
267
help="Enable shell code useful for debugging"),
268
option.ListOption("plugin", type=str, argname="name",
269
# param_name="selected_plugins", # doesn't work, bug #387117
270
help="Enable completions for the selected plugin"
271
+ " (default: all plugins)"),
274
def run(self, **kwargs):
275
if 'plugin' in kwargs:
276
# work around bug #387117 which prevents us from using param_name
277
if len(kwargs['plugin']) > 0:
278
kwargs['selected_plugins'] = kwargs['plugin']
280
zsh_completion_function(sys.stdout, **kwargs)