/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/plugins/bash_completion/bashcomp.py

  • Committer: Robert Collins
  • Date: 2010-05-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python3
2
 
 
3
 
# Copyright (C) 2009, 2010 Canonical Ltd
4
 
#
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.
9
 
#
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.
14
 
#
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
18
 
 
19
 
from __future__ import absolute_import
20
 
 
21
 
from ... import (
22
 
    cmdline,
23
 
    commands,
24
 
    config,
25
 
    help_topics,
26
 
    option,
27
 
    plugin,
28
 
)
29
 
from ...sixish import (
30
 
    text_type,
31
 
    )
32
 
import breezy
33
 
import re
34
 
import sys
35
 
 
36
 
 
37
 
class BashCodeGen(object):
38
 
    """Generate a bash script for given completion data."""
39
 
 
40
 
    def __init__(self, data, function_name='_brz', debug=False):
41
 
        self.data = data
42
 
        self.function_name = function_name
43
 
        self.debug = debug
44
 
 
45
 
    def script(self):
46
 
        return ("""\
47
 
# Programmable completion for the Breezy brz command under bash.
48
 
# Known to work with bash 2.05a as well as bash 4.1.2, and probably
49
 
# all versions in between as well.
50
 
 
51
 
# Based originally on the svn bash completition script.
52
 
# Customized by Sven Wilhelm/Icecrash.com
53
 
# Adjusted for automatic generation by Martin von Gagern
54
 
 
55
 
# Generated using the bash_completion plugin.
56
 
# See https://launchpad.net/bzr-bash-completion for details.
57
 
 
58
 
# Commands and options of brz %(brz_version)s
59
 
 
60
 
shopt -s progcomp
61
 
%(function)s
62
 
complete -F %(function_name)s -o default brz
63
 
""" % {
64
 
            "function_name": self.function_name,
65
 
            "function": self.function(),
66
 
            "brz_version": self.brz_version(),
67
 
        })
68
 
 
69
 
    def function(self):
