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='_brz', debug=False):
39
self.function_name = function_name
44
# Programmable completion for the Breezy brz 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 brz %(brz_version)s
59
complete -F %(function_name)s -o default brz
61
"function_name": self.function_name,
62
"function": self.function(),
63
"brz_version": self.brz_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=( $(brz tags 2>/dev/null | sed 's/ *[^ ]*$//; s/ /\\\\\\\\ /g;') )
133
fixedWords=( $(brz tags 2>/dev/null | sed 's/ *[^ ]*$//; s/^/tag:/') )
136
fixedWords=( $(brz 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 brz_version(self):
187
brz_version = breezy.version_string
188
if not self.data.plugins:
191
brz_version += " and the following plugins:"
192
for name, plugin in sorted(self.data.plugins.items()):
193
brz_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 = breezy.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)
288
def __lt__(self, other):
289
return self.name < other.name
292
class DataCollector(object):
294
def __init__(self, no_plugins=False, selected_plugins=None):
295
self.data = CompletionData()
296
self.user_aliases = {}
298
self.selected_plugins = set()
299
elif selected_plugins is None:
300
self.selected_plugins = None
302
self.selected_plugins = {x.replace('-', '_')
303
for x in selected_plugins}
306
self.global_options()
311
def global_options(self):
312
re_switch = re.compile(r'\n(--[A-Za-z0-9-_]+)(?:, (-\S))?\s')
313
help_text = help_topics.topic_registry.get_detail('global-options')
314
for long, short in re_switch.findall(help_text):
315
self.data.global_options.add(long)
317
self.data.global_options.add(short)
320
for alias, expansion in config.GlobalConfig().get_aliases().items():
321
for token in cmdline.split(expansion):
322
if not token.startswith("-"):
323
self.user_aliases.setdefault(token, set()).add(alias)
327
for name in sorted(commands.all_command_names()):
330
def command(self, name):
331
cmd = commands.get_cmd_object(name)
332
cmd_data = CommandData(name)
334
plugin_name = cmd.plugin_name()
335
if plugin_name is not None:
336
if (self.selected_plugins is not None and
337
plugin not in self.selected_plugins):
339
plugin_data = self.data.plugins.get(plugin_name)
340
if plugin_data is None:
341
plugin_data = PluginData(plugin_name)
342
self.data.plugins[plugin_name] = plugin_data
343
cmd_data.plugin = plugin_data
344
self.data.commands.append(cmd_data)
346
# Find all aliases to the command; both cmd-defined and user-defined.
347
# We assume a user won't override one command with a different one,
348
# but will choose completely new names or add options to existing
349
# ones while maintaining the actual command name unchanged.
350
cmd_data.aliases.extend(cmd.aliases)
351
cmd_data.aliases.extend(sorted([useralias
352
for cmdalias in cmd_data.aliases
353
if cmdalias in self.user_aliases
354
for useralias in self.user_aliases[cmdalias]
355
if useralias not in cmd_data.aliases]))
358
for optname, opt in sorted(opts.items()):
359
cmd_data.options.extend(self.option(opt))
361
if 'help' == name or 'help' in cmd.aliases:
362
cmd_data.fixed_words = ('($cmds %s)' %
363
" ".join(sorted(help_topics.topic_registry.keys())))
367
def option(self, opt):
369
parser = option.get_optparser([opt])
370
parser = self.wrap_parser(optswitches, parser)
372
opt.add_option(parser, opt.short_name())
373
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
374
enum_switch = '--%s' % opt.name
375
enum_data = optswitches.get(enum_switch)
378
enum_data.registry_keys = opt.registry.keys()
379
except ImportError as e:
380
enum_data.error_messages.append(
381
"ERROR getting registry keys for '--%s': %s"
382
% (opt.name, str(e).split('\n')[0]))
383
return sorted(optswitches.values())
385
def wrap_container(self, optswitches, parser):
386
def tweaked_add_option(*opts, **attrs):
388
optswitches[name] = OptionData(name)
389
parser.add_option = tweaked_add_option
392
def wrap_parser(self, optswitches, parser):
393
orig_add_option_group = parser.add_option_group
395
def tweaked_add_option_group(*opts, **attrs):
396
return self.wrap_container(optswitches,
397
orig_add_option_group(*opts, **attrs))
398
parser.add_option_group = tweaked_add_option_group
399
return self.wrap_container(optswitches, parser)
402
def bash_completion_function(out, function_name="_brz", function_only=False,
404
no_plugins=False, selected_plugins=None):
405
dc = DataCollector(no_plugins=no_plugins,
406
selected_plugins=selected_plugins)
408
cg = BashCodeGen(data, function_name=function_name, debug=debug)
416
class cmd_bash_completion(commands.Command):
417
__doc__ = """Generate a shell function for bash command line completion.
419
This command generates a shell function which can be used by bash to
420
automatically complete the currently typed command when the user presses
421
the completion key (usually tab).
423
Commonly used like this:
424
eval "`brz bash-completion`"
428
option.Option("function-name", short_name="f", type=str, argname="name",
429
help="Name of the generated function (default: _brz)"),
430
option.Option("function-only", short_name="o", type=None,
431
help="Generate only the shell function, don't enable it"),
432
option.Option("debug", type=None, hidden=True,
433
help="Enable shell code useful for debugging"),
434
option.ListOption("plugin", type=str, argname="name",
435
# param_name="selected_plugins", # doesn't work, bug #387117
436
help="Enable completions for the selected plugin"
437
+ " (default: all plugins)"),
440
def run(self, **kwargs):
441
if 'plugin' in kwargs:
442
# work around bug #387117 which prevents us from using param_name
443
if len(kwargs['plugin']) > 0:
444
kwargs['selected_plugins'] = kwargs['plugin']
446
bash_completion_function(sys.stdout, **kwargs)
449
if __name__ == '__main__':
454
def plugin_callback(option, opt, value, parser):
455
values = parser.values.selected_plugins
461
parser = optparse.OptionParser(usage="%prog [-f NAME] [-o]")
462
parser.add_option("--function-name", "-f", metavar="NAME",
463
help="Name of the generated function (default: _brz)")
464
parser.add_option("--function-only", "-o", action="store_true",
465
help="Generate only the shell function, don't enable it")
466
parser.add_option("--debug", action="store_true",
467
help=optparse.SUPPRESS_HELP)
468
parser.add_option("--no-plugins", action="store_true",
469
help="Don't load any brz plugins")
470
parser.add_option("--plugin", metavar="NAME", type="string",
471
dest="selected_plugins", default=[],
472
action="callback", callback=plugin_callback,
473
help="Enable completions for the selected plugin"
474
+ " (default: all plugins)")
475
(opts, args) = parser.parse_args()
477
parser.error("script does not take positional arguments")
479
for name, value in opts.__dict__.items():
480
if value is not None:
483
locale.setlocale(locale.LC_ALL, '')
484
if not kwargs.get('no_plugins', False):
485
plugin.load_plugins()
486
commands.install_bzr_command_hooks()
487
bash_completion_function(sys.stdout, **kwargs)