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