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
33
class BashCodeGen(object):
34
"""Generate a bash script for given completion data."""
36
def __init__(self, data, function_name='_bzr', debug=False):
38
self.function_name = function_name
43
# Programmable completion for the Bazaar-NG bzr command under bash.
44
# Known to work with bash 2.05a as well as bash 4.1.2, and probably
45
# all versions in between as well.
47
# Based originally on the svn bash completition script.
48
# Customized by Sven Wilhelm/Icecrash.com
49
# Adjusted for automatic generation by Martin von Gagern
51
# Generated using the bash_completion plugin.
52
# See https://launchpad.net/bzr-bash-completion for details.
54
# Commands and options of bzr %(bzr_version)s
58
complete -F %(function_name)s -o default bzr
60
"function_name": self.function_name,
61
"function": self.function(),
62
"bzr_version": self.bzr_version(),
69
local cur cmds cmdIdx cmd cmdOpts fixedWords i globalOpts
74
cur=${COMP_WORDS[COMP_CWORD]}
77
globalOpts=( %(global_options)s )
79
# do ordinary expansion if we are anywhere after a -- argument
80
for ((i = 1; i < COMP_CWORD; ++i)); do
81
[[ ${COMP_WORDS[i]} == "--" ]] && return 0
84
# find the command; it's the first word not starting in -
86
for ((cmdIdx = 1; cmdIdx < ${#COMP_WORDS[@]}; ++cmdIdx)); do
87
if [[ ${COMP_WORDS[cmdIdx]} != -* ]]; then
88
cmd=${COMP_WORDS[cmdIdx]}
93
# complete command name if we are not already past the command
94
if [[ $COMP_CWORD -le cmdIdx ]]; then
95
COMPREPLY=( $( compgen -W "$cmds ${globalOpts[*]}" -- $cur ) )
99
# find the option for which we want to complete a value
101
if [[ $cur != -* ]] && [[ $COMP_CWORD -gt 1 ]]; then
102
curOpt=${COMP_WORDS[COMP_CWORD - 1]}
103
if [[ $curOpt == = ]]; then
104
curOpt=${COMP_WORDS[COMP_CWORD - 2]}
105
elif [[ $cur == : ]]; then
108
elif [[ $curOpt == : ]]; then
109
curOpt=${COMP_WORDS[COMP_CWORD - 2]}:
124
if [[ ${#fixedWords[@]} -eq 0 ]] && [[ ${#optEnums[@]} -eq 0 ]] && [[ $cur != -* ]]; then
127
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//; s/ /\\\\\\\\ /g;') )
132
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//; s/^/tag:/') )
135
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//') )
136
fixedWords=( $(for i in "${fixedWords[@]}"; do echo "${cur%%..tag:*}..tag:${i}"; done) )
139
elif [[ $cur == = ]] && [[ ${#optEnums[@]} -gt 0 ]]; then
140
# complete directly after "--option=", list all enum values
141
COMPREPLY=( "${optEnums[@]}" )
144
fixedWords=( "${cmdOpts[@]}"
150
if [[ ${#fixedWords[@]} -gt 0 ]]; then
151
COMPREPLY=( $( compgen -W "${fixedWords[*]}" -- $cur ) )
157
"cmds": self.command_names(),
158
"function_name": self.function_name,
159
"cases": self.command_cases(),
160
"global_options": self.global_options(),
161
"debug": self.debug_output(),
163
# Help Emacs terminate strings: "
165
def command_names(self):
166
return " ".join(self.data.all_command_aliases())
168
def debug_output(self):
173
# Debugging code enabled using the --debug command line switch.
174
# Will dump some variables to the top portion of the terminal.
176
for (( i=0; i < ${#COMP_WORDS[@]}; ++i)); do
177
echo "\$COMP_WORDS[$i]='${COMP_WORDS[i]}'"$'\e[K'
179
for i in COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY cur curOpt; do
180
echo "\$${i}=\"${!i}\""$'\e[K'
182
echo -ne '---\e[K\e[u'
185
def bzr_version(self):
186
bzr_version = bzrlib.version_string
187
if not self.data.plugins:
190
bzr_version += " and the following plugins:"
191
for name, plugin in sorted(self.data.plugins.iteritems()):
192
bzr_version += "\n# %s" % plugin
195
def global_options(self):
196
return " ".join(sorted(self.data.global_options))
198
def command_cases(self):
200
for command in self.data.commands:
201
cases += self.command_case(command)
204
def command_case(self, command):
205
case = "\t%s)\n" % "|".join(command.aliases)
207
case += "\t\t# plugin \"%s\"\n" % command.plugin
210
for option in command.options:
211
for message in option.error_messages:
212
case += "\t\t# %s\n" % message
213
if option.registry_keys:
214
for key in option.registry_keys:
215
options.append("%s=%s" % (option, key))
216
enums.append("%s) optEnums=( %s ) ;;" %
217
(option, ' '.join(option.registry_keys)))
219
options.append(str(option))
220
case += "\t\tcmdOpts=( %s )\n" % " ".join(options)
221
if command.fixed_words:
222
fixed_words = command.fixed_words
223
if isinstance(fixed_words, list):
224
fixed_words = "( %s )" + ' '.join(fixed_words)
225
case += "\t\tfixedWords=%s\n" % fixed_words
227
case += "\t\tcase $curOpt in\n\t\t\t"
228
case += "\n\t\t\t".join(enums)
229
case += "\n\t\tesac\n"
234
class CompletionData(object):
238
self.global_options = set()
241
def all_command_aliases(self):
242
for c in self.commands:
247
class CommandData(object):
249
def __init__(self, name):
251
self.aliases = [name]
254
self.fixed_words = None
257
class PluginData(object):
259
def __init__(self, name, version=None):
262
version = bzrlib.plugin.plugins()[name].__version__
266
self.version = version
269
if self.version == 'unknown':
271
return '%s %s' % (self.name, self.version)
274
class OptionData(object):
276
def __init__(self, name):
278
self.registry_keys = None
279
self.error_messages = []
284
def __cmp__(self, other):
285
return cmp(self.name, other.name)
288
class DataCollector(object):
290
def __init__(self, no_plugins=False, selected_plugins=None):
291
self.data = CompletionData()
292
self.user_aliases = {}
294
self.selected_plugins = set()
295
elif selected_plugins is None:
296
self.selected_plugins = None
298
self.selected_plugins = set([x.replace('-', '_')
299
for x in selected_plugins])
302
self.global_options()
307
def global_options(self):
308
re_switch = re.compile(r'\n(--[A-Za-z0-9-_]+)(?:, (-\S))?\s')
309
help_text = help_topics.topic_registry.get_detail('global-options')
310
for long, short in re_switch.findall(help_text):
311
self.data.global_options.add(long)
313
self.data.global_options.add(short)
316
for alias, expansion in config.GlobalConfig().get_aliases().iteritems():
317
for token in cmdline.split(expansion):
318
if not token.startswith("-"):
319
self.user_aliases.setdefault(token, set()).add(alias)
323
for name in sorted(commands.all_command_names()):
326
def command(self, name):
327
cmd = commands.get_cmd_object(name)
328
cmd_data = CommandData(name)
330
plugin_name = cmd.plugin_name()
331
if plugin_name is not None:
332
if (self.selected_plugins is not None and
333
plugin not in self.selected_plugins):
335
plugin_data = self.data.plugins.get(plugin_name)
336
if plugin_data is None:
337
plugin_data = PluginData(plugin_name)
338
self.data.plugins[plugin_name] = plugin_data
339
cmd_data.plugin = plugin_data
340
self.data.commands.append(cmd_data)
342
# Find all aliases to the command; both cmd-defined and user-defined.
343
# We assume a user won't override one command with a different one,
344
# but will choose completely new names or add options to existing
345
# ones while maintaining the actual command name unchanged.
346
cmd_data.aliases.extend(cmd.aliases)
347
cmd_data.aliases.extend(sorted([useralias
348
for cmdalias in cmd_data.aliases
349
if cmdalias in self.user_aliases
350
for useralias in self.user_aliases[cmdalias]
351
if useralias not in cmd_data.aliases]))
354
for optname, opt in sorted(opts.iteritems()):
355
cmd_data.options.extend(self.option(opt))
357
if 'help' == name or 'help' in cmd.aliases:
358
cmd_data.fixed_words = ('($cmds %s)' %
359
" ".join(sorted(help_topics.topic_registry.keys())))
363
def option(self, opt):
365
parser = option.get_optparser({opt.name: opt})
366
parser = self.wrap_parser(optswitches, parser)
368
opt.add_option(parser, opt.short_name())
369
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
370
enum_switch = '--%s' % opt.name
371
enum_data = optswitches.get(enum_switch)
374
enum_data.registry_keys = opt.registry.keys()
375
except ImportError, e:
376
enum_data.error_messages.append(
377
"ERROR getting registry keys for '--%s': %s"
378
% (opt.name, str(e).split('\n')[0]))
379
return sorted(optswitches.values())
381
def wrap_container(self, optswitches, parser):
382
def tweaked_add_option(*opts, **attrs):
384
optswitches[name] = OptionData(name)
385
parser.add_option = tweaked_add_option
388
def wrap_parser(self, optswitches, parser):
389
orig_add_option_group = parser.add_option_group
390
def tweaked_add_option_group(*opts, **attrs):
391
return self.wrap_container(optswitches,
392
orig_add_option_group(*opts, **attrs))
393
parser.add_option_group = tweaked_add_option_group
394
return self.wrap_container(optswitches, parser)
397
def bash_completion_function(out, function_name="_bzr", function_only=False,
399
no_plugins=False, selected_plugins=None):
400
dc = DataCollector(no_plugins=no_plugins, selected_plugins=selected_plugins)
402
cg = BashCodeGen(data, function_name=function_name, debug=debug)
410
class cmd_bash_completion(commands.Command):
411
__doc__ = """Generate a shell function for bash command line completion.
413
This command generates a shell function which can be used by bash to
414
automatically complete the currently typed command when the user presses
415
the completion key (usually tab).
417
Commonly used like this:
418
eval "`bzr bash-completion`"
422
option.Option("function-name", short_name="f", type=str, argname="name",
423
help="Name of the generated function (default: _bzr)"),
424
option.Option("function-only", short_name="o", type=None,
425
help="Generate only the shell function, don't enable it"),
426
option.Option("debug", type=None, hidden=True,
427
help="Enable shell code useful for debugging"),
428
option.ListOption("plugin", type=str, argname="name",
429
# param_name="selected_plugins", # doesn't work, bug #387117
430
help="Enable completions for the selected plugin"
431
+ " (default: all plugins)"),
434
def run(self, **kwargs):
436
from bashcomp import bash_completion_function
437
if 'plugin' in kwargs:
438
# work around bug #387117 which prevents us from using param_name
439
if len(kwargs['plugin']) > 0:
440
kwargs['selected_plugins'] = kwargs['plugin']
442
bash_completion_function(sys.stdout, **kwargs)
445
if __name__ == '__main__':
451
def plugin_callback(option, opt, value, parser):
452
values = parser.values.selected_plugins
458
parser = optparse.OptionParser(usage="%prog [-f NAME] [-o]")
459
parser.add_option("--function-name", "-f", metavar="NAME",
460
help="Name of the generated function (default: _bzr)")
461
parser.add_option("--function-only", "-o", action="store_true",
462
help="Generate only the shell function, don't enable it")
463
parser.add_option("--debug", action="store_true",
464
help=optparse.SUPPRESS_HELP)
465
parser.add_option("--no-plugins", action="store_true",
466
help="Don't load any bzr plugins")
467
parser.add_option("--plugin", metavar="NAME", type="string",
468
dest="selected_plugins", default=[],
469
action="callback", callback=plugin_callback,
470
help="Enable completions for the selected plugin"
471
+ " (default: all plugins)")
472
(opts, args) = parser.parse_args()
474
parser.error("script does not take positional arguments")
476
for name, value in opts.__dict__.iteritems():
477
if value is not None:
480
locale.setlocale(locale.LC_ALL, '')
481
if not kwargs.get('no_plugins', False):
482
plugin.load_plugins()
483
commands.install_bzr_command_hooks()
484
bash_completion_function(sys.stdout, **kwargs)