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
30
class BashCodeGen(object):
31
"""Generate a bash script for given completion data."""
33
def __init__(self, data, function_name='_bzr', debug=False):
35
self.function_name = function_name
40
# Programmable completion for the Bazaar-NG bzr command under bash.
41
# Known to work with bash 2.05a as well as bash 4.1.2, and probably
42
# all versions in between as well.
44
# Based originally on the svn bash completition script.
45
# Customized by Sven Wilhelm/Icecrash.com
46
# Adjusted for automatic generation by Martin von Gagern
48
# Generated using the bash_completion plugin.
49
# See https://launchpad.net/bzr-bash-completion for details.
51
# Commands and options of bzr %(bzr_version)s
55
complete -F %(function_name)s -o default bzr
57
"function_name": self.function_name,
58
"function": self.function(),
59
"bzr_version": self.bzr_version(),
66
local cur cmds cmdIdx cmd cmdOpts fixedWords i globalOpts
70
cur=${COMP_WORDS[COMP_CWORD]}
73
globalOpts='%(global_options)s'
75
# do ordinary expansion if we are anywhere after a -- argument
76
for ((i = 1; i < COMP_CWORD; ++i)); do
77
[[ ${COMP_WORDS[i]} == "--" ]] && return 0
80
# find the command; it's the first word not starting in -
82
for ((cmdIdx = 1; cmdIdx < ${#COMP_WORDS[@]}; ++cmdIdx)); do
83
if [[ ${COMP_WORDS[cmdIdx]} != -* ]]; then
84
cmd=${COMP_WORDS[cmdIdx]}
89
# complete command name if we are not already past the command
90
if [[ $COMP_CWORD -le cmdIdx ]]; then
91
COMPREPLY=( $( compgen -W "$cmds $globalOpts" -- $cur ) )
95
# find the option for which we want to complete a value
97
if [[ $cur != -* ]] && [[ $COMP_CWORD -gt 1 ]]; then
98
curOpt=${COMP_WORDS[COMP_CWORD - 1]}
99
if [[ $curOpt == = ]]; then
100
curOpt=${COMP_WORDS[COMP_CWORD - 2]}
101
elif [[ $cur == : ]]; then
104
elif [[ $curOpt == : ]]; then
105
curOpt=${COMP_WORDS[COMP_CWORD - 2]}:
119
if [[ -z $fixedWords ]] && [[ -z $optEnums ]] && [[ $cur != -* ]]; then
122
fixedWords="$(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//')"
125
elif [[ $cur == = ]] && [[ -n $optEnums ]]; then
126
# complete directly after "--option=", list all enum values
127
COMPREPLY=( $optEnums )
130
fixedWords="$cmdOpts $globalOpts $optEnums $fixedWords"
133
if [[ -n $fixedWords ]]; then
134
COMPREPLY=( $( compgen -W "$fixedWords" -- $cur ) )
140
"cmds": self.command_names(),
141
"function_name": self.function_name,
142
"cases": self.command_cases(),
143
"global_options": self.global_options(),
144
"debug": self.debug_output(),
147
def command_names(self):
148
return " ".join(self.data.all_command_aliases())
150
def debug_output(self):
155
# Debugging code enabled using the --debug command line switch.
156
# Will dump some variables to the top portion of the terminal.
158
for (( i=0; i < ${#COMP_WORDS[@]}; ++i)); do
159
echo "\$COMP_WORDS[$i]='${COMP_WORDS[i]}'"$'\e[K'
161
for i in COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY cur curOpt; do
162
echo "\$${i}=\"${!i}\""$'\e[K'
164
echo -ne '---\e[K\e[u'
167
def bzr_version(self):
168
bzr_version = bzrlib.version_string
169
if not self.data.plugins:
172
bzr_version += " and the following plugins:"
173
for name, plugin in sorted(self.data.plugins.iteritems()):
174
bzr_version += "\n# %s" % plugin
177
def global_options(self):
178
return " ".join(sorted(self.data.global_options))
180
def command_cases(self):
182
for command in self.data.commands:
183
cases += self.command_case(command)
186
def command_case(self, command):
187
case = "\t%s)\n" % "|".join(command.aliases)
189
case += "\t\t# plugin \"%s\"\n" % command.plugin
192
for option in command.options:
193
for message in option.error_messages:
194
case += "\t\t# %s\n" % message
195
if option.registry_keys:
196
for key in option.registry_keys:
197
options.append("%s=%s" % (option, key))
198
enums.append("%s) optEnums='%s' ;;" %
199
(option, ' '.join(option.registry_keys)))
201
options.append(str(option))
202
case += "\t\tcmdOpts='%s'\n" % " ".join(options)
203
if command.fixed_words:
204
fixed_words = command.fixed_words
205
if isinstance(fixed_words, list):
206
fixed_words = "'%s'" + ' '.join(fixed_words)
207
case += "\t\tfixedWords=%s\n" % fixed_words
209
case += "\t\tcase $curOpt in\n\t\t\t"
210
case += "\n\t\t\t".join(enums)
211
case += "\n\t\tesac\n"
216
class CompletionData(object):
220
self.global_options = set()
223
def all_command_aliases(self):
224
for c in self.commands:
229
class CommandData(object):
231
def __init__(self, name):
233
self.aliases = [name]
236
self.fixed_words = None
239
class PluginData(object):
241
def __init__(self, name, version=None):
243
version = bzrlib.plugin.plugins()[name].__version__
245
self.version = version
248
if self.version == 'unknown':
250
return '%s %s' % (self.name, self.version)
253
class OptionData(object):
255
def __init__(self, name):
257
self.registry_keys = None
258
self.error_messages = []
263
def __cmp__(self, other):
264
return cmp(self.name, other.name)
267
class DataCollector(object):
269
def __init__(self, no_plugins=False, selected_plugins=None):
270
self.data = CompletionData()
271
self.user_aliases = {}
273
self.selected_plugins = set()
274
elif selected_plugins is None:
275
self.selected_plugins = None
277
self.selected_plugins = set([x.replace('-', '_')
278
for x in selected_plugins])
281
self.global_options()
286
def global_options(self):
287
re_switch = re.compile(r'\n(--[A-Za-z0-9-_]+)(?:, (-\S))?\s')
288
help_text = help_topics.topic_registry.get_detail('global-options')
289
for long, short in re_switch.findall(help_text):
290
self.data.global_options.add(long)
292
self.data.global_options.add(short)
295
for alias, expansion in config.GlobalConfig().get_aliases().iteritems():
296
for token in commands.shlex_split_unicode(expansion):
297
if not token.startswith("-"):
298
self.user_aliases.setdefault(token, set()).add(alias)
302
for name in sorted(commands.all_command_names()):
305
def command(self, name):
306
cmd = commands.get_cmd_object(name)
307
cmd_data = CommandData(name)
309
plugin_name = cmd.plugin_name()
310
if plugin_name is not None:
311
if (self.selected_plugins is not None and
312
plugin not in self.selected_plugins):
314
plugin_data = self.data.plugins.get(plugin_name)
315
if plugin_data is None:
316
plugin_data = PluginData(plugin_name)
317
self.data.plugins[plugin_name] = plugin_data
318
cmd_data.plugin = plugin_data
319
self.data.commands.append(cmd_data)
321
# Find all aliases to the command; both cmd-defined and user-defined.
322
# We assume a user won't override one command with a different one,
323
# but will choose completely new names or add options to existing
324
# ones while maintaining the actual command name unchanged.
325
cmd_data.aliases.extend(cmd.aliases)
326
cmd_data.aliases.extend(sorted([useralias
327
for cmdalias in cmd_data.aliases
328
if cmdalias in self.user_aliases
329
for useralias in self.user_aliases[cmdalias]
330
if useralias not in cmd_data.aliases]))
333
for optname, opt in sorted(opts.iteritems()):
334
cmd_data.options.extend(self.option(opt))
336
if 'help' == name or 'help' in cmd.aliases:
337
cmd_data.fixed_words = ('"$cmds %s"' %
338
" ".join(sorted(help_topics.topic_registry.keys())))
342
def option(self, opt):
344
parser = option.get_optparser({opt.name: opt})
345
parser = self.wrap_parser(optswitches, parser)
347
opt.add_option(parser, opt.short_name())
348
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
349
enum_switch = '--%s' % opt.name
350
enum_data = optswitches.get(enum_switch)
353
enum_data.registry_keys = opt.registry.keys()
354
except ImportError, e:
355
enum_data.error_messages.append(
356
"ERROR getting registry keys for '--%s': %s"
357
% (opt.name, str(e).split('\n')[0]))
358
return sorted(optswitches.values())
360
def wrap_container(self, optswitches, parser):
361
def tweaked_add_option(*opts, **attrs):
363
optswitches[name] = OptionData(name)
364
parser.add_option = tweaked_add_option
367
def wrap_parser(self, optswitches, parser):
368
orig_add_option_group = parser.add_option_group
369
def tweaked_add_option_group(*opts, **attrs):
370
return self.wrap_container(optswitches,
371
orig_add_option_group(*opts, **attrs))
372
parser.add_option_group = tweaked_add_option_group
373
return self.wrap_container(optswitches, parser)
376
def bash_completion_function(out, function_name="_bzr", function_only=False,
378
no_plugins=False, selected_plugins=None):
379
dc = DataCollector(no_plugins=no_plugins, selected_plugins=selected_plugins)
381
cg = BashCodeGen(data, function_name=function_name, debug=debug)
389
class cmd_bash_completion(commands.Command):
390
__doc__ = """Generate a shell function for bash command line completion.
392
This command generates a shell function which can be used by bash to
393
automatically complete the currently typed command when the user presses
394
the completion key (usually tab).
396
Commonly used like this:
397
eval "`bzr bash-completion`"
401
option.Option("function-name", short_name="f", type=str, argname="name",
402
help="Name of the generated function (default: _bzr)"),
403
option.Option("function-only", short_name="o", type=None,
404
help="Generate only the shell function, don't enable it"),
405
option.Option("debug", type=None, hidden=True,
406
help="Enable shell code useful for debugging"),
407
option.ListOption("plugin", type=str, argname="name",
408
# param_name="selected_plugins", # doesn't work, bug #387117
409
help="Enable completions for the selected plugin"
410
+ " (default: all plugins)"),
413
def run(self, **kwargs):
415
from bashcomp import bash_completion_function
416
if 'plugin' in kwargs:
417
# work around bug #387117 which prevents us from using param_name
418
if len(kwargs['plugin']) > 0:
419
kwargs['selected_plugins'] = kwargs['plugin']
421
bash_completion_function(sys.stdout, **kwargs)
424
if __name__ == '__main__':
430
def plugin_callback(option, opt, value, parser):
431
values = parser.values.selected_plugins
437
parser = optparse.OptionParser(usage="%prog [-f NAME] [-o]")
438
parser.add_option("--function-name", "-f", metavar="NAME",
439
help="Name of the generated function (default: _bzr)")
440
parser.add_option("--function-only", "-o", action="store_true",
441
help="Generate only the shell function, don't enable it")
442
parser.add_option("--debug", action="store_true",
443
help=optparse.SUPPRESS_HELP)
444
parser.add_option("--no-plugins", action="store_true",
445
help="Don't load any bzr plugins")
446
parser.add_option("--plugin", metavar="NAME", type="string",
447
dest="selected_plugins", default=[],
448
action="callback", callback=plugin_callback,
449
help="Enable completions for the selected plugin"
450
+ " (default: all plugins)")
451
(opts, args) = parser.parse_args()
453
parser.error("script does not take positional arguments")
455
for name, value in opts.__dict__.iteritems():
456
if value is not None:
459
locale.setlocale(locale.LC_ALL, '')
460
if not kwargs.get('no_plugins', False):
461
plugin.load_plugins()
462
commands.install_bzr_command_hooks()
463
bash_completion_function(sys.stdout, **kwargs)