70
 
        return ("""\
71
 
%(function_name)s ()
72
 
{
73
 
    local cur cmds cmdIdx cmd cmdOpts fixedWords i globalOpts
74
 
    local curOpt optEnums
75
 
    local IFS=$' \\n'
76
 
 
77
 
    COMPREPLY=()
78
 
    cur=${COMP_WORDS[COMP_CWORD]}
79
 
 
80
 
    cmds='%(cmds)s'
81
 
    globalOpts=( %(global_options)s )
82
 
 
83
 
    # do ordinary expansion if we are anywhere after a -- argument
84
 
    for ((i = 1; i < COMP_CWORD; ++i)); do
85
 
        [[ ${COMP_WORDS[i]} == "--" ]] && return 0
86
 
    done
87
 
 
88
 
    # find the command; it's the first word not starting in -
89
 
    cmd=
90
 
    for ((cmdIdx = 1; cmdIdx < ${#COMP_WORDS[@]}; ++cmdIdx)); do
91
 
        if [[ ${COMP_WORDS[cmdIdx]} != -* ]]; then
92
 
            cmd=${COMP_WORDS[cmdIdx]}
93
 
            break
94
 
        fi
95
 
    done
96
 
 
97
 
    # complete command name if we are not already past the command
98
 
    if [[ $COMP_CWORD -le cmdIdx ]]; then
99
 
        COMPREPLY=( $( compgen -W "$cmds ${globalOpts[*]}" -- $cur ) )
100
 
        return 0
101
 
    fi
102
 
 
103
 
    # find the option for which we want to complete a value
104
 
    curOpt=
105
 
    if [[ $cur != -* ]] && [[ $COMP_CWORD -gt 1 ]]; then
106
 
        curOpt=${COMP_WORDS[COMP_CWORD - 1]}
107
 
        if [[ $curOpt == = ]]; then
108
 
            curOpt=${COMP_WORDS[COMP_CWORD - 2]}
109
 
        elif [[ $cur == : ]]; then
110
 
            cur=
111
 
            curOpt="$curOpt:"
112
 
        elif [[ $curOpt == : ]]; then
113
 
            curOpt=${COMP_WORDS[COMP_CWORD - 2]}:
114
 
        fi
115
 
    fi
116
 
%(debug)s
117
 
    cmdOpts=( )
118
 
    optEnums=( )
119
 
    fixedWords=( )
120
 
    case $cmd in
121
 
%(cases)s\
122
 
    *)
123
 
        cmdOpts=(--help -h)
124
 
        ;;
125
 
    esac
126
 
 
127
 
    IFS=$'\\n'
128
 
    if [[ ${#fixedWords[@]} -eq 0 ]] && [[ ${#optEnums[@]} -eq 0 ]] && [[ $cur != -* ]]; then
129
 
        case $curOpt in
130
 
            tag:|*..tag:)
131
 
                fixedWords=( $(brz tags 2>/dev/null | sed 's/  *[^ ]*$//; s/ /\\\\\\\\ /g;') )
132
 
                ;;
133
 
        esac
134
 
        case $cur in
135
 
            [\\"\\']tag:*)
136
 
                fixedWords=( $(brz tags 2>/dev/null | sed 's/  *[^ ]*$//; s/^/tag:/') )
137
 
                ;;
138
 
            [\\"\\']*..tag:*)
139
 
                fixedWords=( $(brz tags 2>/dev/null | sed 's/  *[^ ]*$//') )
140
 
                fixedWords=( $(for i in "${fixedWords[@]}"; do echo "${cur%%..tag:*}..tag:${i}"; done) )
141
 
                ;;
142
 
        esac
143
 
    elif [[ $cur == = ]] && [[ ${#optEnums[@]} -gt 0 ]]; then
144
 
        # complete directly after "--option=", list all enum values
145
 
        COMPREPLY=( "${optEnums[@]}" )
146
 
        return 0
147
 
    else
148
 
        fixedWords=( "${cmdOpts[@]}"
149
 
                     "${globalOpts[@]}"
150
 
                     "${optEnums[@]}"
151
 
                     "${fixedWords[@]}" )
152
 
    fi
153
 
 
154
 
    if [[ ${#fixedWords[@]} -gt 0 ]]; then
155
 
        COMPREPLY=( $( compgen -W "${fixedWords[*]}" -- $cur ) )
156
 
    fi
157
 
 
158
 
    return 0
159
 
}
160
 
""" % {
161
 
            "cmds": self.command_names(),
162
 
            "function_name": self.function_name,
163
 
            "cases": self.command_cases(),
164
 
            "global_options": self.global_options(),
165
 
            "debug": self.debug_output(),
166
 
        })
167
 
        # Help Emacs terminate strings: "
168
 
 
169
 
    def command_names(self):
170
 
        return " ".join(self.data.all_command_aliases())
171
 
 
172
 
    def debug_output(self):
173
 
        if not self.debug:
174
 
            return ''
175
 
        else:
176
 
            return (r"""
177
 
    # Debugging code enabled using the --debug command line switch.
178
 
    # Will dump some variables to the top portion of the terminal.
179
 
    echo -ne '\e[s\e[H'
180
 
    for (( i=0; i < ${#COMP_WORDS[@]}; ++i)); do
181
 
        echo "\$COMP_WORDS[$i]='${COMP_WORDS[i]}'"$'\e[K'
182
 
    done
183
 
    for i in COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY cur curOpt; do
184
 
        echo "\$${i}=\"${!i}\""$'\e[K'
185
 
    done
186
 
    echo -ne '---\e[K\e[u'
187
 
""")
188
 
 
189
 
    def brz_version(self):
190
 
        brz_version = breezy.version_string
191
 
        if not self.data.plugins:
192
 
            brz_version += "."
193
 
        else:
194
 
            brz_version += " and the following plugins:"
195
 
            for name, plugin in sorted(self.data.plugins.items()):
196
 
                brz_version += "\n# %s" % plugin
197
 
        return brz_version
198
 
 
199
 
    def global_options(self):
200
 
        return " ".join(sorted(self.data.global_options))
201
 
 
202
 
    def command_cases(self):
