/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 bzrlib/plugins/bash_completion/bashcomp.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

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