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
31
class BashCodeGen(object):
32
"""Generate a bash script for given completion data."""
34
def __init__(self, data, function_name='_bzr', debug=False):
36
self.function_name = function_name
41
# Programmable completion for the Bazaar-NG bzr command under bash.
42
# Known to work with bash 2.05a as well as bash 4.1.2, and probably
43
# all versions in between as well.
45
# Based originally on the svn bash completition script.
46
# Customized by Sven Wilhelm/Icecrash.com
47
# Adjusted for automatic generation by Martin von Gagern
49
# Generated using the bash_completion plugin.
50
# See https://launchpad.net/bzr-bash-completion for details.
52
# Commands and options of bzr %(bzr_version)s
56
complete -F %(function_name)s -o default bzr
58
"function_name": self.function_name,
59
"function": self.function(),
60
"bzr_version": self.bzr_version(),
67
local cur cmds cmdIdx cmd cmdOpts fixedWords i globalOpts
72
cur=${COMP_WORDS[COMP_CWORD]}
75
globalOpts=( %(global_options)s )
77
# do ordinary expansion if we are anywhere after a -- argument
78
for ((i = 1; i < COMP_CWORD; ++i)); do
79
[[ ${COMP_WORDS[i]} == "--" ]] && return 0
82
# find the command; it's the first word not starting in -
84
for ((cmdIdx = 1; cmdIdx < ${#COMP_WORDS[@]}; ++cmdIdx)); do
85
if [[ ${COMP_WORDS[cmdIdx]} != -* ]]; then
86
cmd=${COMP_WORDS[cmdIdx]}
91
# complete command name if we are not already past the command
92
if [[ $COMP_CWORD -le cmdIdx ]]; then
93
COMPREPLY=( $( compgen -W "$cmds ${globalOpts[*]}" -- $cur ) )
97
# find the option for which we want to complete a value
99
if [[ $cur != -* ]] && [[ $COMP_CWORD -gt 1 ]]; then
100
curOpt=${COMP_WORDS[COMP_CWORD - 1]}
101
if [[ $curOpt == = ]]; then
102
curOpt=${COMP_WORDS[COMP_CWORD - 2]}
103
elif [[ $cur == : ]]; then
106
elif [[ $curOpt == : ]]; then
107
curOpt=${COMP_WORDS[COMP_CWORD - 2]}:
122
if [[ ${#fixedWords[@]} -eq 0 ]] && [[ ${#optEnums[@]} -eq 0 ]] && [[ $cur != -* ]]; then
125
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//; s/ /\\\\\\\\ /g;') )
130
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//; s/^/tag:/') )
133
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//') )
134
fixedWords=( $(for i in "${fixedWords[@]}"; do echo "${cur%%..tag:*}..tag:${i}"; done) )
137
elif [[ $cur == = ]] && [[ ${#optEnums[@]} -gt 0 ]]; then
138
# complete directly after "--option=", list all enum values
139
COMPREPLY=( "${optEnums[@]}" )
142
fixedWords=( "${cmdOpts[@]}"
148
if [[ ${#fixedWords[@]} -gt 0 ]]; then
149
COMPREPLY=( $( compgen -W "${fixedWords[*]}" -- $cur ) )
155
"cmds": self.command_names(),
156
"function_name": self.function_name,
157
"cases": self.command_cases(),
158
"global_options": self.global_options(),
159
"debug": self.debug_output(),
161
# Help Emacs terminate strings: "
163
def command_names(self):
164
return " ".join(self.data.all_command_aliases())
166
def debug_output(self):
171
# Debugging code enabled using the --debug command line switch.
172
# Will dump some variables to the top portion of the terminal.
174
for (( i=0; i < ${#COMP_WORDS[@]}; ++i)); do
175
echo "\$COMP_WORDS[$i]='${COMP_WORDS[i]}'"$'\e[K'
177
for i in COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY cur curOpt; do
178
echo "\$${i}=\"${!i}\""$'\e[K'
180
echo -ne '---\e[K\e[u'
183
def bzr_version(self):
184
bzr_version = bzrlib.version_string
185
if not self.data.plugins:
188
bzr_version += " and the following plugins:"
189
for name, plugin in sorted(self.data.plugins.iteritems()):
190
bzr_version += "\n# %s" % plugin
193
def global_options(self):
194
return " ".join(sorted(self.data.global_options))
196
def command_cases(self):
198
for command in self.data.commands:
199
cases += self.command_case(command)
202
def command_case(self, command):
203
case = "\t%s)\n" % "|".join(command.aliases)
205
case += "\t\t# plugin \"%s\"\n" % command.plugin
208
for option in command.options:
209
for message in option.error_messages:
210
case += "\t\t# %s\n" % message
211
if option.registry_keys:
212
for key in option.registry_keys:
213
options.append("%s=%s" % (option, key))
214
enums.append("%s) optEnums=( %s ) ;;" %
215
(option, ' '.join(option.registry_keys)))
217
options.append(str(option))
218
case += "\t\tcmdOpts=( %s )\n" % " ".join(options)
219
if command.fixed_words:
220
fixed_words = command.fixed_words
221
if isinstance(fixed_words, list):
222
fixed_words = "( %s )" + ' '.join(fixed_words)
223
case += "\t\tfixedWords=%s\n" % fixed_words
225
case += "\t\tcase $curOpt in\n\t\t\t"
226
case += "\n\t\t\t".join(enums)
227
case += "\n\t\tesac\n"
232
class CompletionData(object):
236
self.global_options = set()
239
def all_command_aliases(self):
240
for c in self.commands:
245
class CommandData(object):
247
def __init__(self, name):
249
self.aliases = [name]
252
self.fixed_words = None
255
class PluginData(object):
257
def __init__(self, name, version=None):
260
version = bzrlib.plugin.plugins()[name].__version__
264
self.version = version
267
if self.version == 'unknown':
269
return '%s %s' % (self.name, self.version)
272
class OptionData(object):
274
def __init__(self, name):
276
self.registry_keys = None
277
self.error_messages = []
282
def __cmp__(self, other):
283
return cmp(self.name, other.name)
286
class DataCollector(object):
288
def __init__(self, no_plugins=False, selected_plugins=None):
289
self.data = CompletionData()
290
self.user_aliases = {}
292
self.selected_plugins = set()
293
elif selected_plugins is None:
294
self.selected_plugins = None
296
self.selected_plugins = set([x.replace('-', '_')
297
for x in selected_plugins])
300
self.global_options()
305
def global_options(self):
306
re_switch = re.compile(r'\n(--[A-Za-z0-9-_]+)(?:, (-\S))?\s')
307
help_text = help_topics.topic_registry.get_detail('global-options')
308
for long, short in re_switch.findall(help_text):
309
self.data.global_options.add(long)
311
self.data.global_options.add(short)
314
for alias, expansion in config.GlobalConfig().get_aliases().iteritems():
315
for token in cmdline.split(expansion):
316
if not token.startswith("-"):
317
self.user_aliases.setdefault(token, set()).add(alias)
321
for name in sorted(commands.all_command_names()):
324
def command(self, name):
325
cmd = commands.get_cmd_object(name)
326
cmd_data = CommandData(name)
328
plugin_name = cmd.plugin_name()
329
if plugin_name is not None:
330
if (self.selected_plugins is not None and
331
plugin not in self.selected_plugins):
333
plugin_data = self.data.plugins.get(plugin_name)
334
if plugin_data is None:
335
plugin_data = PluginData(plugin_name)
336
self.data.plugins[plugin_name] = plugin_data
337
cmd_data.plugin = plugin_data
338
self.data.commands.append(cmd_data)
340
# Find all aliases to the command; both cmd-defined and user-defined.
341
# We assume a user won't override one command with a different one,
342
# but will choose completely new names or add options to existing
343
# ones while maintaining the actual command name unchanged.
344
cmd_data.aliases.extend(cmd.aliases)
345
cmd_data.aliases.extend(sorted([useralias
346
for cmdalias in cmd_data.aliases
347
if cmdalias in self.user_aliases
348
for useralias in self.user_aliases[cmdalias]
349
if useralias not in cmd_data.aliases]))
352
for optname, opt in sorted(opts.iteritems()):
353
cmd_data.options.extend(self.option(opt))
355
if 'help' == name or 'help' in cmd.aliases:
356
cmd_data.fixed_words = ('($cmds %s)' %
357
" ".join(sorted(help_topics.topic_registry.keys())))
361
def option(self, opt):
363
parser = option.get_optparser({opt.name: opt})
364
parser = self.wrap_parser(optswitches, parser)
366
opt.add_option(parser, opt.short_name())
367
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
368
enum_switch = '--%s' % opt.name
369
enum_data = optswitches.get(enum_switch)
372
enum_data.registry_keys = opt.registry.keys()
373
except ImportError, e:
374
enum_data.error_messages.append(
375
"ERROR getting registry keys for '--%s': %s"
376
% (opt.name, str(e).split('\n')[0]))
377
return sorted(optswitches.values())
379
def wrap_container(self, optswitches, parser):
380
def tweaked_add_option(*opts, **attrs):
382
optswitches[name] = OptionData(name)
383
parser.add_option = tweaked_add_option
386
def wrap_parser(self, optswitches, parser):
387
orig_add_option_group = parser.add_option_group
388
def tweaked_add_option_group(*opts, **attrs):
389
return self.wrap_container(optswitches,
390
orig_add_option_group(*opts, **attrs))
391
parser.add_option_group = tweaked_add_option_group
392
return self.wrap_container(optswitches, parser)
395
def bash_completion_function(out, function_name="_bzr", function_only=False,
397
no_plugins=False, selected_plugins=None):
398
dc = DataCollector(no_plugins=no_plugins, selected_plugins=selected_plugins)
400
cg = BashCodeGen(data, function_name=function_name, debug=debug)
408
class cmd_bash_completion(commands.Command):
409
__doc__ = """Generate a shell function for bash command line completion.
411
This command generates a shell function which can be used by bash to
412
automatically complete the currently typed command when the user presses
413
the completion key (usually tab).
415
Commonly used like this:
416
eval "`bzr bash-completion`"
420
option.Option("function-name", short_name="f", type=str, argname="name",
421
help="Name of the generated function (default: _bzr)"),
422
option.Option("function-only", short_name="o", type=None,
423
help="Generate only the shell function, don't enable it"),
424
option.Option("debug", type=None, hidden=True,
425
help="Enable shell code useful for debugging"),
426
option.ListOption("plugin", type=str, argname="name",
427
# param_name="selected_plugins", # doesn't work, bug #387117
428
help="Enable completions for the selected plugin"
429
+ " (default: all plugins)"),
432
def run(self, **kwargs):
434
from bashcomp import bash_completion_function
435
if 'plugin' in kwargs:
436
# work around bug #387117 which prevents us from using param_name
437
if len(kwargs['plugin']) > 0:
438
kwargs['selected_plugins'] = kwargs['plugin']
440
bash_completion_function(sys.stdout, **kwargs)
443
if __name__ == '__main__':
449
def plugin_callback(option, opt, value, parser):
450
values = parser.values.selected_plugins
456
parser = optparse.OptionParser(usage="%prog [-f NAME] [-o]")
457
parser.add_option("--function-name", "-f", metavar="NAME",
458
help="Name of the generated function (default: _bzr)")
459
parser.add_option("--function-only", "-o", action="store_true",
460
help="Generate only the shell function, don't enable it")
461
parser.add_option("--debug", action="store_true",
462
help=optparse.SUPPRESS_HELP)
463
parser.add_option("--no-plugins", action="store_true",
464
help="Don't load any bzr plugins")
465
parser.add_option("--plugin", metavar="NAME", type="string",
466
dest="selected_plugins", default=[],
467
action="callback", callback=plugin_callback,
468
help="Enable completions for the selected plugin"
469
+ " (default: all plugins)")
470
(opts, args) = parser.parse_args()
472
parser.error("script does not take positional arguments")
474
for name, value in opts.__dict__.iteritems():
475
if value is not None:
478
locale.setlocale(locale.LC_ALL, '')
479
if not kwargs.get('no_plugins', False):
480
plugin.load_plugins()
481
commands.install_bzr_command_hooks()
482
bash_completion_function(sys.stdout, **kwargs)