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
19
from __future__ import absolute_import
29
from ...sixish import (
37
class ZshCodeGen(object):
38
"""Generate a zsh script for given completion data."""
40
def __init__(self, data, function_name='_brz', debug=False):
42
self.function_name = function_name
57
_arguments $args[@] && ret=0
64
'global-options': self.global_options(),
65
'function_name': self.function_name})
67
def global_options(self):
69
for (long, short, help) in self.data.global_options:
71
' \'(%s%s)%s[%s]\'' % (
72
(short + ' ') if short else '',
77
return "\n".join(lines)
80
class CompletionData(object):
84
self.global_options = []
87
def all_command_aliases(self):
88
for c in self.commands:
93
class CommandData(object):
95
def __init__(self, name):
100
self.fixed_words = None
103
class PluginData(object):
105
def __init__(self, name, version=None):
108
version = breezy.plugin.plugins()[name].__version__
112
self.version = version
115
if self.version == 'unknown':
117
return '%s %s' % (self.name, self.version)
120
class OptionData(object):
122
def __init__(self, name):
124
self.registry_keys = None
125
self.error_messages = []
130
def __cmp__(self, other):
131
return cmp(self.name, other.name)
133
def __lt__(self, other):
134
return self.name < other.name
137
class DataCollector(object):
139
def __init__(self, no_plugins=False, selected_plugins=None):
140
self.data = CompletionData()
141
self.user_aliases = {}
143
self.selected_plugins = set()
144
elif selected_plugins is None:
145
self.selected_plugins = None
147
self.selected_plugins = {x.replace('-', '_')
148
for x in selected_plugins}
151
self.global_options()
156
def global_options(self):
157
for name, item in option.Option.OPTIONS.items():
158
self.data.global_options.append(
160
'-' + item.short_name() if item.short_name() else None,
164
for alias, expansion in config.GlobalConfig().get_aliases().items():
165
for token in cmdline.split(expansion):
166
if not token.startswith("-"):
167
self.user_aliases.setdefault(token, set()).add(alias)
171
for name in sorted(commands.all_command_names()):
174
def command(self, name):
175
cmd = commands.get_cmd_object(name)
176
cmd_data = CommandData(name)
178
plugin_name = cmd.plugin_name()
179
if plugin_name is not None:
180
if (self.selected_plugins is not None and
181
plugin not in self.selected_plugins):
183
plugin_data = self.data.plugins.get(plugin_name)
184
if plugin_data is None:
185
plugin_data = PluginData(plugin_name)
186
self.data.plugins[plugin_name] = plugin_data
187
cmd_data.plugin = plugin_data
188
self.data.commands.append(cmd_data)
190
# Find all aliases to the command; both cmd-defined and user-defined.
191
# We assume a user won't override one command with a different one,
192
# but will choose completely new names or add options to existing
193
# ones while maintaining the actual command name unchanged.
194
cmd_data.aliases.extend(cmd.aliases)
195
cmd_data.aliases.extend(sorted([useralias
196
for cmdalias in cmd_data.aliases
197
if cmdalias in self.user_aliases
198
for useralias in self.user_aliases[cmdalias]
199
if useralias not in cmd_data.aliases]))
202
for optname, opt in sorted(opts.items()):
203
cmd_data.options.extend(self.option(opt))
205
if 'help' == name or 'help' in cmd.aliases:
206
cmd_data.fixed_words = ('($cmds %s)' %
207
" ".join(sorted(help_topics.topic_registry.keys())))
211
def option(self, opt):
213
parser = option.get_optparser([opt])
214
parser = self.wrap_parser(optswitches, parser)
216
opt.add_option(parser, opt.short_name())
217
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
218
enum_switch = '--%s' % opt.name
219
enum_data = optswitches.get(enum_switch)
222
enum_data.registry_keys = opt.registry.keys()
223
except ImportError as e:
224
enum_data.error_messages.append(
225
"ERROR getting registry keys for '--%s': %s"
226
% (opt.name, str(e).split('\n')[0]))
227
return sorted(optswitches.values())
229
def wrap_container(self, optswitches, parser):
230
def tweaked_add_option(*opts, **attrs):
232
optswitches[name] = OptionData(name)
233
parser.add_option = tweaked_add_option
236
def wrap_parser(self, optswitches, parser):
237
orig_add_option_group = parser.add_option_group
239
def tweaked_add_option_group(*opts, **attrs):
240
return self.wrap_container(optswitches,
241
orig_add_option_group(*opts, **attrs))
242
parser.add_option_group = tweaked_add_option_group
243
return self.wrap_container(optswitches, parser)
246
def zsh_completion_function(out, function_name="_brz",
248
no_plugins=False, selected_plugins=None):
249
dc = DataCollector(no_plugins=no_plugins,
250
selected_plugins=selected_plugins)
252
cg = ZshCodeGen(data, function_name=function_name, debug=debug)
257
class cmd_zsh_completion(commands.Command):
258
__doc__ = """Generate a shell function for zsh command line completion.
260
This command generates a shell function which can be used by zsh to
261
automatically complete the currently typed command when the user presses
262
the completion key (usually tab).
264
Commonly used like this:
265
eval "`brz zsh -completion`"
269
option.Option("function-name", short_name="f", type=text_type, argname="name",
270
help="Name of the generated function (default: _brz)"),
271
option.Option("debug", type=None, hidden=True,
272
help="Enable shell code useful for debugging"),
273
option.ListOption("plugin", type=text_type, argname="name",
274
# param_name="selected_plugins", # doesn't work, bug #387117
275
help="Enable completions for the selected plugin"
276
+ " (default: all plugins)"),
279
def run(self, **kwargs):
280
if 'plugin' in kwargs:
281
# work around bug #387117 which prevents us from using param_name
282
if len(kwargs['plugin']) > 0:
283
kwargs['selected_plugins'] = kwargs['plugin']
285
zsh_completion_function(sys.stdout, **kwargs)