/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
7476.2.1 by Jelmer Vernooij
Default to running Python 3.
1
#!/usr/bin/env python3
7397.3.1 by Jelmer Vernooij
Add really basic zsh completion plugin.
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
import breezy
30
import re
31
import sys
32
33
34
class ZshCodeGen(object):
35
    """Generate a zsh script for given completion data."""
36
37
    def __init__(self, data, function_name='_brz', debug=False):
38
        self.data = data
39
        self.function_name = function_name
40
        self.debug = debug
41
42
    def script(self):
43
        return ("""\
44
#compdef brz bzr
45
46
%(function_name)s ()
47
{
48
    local ret=1
49
    local -a args
50
    args+=(
51
%(global-options)s
52
    )
53
54
    _arguments $args[@] && ret=0
55
56
    return ret
57
}
58
59
%(function_name)s
60
""" % {
61
            'global-options': self.global_options(),
62
            'function_name': self.function_name})
63
64
    def global_options(self):
65
        lines = []
66
        for (long, short, help) in self.data.global_options:
67
            lines.append(
68
                '      \'(%s%s)%s[%s]\'' % (
69
                    (short + ' ') if short else '',
70
                    long,
71
                    long,
72
                    help))
73
74
        return "\n".join(lines)
75
76
77
class CompletionData(object):
78
79
    def __init__(self):
80
        self.plugins = {}
81
        self.global_options = []
82
        self.commands = []
83
84
    def all_command_aliases(self):
85
        for c in self.commands:
86
            for a in c.aliases:
87
                yield a
88
89
90
class CommandData(object):
91
92
    def __init__(self, name):
93
        self.name = name
94
        self.aliases = [name]
95
        self.plugin = None
96
        self.options = []
97
        self.fixed_words = None
98
99
100
class PluginData(object):
101
102
    def __init__(self, name, version=None):
103
        if version is None:
104
            try:
105
                version = breezy.plugin.plugins()[name].__version__
106
            except:
107
                version = 'unknown'
108
        self.name = name
109
        self.version = version
110
111
    def __str__(self):
112
        if self.version == 'unknown':
113
            return self.name
114
        return '%s %s' % (self.name, self.version)
115
116
117
class OptionData(object):
118
119
    def __init__(self, name):
120
        self.name = name
121
        self.registry_keys = None
122
        self.error_messages = []
123
124
    def __str__(self):
125
        return self.name
126
127
    def __cmp__(self, other):
128
        return cmp(self.name, other.name)
129
130
    def __lt__(self, other):
131
        return self.name < other.name
132
133
134
class DataCollector(object):
135
136
    def __init__(self, no_plugins=False, selected_plugins=None):
137
        self.data = CompletionData()
138
        self.user_aliases = {}
139
        if no_plugins:
140
            self.selected_plugins = set()
141
        elif selected_plugins is None:
142
            self.selected_plugins = None
143
        else:
144
            self.selected_plugins = {x.replace('-', '_')
145
                                     for x in selected_plugins}
146
147
    def collect(self):
148
        self.global_options()
149
        self.aliases()
150
        self.commands()
151
        return self.data
152
153
    def global_options(self):
154
        for name, item in option.Option.OPTIONS.items():
155
            self.data.global_options.append(
156
                ('--' + item.name,
157
                 '-' + item.short_name() if item.short_name() else None,
158
                 item.help.rstrip()))
159
160
    def aliases(self):
161
        for alias, expansion in config.GlobalConfig().get_aliases().items():
162
            for token in cmdline.split(expansion):
163
                if not token.startswith("-"):
164
                    self.user_aliases.setdefault(token, set()).add(alias)
165
                    break
166
167
    def commands(self):
168
        for name in sorted(commands.all_command_names()):
169
            self.command(name)
170
171
    def command(self, name):
172
        cmd = commands.get_cmd_object(name)
173
        cmd_data = CommandData(name)
174
175
        plugin_name = cmd.plugin_name()
176
        if plugin_name is not None:
177
            if (self.selected_plugins is not None and
178
                    plugin not in self.selected_plugins):
179
                return None
180
            plugin_data = self.data.plugins.get(plugin_name)
181
            if plugin_data is None:
182
                plugin_data = PluginData(plugin_name)
183
                self.data.plugins[plugin_name] = plugin_data
184
            cmd_data.plugin = plugin_data
185
        self.data.commands.append(cmd_data)
