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
71
cur=${COMP_WORDS[COMP_CWORD]}
74
globalOpts='%(global_options)s'
76
# do ordinary expansion if we are anywhere after a -- argument
77
for ((i = 1; i < COMP_CWORD; ++i)); do
78
[[ ${COMP_WORDS[i]} == "--" ]] && return 0
81
# find the command; it's the first word not starting in -
83
for ((cmdIdx = 1; cmdIdx < ${#COMP_WORDS[@]}; ++cmdIdx)); do
84
if [[ ${COMP_WORDS[cmdIdx]} != -* ]]; then
85
cmd=${COMP_WORDS[cmdIdx]}
90
# complete command name if we are not already past the command
91
if [[ $COMP_CWORD -le cmdIdx ]]; then
92
COMPREPLY=( $( compgen -W "$cmds $globalOpts" -- $cur ) )
96
# find the option for which we want to complete a value
98
if [[ $cur != -* ]] && [[ $COMP_CWORD -gt 1 ]]; then
99
curOpt=${COMP_WORDS[COMP_CWORD - 1]}
100
if [[ $curOpt == = ]]; then
101
curOpt=${COMP_WORDS[COMP_CWORD - 2]}
102
elif [[ $cur == : ]]; then
105
elif [[ $curOpt == : ]]; then
106
curOpt=${COMP_WORDS[COMP_CWORD - 2]}:
120
if [[ -z $fixedWords ]] && [[ -z $optEnums ]] && [[ $cur != -* ]]; then
123
fixedWords="$(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//')"
126
elif [[ $cur == = ]] && [[ -n $optEnums ]]; then
127
# complete directly after "--option=", list all enum values
128
COMPREPLY=( $optEnums )
131
fixedWords="$cmdOpts $globalOpts $optEnums $fixedWords"
134
if [[ -n $fixedWords ]]; then
135
COMPREPLY=( $( compgen -W "$fixedWords" -- $cur ) )
141
"cmds": self.command_names(),
142
"function_name": self.function_name,
143
"cases": self.command_cases(),
144
"global_options": self.global_options(),
145
"debug": self.debug_output(),
148
def command_names(self):
149
return " ".join(self.data.all_command_aliases())
151
def debug_output(self):
156
# Debugging code enabled using the --debug command line switch.
157
# Will dump some variables to the top portion of the terminal.
159
for (( i=0; i < ${#COMP_WORDS[@]}; ++i)); do
160
echo "\$COMP_WORDS[$i]='${COMP_WORDS[i]}'"$'\e[K'
162
for i in COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY cur curOpt; do
163
echo "\$${i}=\"${!i}\""$'\e[K'
165
echo -ne '---\e[K\e[u'
168
def bzr_version(self):
169
bzr_version = bzrlib.version_string
170
if not self.data.plugins:
173
bzr_version += " and the following plugins:"
174
for name, plugin in sorted(self.data.plugins.iteritems()):
175
bzr_version += "\n# %s" % plugin
178
def global_options(self):
179
return " ".join(sorted(self.data.global_options))
181
def command_cases(self):
183
for command in self.data.commands:
184
cases += self.command_case(command)
187
def command_case(self, command):
188
case = "\t%s)\n" % "|".join(command.aliases)
190
case += "\t\t# plugin \"%s\"\n" % command.plugin
193
for option in command.options:
194
for message in option.error_messages:
195
case += "\t\t# %s\n" % message
196
if option.registry_keys:
197
for key in option.registry_keys:
198
options.append("%s=%s" % (option, key))
199
enums.append("%s) optEnums='%s' ;;" %
200
(option, ' '.join(option.registry_keys)))
202
options.append(str(option))
203
case += "\t\tcmdOpts='%s'\n" % " ".join(options)
204
if command.fixed_words:
205
fixed_words = command.fixed_words
206
if isinstance(fixed_words, list):
207
fixed_words = "'%s'" + ' '.join(fixed_words)
208
case += "\t\tfixedWords=%s\n" % fixed_words
210
case += "\t\tcase $curOpt in\n\t\t\t"
211
case += "\n\t\t\t".join(enums)
212
case += "\n\t\tesac\n"
217
class CompletionData(object):
221
self.global_options = set()
224
def all_command_aliases(self):
225
for c in self.commands:
230
class CommandData(object):
232
def __init__(self, name):
234
self.aliases = [name]
237
self.fixed_words = None
240
class PluginData(object):
242
def __init__(self, name, version=None):
244
version = bzrlib.plugin.plugins()[name].__version__
246
self.version = version
249
if self.version == 'unknown':
251
return '%s %s' % (self.name, self.version)
254
class OptionData(object):
256
def __init__(self, name):
258
self.registry_keys = None
259
self.error_messages = []
264
def __cmp__(self, other):
265
return cmp(self.name, other.name)
268
class DataCollector(object):
270
def __init__(self, no_plugins=False, selected_plugins=None):
271
self.data = CompletionData()
272
self.user_aliases = {}
274
self.selected_plugins = set()
275
elif selected_plugins is None:
276
self.selected_plugins = None
278
self.selected_plugins = set([x.replace('-', '_')
279
for x in selected_plugins])
282
self.global_options()
287
def global_options(self):
288
re_switch = re.compile(r'\n(--[A-Za-z0-9-_]+)(?:, (-\S))?\s')
289
help_text = help_topics.topic_registry.get_detail('global-options')
290
for long, short in re_switch.findall(help_text):
291
self.data.global_options.add(long)
293
self.data.global_options.add(short)
296
for alias, expansion in config.GlobalConfig().get_aliases().iteritems():
297
for token in cmdline.split(expansion):
298
if not token.startswith("-"):
299
self.user_aliases.setdefault(token, set()).add(alias)
303
for name in sorted(commands.all_command_names()):
306
def command(self, name):
307
cmd = commands.get_cmd_object(name)
308
cmd_data = CommandData(name)
310
plugin_name = cmd.plugin_name()
311
if plugin_name is not None:
312
if (self.selected_plugins is not None and
313
plugin not in self.selected_plugins):
315
plugin_data = self.data.plugins.get(plugin_name)
316
if plugin_data is None:
317
plugin_data = PluginData(plugin_name)
318
self.data.plugins[plugin_name] = plugin_data
319
cmd_data.plugin = plugin_data
320
self.data.commands.append(cmd_data)
322
# Find all aliases to the command; both cmd-defined and user-defined.
323
# We assume a user won't override one command with a different one,
324
# but will choose completely new names or add options to existing
325
# ones while maintaining the actual command name unchanged.
326
cmd_data.aliases.extend(cmd.aliases)
327
cmd_data.aliases.extend(sorted([useralias
328
for cmdalias in cmd_data.aliases
329
if cmdalias in self.user_aliases
330
for useralias in self.user_aliases[cmdalias]
331
if useralias not in cmd_data.aliases]))
334
for optname, opt in sorted(opts.iteritems()):
335
cmd_data.options.extend(self.option(opt))
337
if 'help' == name or 'help' in cmd.aliases:
338
cmd_data.fixed_words = ('"$cmds %s"' %
339
" ".join(sorted(help_topics.topic_registry.keys())))
343
def option(self, opt):
345
parser = option.get_optparser({opt.name: opt})
346
parser = self.wrap_parser(optswitches, parser)
348
opt.add_option(parser, opt.short_name())
349
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
350
enum_switch = '--%s' % opt.name
351
enum_data = optswitches.get(enum_switch)
354
enum_data.registry_keys = opt.registry.keys()
355
except ImportError, e:
356
enum_data.error_messages.append(
357
"ERROR getting registry keys for '--%s': %s"
358
% (opt.name, str(e).split('\n')[0]))
359
return sorted(optswitches.values())
361
def wrap_container(self, optswitches, parser):
362
def tweaked_add_option(*opts, **attrs):
364
optswitches[name] = OptionData(name)
365
parser.add_option = tweaked_add_option
368
def wrap_parser(self, optswitches, parser):
369
orig_add_option_group = parser.add_option_group
370
def tweaked_add_option_group(*opts, **attrs):
371
return self.wrap_container(optswitches,
372
orig_add_option_group(*opts, **attrs))
373
parser.add_option_group = tweaked_add_option_group
374
return self.wrap_container(optswitches, parser)
377
def bash_completion_function(out, function_name="_bzr", function_only=False,
379
no_plugins=False, selected_plugins=None):
380
dc = DataCollector(no_plugins=no_plugins, selected_plugins=selected_plugins)
382
cg = BashCodeGen(data, function_name=function_name, debug=debug)
390
class cmd_bash_completion(commands.Command):
391
__doc__ = """Generate a shell function for bash command line completion.
393
This command generates a shell function which can be used by bash to
394
automatically complete the currently typed command when the user presses
395
the completion key (usually tab).
397
Commonly used like this:
398
eval "`bzr bash-completion`"
402
option.Option("function-name", short_name="f", type=str, argname="name",
403
help="Name of the generated function (default: _bzr)"),
404
option.Option("function-only", short_name="o", type=None,
405
help="Generate only the shell function, don't enable it"),
406
option.Option("debug", type=None, hidden=True,
407
help="Enable shell code useful for debugging"),
408
option.ListOption("plugin", type=str, argname="name",
409
# param_name="selected_plugins", # doesn't work, bug #387117
410
help="Enable completions for the selected plugin"
411
+ " (default: all plugins)"),
414
def run(self, **kwargs):
416
from bashcomp import bash_completion_function
417
if 'plugin' in kwargs:
418
# work around bug #387117 which prevents us from using param_name
419
if len(kwargs['plugin']) > 0:
420
kwargs['selected_plugins'] = kwargs['plugin']
422
bash_completion_function(sys.stdout, **kwargs)
425
if __name__ == '__main__':
431
def plugin_callback(option, opt, value, parser):
432
values = parser.values.selected_plugins
438
parser = optparse.OptionParser(usage="%prog [-f NAME] [-o]")
439
parser.add_option("--function-name", "-f", metavar="NAME",
440
help="Name of the generated function (default: _bzr)")
441
parser.add_option("--function-only", "-o", action="store_true",
442
help="Generate only the shell function, don't enable it")
443
parser.add_option("--debug", action="store_true",
444
help=optparse.SUPPRESS_HELP)
445
parser.add_option("--no-plugins", action="store_true",
446
help="Don't load any bzr plugins")
447
parser.add_option("--plugin", metavar="NAME", type="string",
448
dest="selected_plugins", default=[],
449
action="callback", callback=plugin_callback,
450
help="Enable completions for the selected plugin"
451
+ " (default: all plugins)")
452
(opts, args) = parser.parse_args()
454
parser.error("script does not take positional arguments")
456
for name, value in opts.__dict__.iteritems():
457
if value is not None:
460
locale.setlocale(locale.LC_ALL, '')
461
if not kwargs.get('no_plugins', False):
462
plugin.load_plugins()
463
commands.install_bzr_command_hooks()
464
bash_completion_function(sys.stdout, **kwargs)