/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: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

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)