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
34
class BashCodeGen(object):
35
"""Generate a bash script for given completion data."""
37
def __init__(self, data, function_name='_bzr', debug=False):
39
self.function_name = function_name
44
# Programmable completion for the Bazaar-NG bzr command under bash.
45
# Known to work with bash 2.05a as well as bash 4.1.2, and probably
46
# all versions in between as well.
48
# Based originally on the svn bash completition script.
49
# Customized by Sven Wilhelm/Icecrash.com
50
# Adjusted for automatic generation by Martin von Gagern
52
# Generated using the bash_completion plugin.
53
# See https://launchpad.net/bzr-bash-completion for details.
55
# Commands and options of bzr %(bzr_version)s
59
complete -F %(function_name)s -o default bzr
61
"function_name": self.function_name,
62
"function": self.function(),
63
"bzr_version": self.bzr_version(),
70
local cur cmds cmdIdx cmd cmdOpts fixedWords i globalOpts
75
cur=${COMP_WORDS[COMP_CWORD]}
78
globalOpts=( %(global_options)s )
80
# do ordinary expansion if we are anywhere after a -- argument
81
for ((i = 1; i < COMP_CWORD; ++i)); do
82
[[ ${COMP_WORDS[i]} == "--" ]] && return 0
85
# find the command; it's the first word not starting in -
87
for ((cmdIdx = 1; cmdIdx < ${#COMP_WORDS[@]}; ++cmdIdx)); do
88
if [[ ${COMP_WORDS[cmdIdx]} != -* ]]; then
89
cmd=${COMP_WORDS[cmdIdx]}
94
# complete command name if we are not already past the command
95
if [[ $COMP_CWORD -le cmdIdx ]]; then
96
COMPREPLY=( $( compgen -W "$cmds ${globalOpts[*]}" -- $cur ) )
100
# find the option for which we want to complete a value
102
if [[ $cur != -* ]] && [[ $COMP_CWORD -gt 1 ]]; then
103
curOpt=${COMP_WORDS[COMP_CWORD - 1]}
104
if [[ $curOpt == = ]]; then
105
curOpt=${COMP_WORDS[COMP_CWORD - 2]}
106
elif [[ $cur == : ]]; then
109
elif [[ $curOpt == : ]]; then
110
curOpt=${COMP_WORDS[COMP_CWORD - 2]}:
125
if [[ ${#fixedWords[@]} -eq 0 ]] && [[ ${#optEnums[@]} -eq 0 ]] && [[ $cur != -* ]]; then
128
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//; s/ /\\\\\\\\ /g;') )
133
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//; s/^/tag:/') )
136
fixedWords=( $(bzr tags 2>/dev/null | sed 's/ *[^ ]*$//') )
137
fixedWords=( $(for i in "${fixedWords[@]}"; do echo "${cur%%..tag:*}..tag:${i}"; done) )
140
elif [[ $cur == = ]] && [[ ${#optEnums[@]} -gt 0 ]]; then
141
# complete directly after "--option=", list all enum values
142
COMPREPLY=( "${optEnums[@]}" )
145
fixedWords=( "${cmdOpts[@]}"
151
if [[ ${#fixedWords[@]} -gt 0 ]]; then
152
COMPREPLY=( $( compgen -W "${fixedWords[*]}" -- $cur ) )
158
"cmds": self.command_names(),
159
"function_name": self.function_name,
160
"cases": self.command_cases(),
161
"global_options": self.global_options(),
162
"debug": self.debug_output(),
164
# Help Emacs terminate strings: "
166
def command_names(self):
167
return " ".join(self.data.all_command_aliases())
169
def debug_output(self):
174
# Debugging code enabled using the --debug command line switch.
175
# Will dump some variables to the top portion of the terminal.
177
for (( i=0; i < ${#COMP_WORDS[@]}; ++i)); do
178
echo "\$COMP_WORDS[$i]='${COMP_WORDS[i]}'"$'\e[K'
180
for i in COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY cur curOpt; do
181
echo "\$${i}=\"${!i}\""$'\e[K'
183
echo -ne '---\e[K\e[u'
186
def bzr_version(self):
187
bzr_version = bzrlib.version_string
188
if not self.data.plugins:
191
bzr_version += " and the following plugins:"
192
for name, plugin in sorted(self.data.plugins.iteritems()):
193
bzr_version += "\n# %s" % plugin
196
def global_options(self):
197
return " ".join(sorted(self.data.global_options))
199
def command_cases(self):
201
for command in self.data.commands:
202
cases += self.command_case(command)
205
def command_case(self, command):
206
case = "\t%s)\n" % "|".join(command.aliases)
208
case += "\t\t# plugin \"%s\"\n" % command.plugin
211
for option in command.options:
212
for message in option.error_messages:
213
case += "\t\t# %s\n" % message
214
if option.registry_keys:
215
for key in option.registry_keys:
216
options.append("%s=%s" % (option, key))
217
enums.append("%s) optEnums=( %s ) ;;" %
218
(option, ' '.join(option.registry_keys)))
220
options.append(str(option))
221
case += "\t\tcmdOpts=( %s )\n" % " ".join(options)
222
if command.fixed_words:
223
fixed_words = command.fixed_words
224
if isinstance(fixed_words, list):
225
fixed_words = "( %s )" + ' '.join(fixed_words)
226
case += "\t\tfixedWords=%s\n" % fixed_words
228
case += "\t\tcase $curOpt in\n\t\t\t"
229
case += "\n\t\t\t".join(enums)
230
case += "\n\t\tesac\n"
235
class CompletionData(object):
239
self.global_options = set()
242
def all_command_aliases(self):
243
for c in self.commands:
248
class CommandData(object):
250
def __init__(self, name):
252
self.aliases = [name]
255
self.fixed_words = None
258
class PluginData(object):
260
def __init__(self, name, version=None):
263
version = bzrlib.plugin.plugins()[name].__version__
267
self.version = version
270
if self.version == 'unknown':
272
return '%s %s' % (self.name, self.version)
275
class OptionData(object):
277
def __init__(self, name):
279
self.registry_keys = None
280
self.error_messages = []
285
def __cmp__(self, other):
286
return cmp(self.name, other.name)
289
class DataCollector(object):
291
def __init__(self, no_plugins=False, selected_plugins=None):
292
self.data = CompletionData()
293
self.user_aliases = {}
295
self.selected_plugins = set()
296
elif selected_plugins is None:
297
self.selected_plugins = None
299
self.selected_plugins = set([x.replace('-', '_')
300
for x in selected_plugins])
303
self.global_options()
308
def global_options(self):
309
re_switch = re.compile(r'\n(--[A-Za-z0-9-_]+)(?:, (-\S))?\s')
310
help_text = help_topics.topic_registry.get_detail('global-options')
311
for long, short in re_switch.findall(help_text):
312
self.data.global_options.add(long)
314
self.data.global_options.add(short)
317
for alias, expansion in config.GlobalConfig().get_aliases().iteritems():
318
for token in cmdline.split(expansion):
319
if not token.startswith("-"):
320
self.user_aliases.setdefault(token, set()).add(alias)
324
for name in sorted(commands.all_command_names()):
327
def command(self, name):
328
cmd = commands.get_cmd_object(name)
329
cmd_data = CommandData(name)
331
plugin_name = cmd.plugin_name()
332
if plugin_name is not None:
333
if (self.selected_plugins is not None and
334
plugin not in self.selected_plugins):
336
plugin_data = self.data.plugins.get(plugin_name)
337
if plugin_data is None:
338
plugin_data = PluginData(plugin_name)
339
self.data.plugins[plugin_name] = plugin_data
340
cmd_data.plugin = plugin_data
341
self.data.commands.append(cmd_data)
343
# Find all aliases to the command; both cmd-defined and user-defined.
344
# We assume a user won't override one command with a different one,
345
# but will choose completely new names or add options to existing
346
# ones while maintaining the actual command name unchanged.
347
cmd_data.aliases.extend(cmd.aliases)
348
cmd_data.aliases.extend(sorted([useralias
349
for cmdalias in cmd_data.aliases
350
if cmdalias in self.user_aliases
351
for useralias in self.user_aliases[cmdalias]
352
if useralias not in cmd_data.aliases]))
355
for optname, opt in sorted(opts.iteritems()):
356
cmd_data.options.extend(self.option(opt))
358
if 'help' == name or 'help' in cmd.aliases:
359
cmd_data.fixed_words = ('($cmds %s)' %
360
" ".join(sorted(help_topics.topic_registry.keys())))
364
def option(self, opt):
366
parser = option.get_optparser({opt.name: opt})
367
parser = self.wrap_parser(optswitches, parser)
369
opt.add_option(parser, opt.short_name())
370
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
371
enum_switch = '--%s' % opt.name
372
enum_data = optswitches.get(enum_switch)
375
enum_data.registry_keys = opt.registry.keys()
376
except ImportError, e:
377
enum_data.error_messages.append(
378
"ERROR getting registry keys for '--%s': %s"
379
% (opt.name, str(e).split('\n')[0]))
380
return sorted(optswitches.values())
382
def wrap_container(self, optswitches, parser):
383
def tweaked_add_option(*opts, **attrs):
385
optswitches[name] = OptionData(name)
386
parser.add_option = tweaked_add_option
389
def wrap_parser(self, optswitches, parser):
390
orig_add_option_group = parser.add_option_group
391
def tweaked_add_option_group(*opts, **attrs):
392
return self.wrap_container(optswitches,
393
orig_add_option_group(*opts, **attrs))
394
parser.add_option_group = tweaked_add_option_group
395
return self.wrap_container(optswitches, parser)
398
def bash_completion_function(out, function_name="_bzr", function_only=False,
400
no_plugins=False, selected_plugins=None):
401
dc = DataCollector(no_plugins=no_plugins, selected_plugins=selected_plugins)
403
cg = BashCodeGen(data, function_name=function_name, debug=debug)
411
class cmd_bash_completion(commands.Command):
412
__doc__ = """Generate a shell function for bash command line completion.
414
This command generates a shell function which can be used by bash to
415
automatically complete the currently typed command when the user presses
416
the completion key (usually tab).
418
Commonly used like this:
419
eval "`bzr bash-completion`"
423
option.Option("function-name", short_name="f", type=str, argname="name",
424
help="Name of the generated function (default: _bzr)"),
425
option.Option("function-only", short_name="o", type=None,
426
help="Generate only the shell function, don't enable it"),
427
option.Option("debug", type=None, hidden=True,
428
help="Enable shell code useful for debugging"),
429
option.ListOption("plugin", type=str, argname="name",
430
# param_name="selected_plugins", # doesn't work, bug #387117
431
help="Enable completions for the selected plugin"
432
+ " (default: all plugins)"),
435
def run(self, **kwargs):
436
if 'plugin' in kwargs:
437
# work around bug #387117 which prevents us from using param_name
438
if len(kwargs['plugin']) > 0:
439
kwargs['selected_plugins'] = kwargs['plugin']
441
bash_completion_function(sys.stdout, **kwargs)
444
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)