/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/zsh_completion/zshcomp.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

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 ... 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 = [
264
 
        option.Option("function-name", short_name="f", type=str, argname="name",
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"),
268
 
        option.ListOption("plugin", type=str, argname="name",
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)