/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: 2019-06-03 23:48:08 UTC
  • mfrom: (7316 work)
  • mto: This revision was merged to the branch mainline in revision 7328.
  • Revision ID: jelmer@jelmer.uk-20190603234808-15yk5c7054tj8e2b
Merge trunk.

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