/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: Martin
  • Date: 2010-05-25 17:27:52 UTC
  • mfrom: (5254 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5257.
  • Revision ID: gzlist@googlemail.com-20100525172752-amm089xcikv968sw
Merge bzr.dev to unite with similar changes already made

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)