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
32
class BashCodeGen(object):
33
"""Generate a bash script for given completion data."""
35
def __init__(self, data, function_name='_brz', debug=False):
37
self.function_name = function_name
42
# Programmable completion for the Breezy brz command under bash.
43
# Known to work with bash 2.05a as well as bash 4.1.2, and probably
44
# all versions in between as well.
46
# Based originally on the svn bash completition script.
47
# Customized by Sven Wilhelm/Icecrash.com
48
# Adjusted for automatic generation by Martin von Gagern
50
# Generated using the bash_completion plugin.
51
# See https://launchpad.net/bzr-bash-completion for details.
53
# Commands and options of brz %(brz_version)s
57
complete -F %(function_name)s -o default brz
59
"function_name": self.function_name,
60
"function": self.function(),
61
"brz_version": self.brz_version(),
68
local cur cmds cmdIdx cmd cmdOpts fixedWords i globalOpts
73
cur=${COMP_WORDS[COMP_CWORD]}
76
globalOpts=( %(global_options)s )
78
# do ordinary expansion if we are anywhere after a -- argument
79
for ((i = 1; i < COMP_CWORD; ++i)); do
80
[[ ${COMP_WORDS[i]} == "--" ]] && return 0
83
# find the command; it's the first word not starting in -
85
for ((cmdIdx = 1; cmdIdx < ${#COMP_WORDS[@]}; ++cmdIdx)); do
86
if [[ ${COMP_WORDS[cmdIdx]} != -* ]]; then
87
cmd=${COMP_WORDS[cmdIdx]}
92
# complete command name if we are not already past the command
93
if [[ $COMP_CWORD -le cmdIdx ]]; then
94
COMPREPLY=( $( compgen -W "$cmds ${globalOpts[*]}" -- $cur ) )
98
# find the option for which we want to complete a value
100
if [[ $cur != -* ]] && [[ $COMP_CWORD -gt 1 ]]; then
101
curOpt=${COMP_WORDS[COMP_CWORD - 1]}
102
if [[ $curOpt == = ]]; then
103
curOpt=${COMP_WORDS[COMP_CWORD - 2]}
104
elif [[ $cur == : ]]; then
107
elif [[ $curOpt == : ]]; then
108
curOpt=${COMP_WORDS[COMP_CWORD - 2]}:
123
if [[ ${#fixedWords[@]} -eq 0 ]] && [[ ${#optEnums[@]} -eq 0 ]] && [[ $cur != -* ]]; then
126
fixedWords=( $(brz tags 2>/dev/null | sed 's/ *[^ ]*$//; s/ /\\\\\\\\ /g;') )
131
fixedWords=( $(brz tags 2>/dev/null | sed 's/ *[^ ]*$//; s/^/tag:/') )
134
fixedWords=( $(brz tags 2>/dev/null | sed 's/ *[^ ]*$//') )
135
fixedWords=( $(for i in "${fixedWords[@]}"; do echo "${cur%%..tag:*}..tag:${i}"; done) )
138
elif [[ $cur == = ]] && [[ ${#optEnums[@]} -gt 0 ]]; then
139
# complete directly after "--option=", list all enum values
140
COMPREPLY=( "${optEnums[@]}" )
143
fixedWords=( "${cmdOpts[@]}"
149
if [[ ${#fixedWords[@]} -gt 0 ]]; then
150
COMPREPLY=( $( compgen -W "${fixedWords[*]}" -- $cur ) )
156
"cmds": self.command_names(),
157
"function_name": self.function_name,
158
"cases": self.command_cases(),
159
"global_options": self.global_options(),
160
"debug": self.debug_output(),
162
# Help Emacs terminate strings: "
164
def command_names(self):
165
return " ".join(self.data.all_command_aliases())
167
def debug_output(self):
172
# Debugging code enabled using the --debug command line switch.
173
# Will dump some variables to the top portion of the terminal.
175
for (( i=0; i < ${#COMP_WORDS[@]}; ++i)); do
176
echo "\$COMP_WORDS[$i]='${COMP_WORDS[i]}'"$'\e[K'
178
for i in COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY cur curOpt; do
179
echo "\$${i}=\"${!i}\""$'\e[K'
181
echo -ne '---\e[K\e[u'
184
def brz_version(self):
185
brz_version = breezy.version_string
186
if not self.data.plugins:
189
brz_version += " and the following plugins:"
190
for name, plugin in sorted(self.data.plugins.items()):
191
brz_version += "\n# %s" % plugin
194
def global_options(self):
195
return " ".join(sorted(self.data.global_options))
197
def command_cases(self):
199
for command in self.data.commands:
200
cases += self.command_case(command)
203
def command_case(self, command):
204
case = "\t%s)\n" % "|".join(command.aliases)
206
case += "\t\t# plugin \"%s\"\n" % command.plugin
209
for option in command.options:
210
for message in option.error_messages:
211
case += "\t\t# %s\n" % message
212
if option.registry_keys:
213
for key in option.registry_keys:
214
options.append("%s=%s" % (option, key))
215
enums.append("%s) optEnums=( %s ) ;;" %
216
(option, ' '.join(option.registry_keys)))
218
options.append(str(option))
219
case += "\t\tcmdOpts=( %s )\n" % " ".join(options)
220
if command.fixed_words:
221
fixed_words = command.fixed_words
222
if isinstance(fixed_words, list):
223
fixed_words = "( %s )" + ' '.join(fixed_words)
224
case += "\t\tfixedWords=%s\n" % fixed_words
226
case += "\t\tcase $curOpt in\n\t\t\t"
227
case += "\n\t\t\t".join(enums)
228
case += "\n\t\tesac\n"
233
class CompletionData(object):
237
self.global_options = set()
240
def all_command_aliases(self):
241
for c in self.commands:
246
class CommandData(object):
248
def __init__(self, name):
250
self.aliases = [name]
253
self.fixed_words = None
256
class PluginData(object):
258
def __init__(self, name, version=None):
261
version = breezy.plugin.plugins()[name].__version__
265
self.version = version
268
if self.version == 'unknown':
270
return '%s %s' % (self.name, self.version)
273
class OptionData(object):
275
def __init__(self, name):
277
self.registry_keys = None
278
self.error_messages = []
283
def __cmp__(self, other):
284
return cmp(self.name, other.name)
286
def __lt__(self, other):
287
return self.name < other.name
290
class DataCollector(object):
292
def __init__(self, no_plugins=False, selected_plugins=None):
293
self.data = CompletionData()
294
self.user_aliases = {}
296
self.selected_plugins = set()
297
elif selected_plugins is None:
298
self.selected_plugins = None
300
self.selected_plugins = {x.replace('-', '_')
301
for x in selected_plugins}
304
self.global_options()
309
def global_options(self):
310
re_switch = re.compile(r'\n(--[A-Za-z0-9-_]+)(?:, (-\S))?\s')
311
help_text = help_topics.topic_registry.get_detail('global-options')
312
for long, short in re_switch.findall(help_text):
313
self.data.global_options.add(long)
315
self.data.global_options.add(short)
318
for alias, expansion in config.GlobalConfig().get_aliases().items():
319
for token in cmdline.split(expansion):
320
if not token.startswith("-"):
321
self.user_aliases.setdefault(token, set()).add(alias)
325
for name in sorted(commands.all_command_names()):
328
def command(self, name):
329
cmd = commands.get_cmd_object(name)
330
cmd_data = CommandData(name)
332
plugin_name = cmd.plugin_name()
333
if plugin_name is not None:
334
if (self.selected_plugins is not None and
335
plugin not in self.selected_plugins):
337
plugin_data = self.data.plugins.get(plugin_name)
338
if plugin_data is None:
339
plugin_data = PluginData(plugin_name)
340
self.data.plugins[plugin_name] = plugin_data
341
cmd_data.plugin = plugin_data
342
self.data.commands.append(cmd_data)
344
# Find all aliases to the command; both cmd-defined and user-defined.
345
# We assume a user won't override one command with a different one,
346
# but will choose completely new names or add options to existing
347
# ones while maintaining the actual command name unchanged.
348
cmd_data.aliases.extend(cmd.aliases)
349
cmd_data.aliases.extend(sorted([useralias
350
for cmdalias in cmd_data.aliases
351
if cmdalias in self.user_aliases
352
for useralias in self.user_aliases[cmdalias]
353
if useralias not in cmd_data.aliases]))
356
for optname, opt in sorted(opts.items()):
357
cmd_data.options.extend(self.option(opt))
359
if 'help' == name or 'help' in cmd.aliases:
360
cmd_data.fixed_words = ('($cmds %s)' %
361
" ".join(sorted(help_topics.topic_registry.keys())))
365
def option(self, opt):
367
parser = option.get_optparser([opt])
368
parser = self.wrap_parser(optswitches, parser)
370
opt.add_option(parser, opt.short_name())
371
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
372
enum_switch = '--%s' % opt.name
373
enum_data = optswitches.get(enum_switch)
376
enum_data.registry_keys = opt.registry.keys()
377
except ImportError as e:
378
enum_data.error_messages.append(
379
"ERROR getting registry keys for '--%s': %s"
380
% (opt.name, str(e).split('\n')[0]))
381
return sorted(optswitches.values())
383
def wrap_container(self, optswitches, parser):
384
def tweaked_add_option(*opts, **attrs):
386
optswitches[name] = OptionData(name)
387
parser.add_option = tweaked_add_option
390
def wrap_parser(self, optswitches, parser):
391
orig_add_option_group = parser.add_option_group
393
def tweaked_add_option_group(*opts, **attrs):
394
return self.wrap_container(optswitches,
395
orig_add_option_group(*opts, **attrs))
396
parser.add_option_group = tweaked_add_option_group
397
return self.wrap_container(optswitches, parser)
400
def bash_completion_function(out, function_name="_brz", function_only=False,
402
no_plugins=False, selected_plugins=None):
403
dc = DataCollector(no_plugins=no_plugins,
404
selected_plugins=selected_plugins)
406
cg = BashCodeGen(data, function_name=function_name, debug=debug)
414
class cmd_bash_completion(commands.Command):
415
__doc__ = """Generate a shell function for bash command line completion.
417
This command generates a shell function which can be used by bash to
418
automatically complete the currently typed command when the user presses
419
the completion key (usually tab).
421
Commonly used like this:
422
eval "`brz bash-completion`"
426
option.Option("function-name", short_name="f", type=str, argname="name",
427
help="Name of the generated function (default: _brz)"),
428
option.Option("function-only", short_name="o", type=None,
429
help="Generate only the shell function, don't enable it"),
430
option.Option("debug", type=None, hidden=True,
431
help="Enable shell code useful for debugging"),
432
option.ListOption("plugin", type=str, argname="name",
433
# param_name="selected_plugins", # doesn't work, bug #387117
434
help="Enable completions for the selected plugin"
435
+ " (default: all plugins)"),
438
def run(self, **kwargs):
439
if 'plugin' in kwargs:
440
# work around bug #387117 which prevents us from using param_name
441
if len(kwargs['plugin']) > 0:
442
kwargs['selected_plugins'] = kwargs['plugin']
444
bash_completion_function(sys.stdout, **kwargs)
447
if __name__ == '__main__':
452
def plugin_callback(option, opt, value, parser):
453
values = parser.values.selected_plugins
459
parser = optparse.OptionParser(usage="%prog [-f NAME] [-o]")
460
parser.add_option("--function-name", "-f", metavar="NAME",
461
help="Name of the generated function (default: _brz)")
462
parser.add_option("--function-only", "-o", action="store_true",
463
help="Generate only the shell function, don't enable it")
464
parser.add_option("--debug", action="store_true",
465
help=optparse.SUPPRESS_HELP)
466
parser.add_option("--no-plugins", action="store_true",
467
help="Don't load any brz plugins")
468
parser.add_option("--plugin", metavar="NAME", type="string",
469
dest="selected_plugins", default=[],
470
action="callback", callback=plugin_callback,
471
help="Enable completions for the selected plugin"
472
+ " (default: all plugins)")
473
(opts, args) = parser.parse_args()
475
parser.error("script does not take positional arguments")
477
for name, value in opts.__dict__.items():
478
if value is not None:
481
locale.setlocale(locale.LC_ALL, '')
482
if not kwargs.get('no_plugins', False):
483
plugin.load_plugins()
484
commands.install_bzr_command_hooks()
485
bash_completion_function(sys.stdout, **kwargs)