File: //kunden/proc/self/root/lib/python3/dist-packages/breezy/plugins/zsh_completion/zshcomp.py
#!/usr/bin/env python3
# Copyright (C) 2009, 2010 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import absolute_import
from ... import (
cmdline,
commands,
config,
help_topics,
option,
plugin,
)
from ...sixish import (
text_type,
)
import breezy
import re
import sys
class ZshCodeGen(object):
"""Generate a zsh script for given completion data."""
def __init__(self, data, function_name='_brz', debug=False):
self.data = data
self.function_name = function_name
self.debug = debug
def script(self):
return ("""\
#compdef brz bzr
%(function_name)s ()
{
local ret=1
local -a args
args+=(
%(global-options)s
)
_arguments $args[@] && ret=0
return ret
}
%(function_name)s
""" % {
'global-options': self.global_options(),
'function_name': self.function_name})
def global_options(self):
lines = []
for (long, short, help) in self.data.global_options:
lines.append(
' \'(%s%s)%s[%s]\'' % (
(short + ' ') if short else '',
long,
long,
help))
return "\n".join(lines)
class CompletionData(object):
def __init__(self):
self.plugins = {}
self.global_options = []
self.commands = []
def all_command_aliases(self):
for c in self.commands:
for a in c.aliases:
yield a
class CommandData(object):
def __init__(self, name):
self.name = name
self.aliases = [name]
self.plugin = None
self.options = []
self.fixed_words = None
class PluginData(object):
def __init__(self, name, version=None):
if version is None:
try:
version = breezy.plugin.plugins()[name].__version__
except:
version = 'unknown'
self.name = name
self.version = version
def __str__(self):
if self.version == 'unknown':
return self.name
return '%s %s' % (self.name, self.version)
class OptionData(object):
def __init__(self, name):
self.name = name
self.registry_keys = None
self.error_messages = []
def __str__(self):
return self.name
def __cmp__(self, other):
return cmp(self.name, other.name)
def __lt__(self, other):
return self.name < other.name
class DataCollector(object):
def __init__(self, no_plugins=False, selected_plugins=None):
self.data = CompletionData()
self.user_aliases = {}
if no_plugins:
self.selected_plugins = set()
elif selected_plugins is None:
self.selected_plugins = None
else:
self.selected_plugins = {x.replace('-', '_')
for x in selected_plugins}
def collect(self):
self.global_options()
self.aliases()
self.commands()
return self.data
def global_options(self):
for name, item in option.Option.OPTIONS.items():
self.data.global_options.append(
('--' + item.name,
'-' + item.short_name() if item.short_name() else None,
item.help.rstrip()))
def aliases(self):
for alias, expansion in config.GlobalConfig().get_aliases().items():
for token in cmdline.split(expansion):
if not token.startswith("-"):
self.user_aliases.setdefault(token, set()).add(alias)
break
def commands(self):
for name in sorted(commands.all_command_names()):
self.command(name)
def command(self, name):
cmd = commands.get_cmd_object(name)
cmd_data = CommandData(name)
plugin_name = cmd.plugin_name()
if plugin_name is not None:
if (self.selected_plugins is not None and
plugin not in self.selected_plugins):
return None
plugin_data = self.data.plugins.get(plugin_name)
if plugin_data is None:
plugin_data = PluginData(plugin_name)
self.data.plugins[plugin_name] = plugin_data
cmd_data.plugin = plugin_data
self.data.commands.append(cmd_data)
# Find all aliases to the command; both cmd-defined and user-defined.
# We assume a user won't override one command with a different one,
# but will choose completely new names or add options to existing
# ones while maintaining the actual command name unchanged.
cmd_data.aliases.extend(cmd.aliases)
cmd_data.aliases.extend(sorted([useralias
for cmdalias in cmd_data.aliases
if cmdalias in self.user_aliases
for useralias in self.user_aliases[cmdalias]
if useralias not in cmd_data.aliases]))
opts = cmd.options()
for optname, opt in sorted(opts.items()):
cmd_data.options.extend(self.option(opt))
if 'help' == name or 'help' in cmd.aliases:
cmd_data.fixed_words = ('($cmds %s)' %
" ".join(sorted(help_topics.topic_registry.keys())))
return cmd_data
def option(self, opt):
optswitches = {}
parser = option.get_optparser([opt])
parser = self.wrap_parser(optswitches, parser)
optswitches.clear()
opt.add_option(parser, opt.short_name())
if isinstance(opt, option.RegistryOption) and opt.enum_switch:
enum_switch = '--%s' % opt.name
enum_data = optswitches.get(enum_switch)
if enum_data:
try:
enum_data.registry_keys = opt.registry.keys()
except ImportError as e:
enum_data.error_messages.append(
"ERROR getting registry keys for '--%s': %s"
% (opt.name, str(e).split('\n')[0]))
return sorted(optswitches.values())
def wrap_container(self, optswitches, parser):
def tweaked_add_option(*opts, **attrs):
for name in opts:
optswitches[name] = OptionData(name)
parser.add_option = tweaked_add_option
return parser
def wrap_parser(self, optswitches, parser):
orig_add_option_group = parser.add_option_group
def tweaked_add_option_group(*opts, **attrs):
return self.wrap_container(optswitches,
orig_add_option_group(*opts, **attrs))
parser.add_option_group = tweaked_add_option_group
return self.wrap_container(optswitches, parser)
def zsh_completion_function(out, function_name="_brz",
debug=False,
no_plugins=False, selected_plugins=None):
dc = DataCollector(no_plugins=no_plugins,
selected_plugins=selected_plugins)
data = dc.collect()
cg = ZshCodeGen(data, function_name=function_name, debug=debug)
res = cg.script()
out.write(res)
class cmd_zsh_completion(commands.Command):
__doc__ = """Generate a shell function for zsh command line completion.
This command generates a shell function which can be used by zsh to
automatically complete the currently typed command when the user presses
the completion key (usually tab).
Commonly used like this:
eval "`brz zsh -completion`"
"""
takes_options = [
option.Option("function-name", short_name="f", type=text_type, argname="name",
help="Name of the generated function (default: _brz)"),
option.Option("debug", type=None, hidden=True,
help="Enable shell code useful for debugging"),
option.ListOption("plugin", type=text_type, argname="name",
# param_name="selected_plugins", # doesn't work, bug #387117
help="Enable completions for the selected plugin"
+ " (default: all plugins)"),
]
def run(self, **kwargs):
if 'plugin' in kwargs:
# work around bug #387117 which prevents us from using param_name
if len(kwargs['plugin']) > 0:
kwargs['selected_plugins'] = kwargs['plugin']
del kwargs['plugin']
zsh_completion_function(sys.stdout, **kwargs)