203
 
        cases = ""
204
 
        for command in self.data.commands:
205
 
            cases += self.command_case(command)
206
 
        return cases
207
 
 
208
 
    def command_case(self, command):
209
 
        case = "\t%s)\n" % "|".join(command.aliases)
210
 
        if command.plugin:
211
 
            case += "\t\t# plugin \"%s\"\n" % command.plugin
212
 
        options = []
213
 
        enums = []
214
 
        for option in command.options:
215
 
            for message in option.error_messages:
216
 
                case += "\t\t# %s\n" % message
217
 
            if option.registry_keys:
218
 
                for key in option.registry_keys:
219
 
                    options.append("%s=%s" % (option, key))
220
 
                enums.append("%s) optEnums=( %s ) ;;" %
221
 
                             (option, ' '.join(option.registry_keys)))
222
 
            else:
223
 
                options.append(str(option))
224
 
        case += "\t\tcmdOpts=( %s )\n" % " ".join(options)
225
 
        if command.fixed_words:
226
 
            fixed_words = command.fixed_words
227
 
            if isinstance(fixed_words, list):
228
 
                fixed_words = "( %s )" + ' '.join(fixed_words)
229
 
            case += "\t\tfixedWords=%s\n" % fixed_words
230
 
        if enums:
231
 
            case += "\t\tcase $curOpt in\n\t\t\t"
232
 
            case += "\n\t\t\t".join(enums)
233
 
            case += "\n\t\tesac\n"
234
 
        case += "\t\t;;\n"
235
 
        return case
236
 
 
237
 
 
238
 
class CompletionData(object):
239
 
 
240
 
    def __init__(self):
241
 
        self.plugins = {}
242
 
        self.global_options = set()
243
 
        self.commands = []
244
 
 
245
 
    def all_command_aliases(self):
246
 
        for c in self.commands:
247
 
            for a in c.aliases:
248
 
                yield a
249
 
 
250
 
 
251
 
class CommandData(object):
252
 
 
253
 
    def __init__(self, name):
254
 
        self.name = name
255
 
        self.aliases = [name]
256
 
        self.plugin = None
257
 
        self.options = []
258
 
        self.fixed_words = None
259
 
 
260
 
 
261
 
class PluginData(object):
262
 
 
263
 
    def __init__(self, name, version=None):
264
 
        if version is None:
265
 
            try:
266
 
                version = breezy.plugin.plugins()[name].__version__
267
 
            except:
268
 
                version = 'unknown'
269
 
        self.name = name
270
 
        self.version = version
271
 
 
272
 
    def __str__(self):
273
 
        if self.version == 'unknown':
274
 
            return self.name
275
 
        return '%s %s' % (self.name, self.version)
276
 
 
277
 
 
278
 
class OptionData(object):
279
 
 
280
 
    def __init__(self, name):
281
 
        self.name = name
282
 
        self.registry_keys = None
283
 
        self.error_messages = []
284
 
 
285
 
    def __str__(self):
286
 
        return self.name
287
 
 
288
 
    def __cmp__(self, other):
289
 
        return cmp(self.name, other.name)
290
 
 
291
 
    def __lt__(self, other):
292
 
        return self.name < other.name
293
 
 
294
 
 
295
 
class DataCollector(object):
296
 
 
297
 
    def __init__(self, no_plugins=False, selected_plugins=None):
298
 
        self.data = CompletionData()
299
 
        self.user_aliases = {}
300
 
        if no_plugins:
301
 
            self.selected_plugins = set()
302
 
        elif selected_plugins is None:
303
 
            self.selected_plugins = None
304
 
        else:
305
 
            self.selected_plugins = {x.replace('-', '_')
306
 
                                     for x in selected_plugins}
307
 
 
308
 
    def collect(self):
309
 
        self.global_options()
310
 
        self.aliases()
311
 
        self.commands()
312
 
        return self.data
313
 
 
314
 
    def global_options(self):
315
 
        re_switch = re.compile(r'\n(--[A-Za-z0-9-_]+)(?:, (-\S))?\s')