186
187
        # Find all aliases to the command; both cmd-defined and user-defined.
188
        # We assume a user won't override one command with a different one,
189
        # but will choose completely new names or add options to existing
190
        # ones while maintaining the actual command name unchanged.
191
        cmd_data.aliases.extend(cmd.aliases)
192
        cmd_data.aliases.extend(sorted([useralias
193
                                        for cmdalias in cmd_data.aliases
194
                                        if cmdalias in self.user_aliases
195
                                        for useralias in self.user_aliases[cmdalias]
196
                                        if useralias not in cmd_data.aliases]))
197
198
        opts = cmd.options()
199
        for optname, opt in sorted(opts.items()):
200
            cmd_data.options.extend(self.option(opt))
201
202
        if 'help' == name or 'help' in cmd.aliases:
203
            cmd_data.fixed_words = ('($cmds %s)' %
204
                                    " ".join(sorted(help_topics.topic_registry.keys())))
205
206
        return cmd_data
207
208
    def option(self, opt):
209
        optswitches = {}
210
        parser = option.get_optparser([opt])
211
        parser = self.wrap_parser(optswitches, parser)
212
        optswitches.clear()
213
        opt.add_option(parser, opt.short_name())
214
        if isinstance(opt, option.RegistryOption) and opt.enum_switch:
215
            enum_switch = '--%s' % opt.name
216
            enum_data = optswitches.get(enum_switch)
217
            if enum_data:
218
                try:
219
                    enum_data.registry_keys = opt.registry.keys()
220
                except ImportError as e:
221
                    enum_data.error_messages.append(
222
                        "ERROR getting registry keys for '--%s': %s"
223
                        % (opt.name, str(e).split('\n')[0]))
224
        return sorted(optswitches.values())
225
226
    def wrap_container(self, optswitches, parser):
227
        def tweaked_add_option(*opts, **attrs):
228
            for name in opts:
229
                optswitches[name] = OptionData(name)
230
        parser.add_option = tweaked_add_option
231
        return parser
232
233
    def wrap_parser(self, optswitches, parser):
234
        orig_add_option_group = parser.add_option_group
235
236
        def tweaked_add_option_group(*opts, **attrs):
237
            return self.wrap_container(optswitches,
238
                                       orig_add_option_group(*opts, **attrs))
239
        parser.add_option_group = tweaked_add_option_group
240
        return self.wrap_container(optswitches, parser)
241
242
243
def zsh_completion_function(out, function_name="_brz",
244
                            debug=False,
245
                            no_plugins=False, selected_plugins=None):
246
    dc = DataCollector(no_plugins=no_plugins,
247
                       selected_plugins=selected_plugins)
248
    data = dc.collect()
249
    cg = ZshCodeGen(data, function_name=function_name, debug=debug)
250
    res = cg.script()
251
    out.write(res)
252
253
254
class cmd_zsh_completion(commands.Command):
255
    __doc__ = """Generate a shell function for zsh command line completion.
256
257
    This command generates a shell function which can be used by zsh to
258
    automatically complete the currently typed command when the user presses
259
    the completion key (usually tab).
260
261
    Commonly used like this:
262
        eval "`brz zsh -completion`"
263
    """
264
265
    takes_options = [
7479.2.1 by Jelmer Vernooij
Drop python2 support.
266
        option.Option("function-name", short_name="f", type=str, argname="name",
7397.3.1 by Jelmer Vernooij
Add really basic zsh completion plugin.
267
                      help="Name of the generated function (default: _brz)"),
268
        option.Option("debug", type=None, hidden=True,
269
                      help="Enable shell code useful for debugging"),
7479.2.1 by Jelmer Vernooij
Drop python2 support.
270
        option.ListOption("plugin", type=str, argname="name",
7397.3.1 by Jelmer Vernooij
Add really basic zsh completion plugin.
271
                          # param_name="selected_plugins", # doesn't work, bug #387117
272
                          help="Enable completions for the selected plugin"
273
                          + " (default: all plugins)"),
274
        ]
275
276
    def run(self, **kwargs):
277
        if 'plugin' in kwargs:
278
            # work around bug #387117 which prevents us from using param_name
279
            if len(kwargs['plugin']) > 0:
280
                kwargs['selected_plugins'] = kwargs['plugin']
281
            del kwargs['plugin']
282
        zsh_completion_function(sys.stdout, **kwargs)