316
 
        help_text = help_topics.topic_registry.get_detail('global-options')
317
 
        for long, short in re_switch.findall(help_text):
318
 
            self.data.global_options.add(long)
319
 
            if short:
320
 
                self.data.global_options.add(short)
321
 
 
322
 
    def aliases(self):
323
 
        for alias, expansion in config.GlobalConfig().get_aliases().items():
324
 
            for token in cmdline.split(expansion):
325
 
                if not token.startswith("-"):
326
 
                    self.user_aliases.setdefault(token, set()).add(alias)
327
 
                    break
328
 
 
329
 
    def commands(self):
330
 
        for name in sorted(commands.all_command_names()):
331
 
            self.command(name)
332
 
 
333
 
    def command(self, name):
334
 
        cmd = commands.get_cmd_object(name)
335
 
        cmd_data = CommandData(name)
336
 
 
337
 
        plugin_name = cmd.plugin_name()
338
 
        if plugin_name is not None:
339
 
            if (self.selected_plugins is not None and
340
 
                    plugin not in self.selected_plugins):
341
 
                return None
342
 
            plugin_data = self.data.plugins.get(plugin_name)
343
 
            if plugin_data is None:
344
 
                plugin_data = PluginData(plugin_name)
345
 
                self.data.plugins[plugin_name] = plugin_data
346
 
            cmd_data.plugin = plugin_data
347
 
        self.data.commands.append(cmd_data)
348
 
 
349
 
        # Find all aliases to the command; both cmd-defined and user-defined.
350
 
        # We assume a user won't override one command with a different one,
351
 
        # but will choose completely new names or add options to existing
352
 
        # ones while maintaining the actual command name unchanged.
353
 
        cmd_data.aliases.extend(cmd.aliases)
354
 
        cmd_data.aliases.extend(sorted([useralias
355
 
                                        for cmdalias in cmd_data.aliases
356
 
                                        if cmdalias in self.user_aliases
357
 
                                        for useralias in self.user_aliases[cmdalias]
358
 
                                        if useralias not in cmd_data.aliases]))
359
 
 
360
 
        opts = cmd.options()
361
 
        for optname, opt in sorted(opts.items()):
362
 
            cmd_data.options.extend(self.option(opt))
363
 
 
364
 
        if 'help' == name or 'help' in cmd.aliases:
365
 
            cmd_data.fixed_words = ('($cmds %s)' %
366
 
                                    " ".join(sorted(help_topics.topic_registry.keys())))
367
 
 
368
 
        return cmd_data
369
 
 
370
 
    def option(self, opt):
371
 
        optswitches = {}
372
 
        parser = option.get_optparser([opt])
373
 
        parser = self.wrap_parser(optswitches, parser)
374
 
        optswitches.clear()
375
 
        opt.add_option(parser, opt.short_name())
376
 
        if isinstance(opt, option.RegistryOption) and opt.enum_switch:
377
 
            enum_switch = '--%s' % opt.name
378
 
            enum_data = optswitches.get(enum_switch)
379
 
            if enum_data:
380
 
                try:
381
 
                    enum_data.registry_keys = opt.registry.keys()
382
 
                except ImportError as e:
383
 
                    enum_data.error_messages.append(
384
 
                        "ERROR getting registry keys for '--%s': %s"
385
 
                        % (opt.name, str(e).split('\n')[0]))
386
 
        return sorted(optswitches.values())
387
 
 
388
 
    def wrap_container(self, optswitches, parser):
389
 
        def tweaked_add_option(*opts, **attrs):
390
 
            for name in opts:
391
 
                optswitches[name] = OptionData(name)
392
 
        parser.add_option = tweaked_add_option
393
 
        return parser
394
 
 
395
 
    def wrap_parser(self, optswitches, parser):
396
 
        orig_add_option_group = parser.add_option_group
397
 
 
398
 
        def tweaked_add_option_group(*opts, **attrs):
399
 
            return self.wrap_container(optswitches,
400
 
                                       orig_add_option_group(*opts, **attrs))
401
 
        parser.add_option_group = tweaked_add_option_group
402
 
        return self.wrap_container(optswitches, parser)
403
 
 
404
 
 
405
 
def bash_completion_function(out, function_name="_brz", function_only=False,
406
 
                             debug=False,
407
 
                             no_plugins=False, selected_plugins=None):
408
 
    dc = DataCollector(no_plugins=no_plugins,
409
 
                       selected_plugins=selected_plugins)
410
 
    data = dc.collect()
411
 
    cg = BashCodeGen(data, function_name=function_name, debug=debug)
412
 
    if function_only:
413
 
        res = cg.function()
414
 
    else:
415
 
        res = cg.script()
416
 
    out.write(res)
417
 
 
418
 
 
419
 
class cmd_bash_completion(commands.Command):
420
 
    __doc__ = """Generate a shell function for bash command line completion.
421
 
 
422
 
    This command generates a shell function which can be used by bash to
423
 
    automatically complete the currently typed command when the user presses
424
 
    the completion key (usually tab).
425
 
 
426
 
    Commonly used like this:
427
 
        eval "`brz bash-completion`"
428
 
    """
429
 
 
430
 
    takes_options = [
431
 
        option.Option("function-name", short_name="f", type=text_type, argname="name",
432
 
                      help="Name of the generated function (default: _brz)"),
433
 
        option.Option("function-only", short_name="o", type=None,
434
 
                      help="Generate only the shell function, don't enable it"),
435
 
        option.Option("debug", type=None, hidden=True,
436
 
                      help="Enable shell code useful for debugging"),
437
 
        option.ListOption("plugin", type=text_type, argname="name",
438
 
                          # param_name="selected_plugins", # doesn't work, bug #387117
439
 
                          help="Enable completions for the selected plugin"
440
 
                          + " (default: all plugins)"),
441
 
        ]
442
 
 
443
 
    def run(self, **kwargs):
444
 
        if 'plugin' in kwargs:
445
 
            # work around bug #387117 which prevents us from using param_name
446
 
            if len(kwargs['plugin']) > 0:
447
 
                kwargs['selected_plugins'] = kwargs['plugin']
448
 
            del kwargs['plugin']
449
 
        bash_completion_function(sys.stdout, **kwargs)
450
 
 
451
 
 
452
 
if __name__ == '__main__':
453
 
 
454
 
    import locale
455
 
    import optparse
456
 
 
457
 
    def plugin_callback(option, opt, value, parser):
458
 
        values = parser.values.selected_plugins
459
 
        if value == '-':
460
 
            del values[:]
461
 
        else:
462
 
            values.append(value)
463
 
 
464
 
    parser = optparse.OptionParser(usage="%prog [-f NAME] [-o]")
465
 
    parser.add_option("--function-name", "-f", metavar="NAME",
466
 
                      help="Name of the generated function (default: _brz)")
467
 
    parser.add_option("--function-only", "-o", action="store_true",
468
 
                      help="Generate only the shell function, don't enable it")
469
 
    parser.add_option("--debug", action="store_true",
470
 
                      help=optparse.SUPPRESS_HELP)
471
 
    parser.add_option("--no-plugins", action="store_true",
472
 
                      help="Don't load any brz plugins")
473
 
    parser.add_option("--plugin", metavar="NAME", type="string",
474
 
                      dest="selected_plugins", default=[],
475
 
                      action="callback", callback=plugin_callback,
476
 
                      help="Enable completions for the selected plugin"
477
 
                      + " (default: all plugins)")
478
 
    (opts, args) = parser.parse_args()
479
 
    if args:
480
 
        parser.error("script does not take positional arguments")
481
 
    kwargs = dict()
482
 
    for name, value in opts.__dict__.items():
483
 
        if value is not None:
484
 
            kwargs[name] = value
485
 
 
486
 
    locale.setlocale(locale.LC_ALL, '')
487
 
    if not kwargs.get('no_plugins', False):
488
 
        plugin.load_plugins()
489
 
    commands.install_bzr_command_hooks()
490
 
    bash_completion_function(sys.stdout, **kwargs)