2014-06-19 09:04:37 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
2014-02-06 14:01:23 +01:00
|
|
|
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
|
|
#
|
|
|
|
# This file is part of qutebrowser.
|
|
|
|
#
|
|
|
|
# qutebrowser 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 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# qutebrowser 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 qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
2014-01-29 15:30:19 +01:00
|
|
|
|
2014-02-17 12:23:52 +01:00
|
|
|
"""Contains the Command class, a skeleton for a command."""
|
|
|
|
|
2014-09-14 22:48:25 +02:00
|
|
|
import inspect
|
2014-09-15 07:42:21 +02:00
|
|
|
import collections
|
2014-09-14 22:48:25 +02:00
|
|
|
|
2014-05-23 16:11:55 +02:00
|
|
|
from PyQt5.QtWebKit import QWebSettings
|
2014-01-29 06:28:21 +01:00
|
|
|
|
2014-09-02 21:54:07 +02:00
|
|
|
from qutebrowser.commands import cmdexc, argparser
|
2014-10-26 22:08:13 +01:00
|
|
|
from qutebrowser.utils import log, utils, message, debug, docutils, objreg
|
2014-01-29 06:28:21 +01:00
|
|
|
|
2014-03-03 21:06:10 +01:00
|
|
|
|
2014-05-22 16:14:43 +02:00
|
|
|
class Command:
|
2014-02-07 20:21:50 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
"""Base skeleton for a command.
|
|
|
|
|
2014-03-03 06:09:23 +01:00
|
|
|
Attributes:
|
2014-03-04 07:02:45 +01:00
|
|
|
name: The main name of the command.
|
2014-12-11 20:25:54 +01:00
|
|
|
maxsplit: The maximum amount of splits to do for the commandline, or
|
|
|
|
None.
|
2014-03-04 07:02:45 +01:00
|
|
|
hide: Whether to hide the arguments or not.
|
|
|
|
desc: The description of the command.
|
|
|
|
handler: The handler function to call.
|
2014-03-21 20:01:13 +01:00
|
|
|
completion: Completions to use for arguments, as a list of strings.
|
2014-06-16 09:44:11 +02:00
|
|
|
debug: Whether this is a debugging command (only shown with --debug).
|
2014-09-02 21:54:07 +02:00
|
|
|
parser: The ArgumentParser to use to parse this command.
|
2014-10-14 07:59:42 +02:00
|
|
|
special_params: A dict with the names of the special parameters as
|
|
|
|
values.
|
2014-12-11 21:17:43 +01:00
|
|
|
flags_with_args: A list of flags which take an argument.
|
2014-09-24 22:17:53 +02:00
|
|
|
_type_conv: A mapping of conversion functions for arguments.
|
|
|
|
_name_conv: A mapping of argument names to parameter names.
|
|
|
|
_needs_js: Whether the command needs javascript enabled
|
|
|
|
_modes: The modes the command can be executed in.
|
|
|
|
_not_modes: The modes the command can not be executed in.
|
2014-10-09 06:33:24 +02:00
|
|
|
_count: The count set for the command.
|
2014-09-24 22:17:53 +02:00
|
|
|
_instance: The object to bind 'self' to.
|
2014-09-25 07:41:51 +02:00
|
|
|
_scope: The scope to get _instance for in the object registry.
|
2014-09-15 07:42:21 +02:00
|
|
|
|
|
|
|
Class attributes:
|
|
|
|
AnnotationInfo: Named tuple for info from an annotation.
|
2014-01-29 06:28:21 +01:00
|
|
|
"""
|
|
|
|
|
2014-09-15 07:42:21 +02:00
|
|
|
AnnotationInfo = collections.namedtuple('AnnotationInfo',
|
2014-10-20 17:20:39 +02:00
|
|
|
['kwargs', 'type', 'name', 'flag',
|
2014-10-09 06:33:24 +02:00
|
|
|
'special'])
|
|
|
|
|
2014-12-11 20:25:54 +01:00
|
|
|
def __init__(self, name, maxsplit, hide, instance, completion, modes,
|
2014-09-15 07:42:21 +02:00
|
|
|
not_modes, needs_js, is_debug, ignore_args,
|
2014-09-25 07:41:51 +02:00
|
|
|
handler, scope):
|
2014-04-25 11:21:00 +02:00
|
|
|
# I really don't know how to solve this in a better way, I tried.
|
2014-09-08 07:44:32 +02:00
|
|
|
# pylint: disable=too-many-arguments,too-many-locals
|
2014-02-28 17:00:25 +01:00
|
|
|
self.name = name
|
2014-12-11 20:25:54 +01:00
|
|
|
self.maxsplit = maxsplit
|
2014-02-28 17:00:25 +01:00
|
|
|
self.hide = hide
|
2014-09-24 22:17:53 +02:00
|
|
|
self._instance = instance
|
2014-03-21 20:01:13 +01:00
|
|
|
self.completion = completion
|
2014-09-24 22:17:53 +02:00
|
|
|
self._modes = modes
|
|
|
|
self._not_modes = not_modes
|
2014-09-25 07:41:51 +02:00
|
|
|
self._scope = scope
|
2014-09-24 22:17:53 +02:00
|
|
|
self._needs_js = needs_js
|
2014-09-03 10:47:27 +02:00
|
|
|
self.debug = is_debug
|
2014-09-15 07:42:21 +02:00
|
|
|
self.ignore_args = ignore_args
|
|
|
|
self.handler = handler
|
2014-09-23 04:22:51 +02:00
|
|
|
self.docparser = docutils.DocstringParser(handler)
|
2014-09-15 07:42:21 +02:00
|
|
|
self.parser = argparser.ArgumentParser(
|
|
|
|
name, description=self.docparser.short_desc,
|
|
|
|
epilog=self.docparser.long_desc)
|
|
|
|
self.parser.add_argument('-h', '--help', action=argparser.HelpAction,
|
|
|
|
default=argparser.SUPPRESS, nargs=0,
|
|
|
|
help=argparser.SUPPRESS)
|
|
|
|
self._check_func()
|
|
|
|
self.opt_args = collections.OrderedDict()
|
2014-09-15 08:16:19 +02:00
|
|
|
self.namespace = None
|
2014-09-24 22:17:53 +02:00
|
|
|
self._count = None
|
2014-09-15 07:42:21 +02:00
|
|
|
self.pos_args = []
|
2014-10-14 07:59:42 +02:00
|
|
|
self.special_params = {'count': None, 'win_id': None}
|
|
|
|
self.desc = None
|
2014-12-11 21:17:43 +01:00
|
|
|
self.flags_with_args = []
|
2014-10-14 07:59:42 +02:00
|
|
|
self._type_conv = {}
|
|
|
|
self._name_conv = {}
|
|
|
|
self._inspect_func()
|
2014-01-29 06:28:21 +01:00
|
|
|
|
2014-09-28 22:13:14 +02:00
|
|
|
def _check_prerequisites(self, win_id):
|
2014-09-02 21:54:07 +02:00
|
|
|
"""Check if the command is permitted to run currently.
|
2014-02-19 10:58:32 +01:00
|
|
|
|
2014-09-28 22:13:14 +02:00
|
|
|
Args:
|
|
|
|
win_id: The window ID the command is run in.
|
2014-01-29 06:28:21 +01:00
|
|
|
"""
|
2014-09-28 22:13:14 +02:00
|
|
|
mode_manager = objreg.get('mode-manager', scope='window',
|
|
|
|
window=win_id)
|
2014-12-28 00:01:27 +01:00
|
|
|
curmode = mode_manager.mode
|
2014-09-24 22:17:53 +02:00
|
|
|
if self._modes is not None and curmode not in self._modes:
|
|
|
|
mode_names = '/'.join(mode.name for mode in self._modes)
|
2014-08-26 19:10:14 +02:00
|
|
|
raise cmdexc.PrerequisitesError(
|
|
|
|
"{}: This command is only allowed in {} mode.".format(
|
|
|
|
self.name, mode_names))
|
2014-09-24 22:17:53 +02:00
|
|
|
elif self._not_modes is not None and curmode in self._not_modes:
|
|
|
|
mode_names = '/'.join(mode.name for mode in self._not_modes)
|
2014-08-26 19:10:14 +02:00
|
|
|
raise cmdexc.PrerequisitesError(
|
|
|
|
"{}: This command is not allowed in {} mode.".format(
|
|
|
|
self.name, mode_names))
|
2014-09-24 22:17:53 +02:00
|
|
|
if self._needs_js and not QWebSettings.globalSettings().testAttribute(
|
2014-04-29 23:06:07 +02:00
|
|
|
QWebSettings.JavascriptEnabled):
|
2014-08-26 19:10:14 +02:00
|
|
|
raise cmdexc.PrerequisitesError(
|
|
|
|
"{}: This command needs javascript enabled.".format(self.name))
|
2014-01-29 06:28:21 +01:00
|
|
|
|
2014-09-15 07:42:21 +02:00
|
|
|
def _check_func(self):
|
|
|
|
"""Make sure the function parameters don't violate any rules."""
|
|
|
|
signature = inspect.signature(self.handler)
|
2014-09-24 22:17:53 +02:00
|
|
|
if 'self' in signature.parameters and self._instance is None:
|
2014-09-15 07:42:21 +02:00
|
|
|
raise TypeError("{} is a class method, but instance was not "
|
|
|
|
"given!".format(self.name[0]))
|
2014-09-24 22:17:53 +02:00
|
|
|
elif 'self' not in signature.parameters and self._instance is not None:
|
2014-09-15 07:42:21 +02:00
|
|
|
raise TypeError("{} is not a class method, but instance was "
|
|
|
|
"given!".format(self.name[0]))
|
|
|
|
elif inspect.getfullargspec(self.handler).varkw is not None:
|
|
|
|
raise TypeError("{}: functions with varkw arguments are not "
|
|
|
|
"supported!".format(self.name[0]))
|
|
|
|
|
|
|
|
def _get_typeconv(self, param, typ):
|
|
|
|
"""Get a dict with a type conversion for the parameter.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
param: The inspect.Parameter to handle.
|
|
|
|
typ: The type of the parameter.
|
|
|
|
"""
|
|
|
|
type_conv = {}
|
|
|
|
if utils.is_enum(typ):
|
|
|
|
type_conv[param.name] = argparser.enum_getter(typ)
|
|
|
|
elif isinstance(typ, tuple):
|
|
|
|
if param.default is not inspect.Parameter.empty:
|
|
|
|
typ = typ + (type(param.default),)
|
|
|
|
type_conv[param.name] = argparser.multitype_conv(typ)
|
|
|
|
return type_conv
|
|
|
|
|
|
|
|
def _get_nameconv(self, param, annotation_info):
|
|
|
|
"""Get a dict with a name conversion for the paraeter.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
param: The inspect.Parameter to handle.
|
|
|
|
annotation_info: The AnnotationInfo tuple for the parameter.
|
|
|
|
"""
|
|
|
|
d = {}
|
|
|
|
if annotation_info.name is not None:
|
|
|
|
d[param.name] = annotation_info.name
|
|
|
|
return d
|
|
|
|
|
2014-10-14 07:59:42 +02:00
|
|
|
def _inspect_special_param(self, param, annotation_info):
|
|
|
|
"""Check if the given parameter is a special one.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
param: The inspect.Parameter to handle.
|
|
|
|
annotation_info: The AnnotationInfo tuple for the parameter.
|
|
|
|
|
|
|
|
Return:
|
|
|
|
True if the parameter is special, False otherwise.
|
|
|
|
"""
|
|
|
|
special = annotation_info.special
|
|
|
|
if special == 'count':
|
|
|
|
if self.special_params['count'] is not None:
|
|
|
|
raise ValueError("Registered multiple parameters ({}/{}) as "
|
|
|
|
"count!".format(self.special_params['count'],
|
|
|
|
param.name))
|
|
|
|
if param.default is inspect.Parameter.empty:
|
|
|
|
raise TypeError("{}: handler has count parameter "
|
|
|
|
"without default!".format(self.name))
|
|
|
|
self.special_params['count'] = param.name
|
|
|
|
return True
|
|
|
|
elif special == 'win_id':
|
|
|
|
if self.special_params['win_id'] is not None:
|
|
|
|
raise ValueError("Registered multiple parameters ({}/{}) as "
|
|
|
|
"win_id!".format(
|
|
|
|
self.special_params['win_id'],
|
|
|
|
param.name))
|
|
|
|
self.special_params['win_id'] = param.name
|
|
|
|
return True
|
|
|
|
elif special is None:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
raise ValueError("{}: Invalid value '{}' for 'special' "
|
|
|
|
"annotation!".format(self.name, special))
|
|
|
|
|
2014-09-15 07:42:21 +02:00
|
|
|
def _inspect_func(self):
|
|
|
|
"""Inspect the function to get useful informations from it.
|
|
|
|
|
2014-10-14 07:59:42 +02:00
|
|
|
Sets instance attributes (desc, type_conv, name_conv) based on the
|
|
|
|
informations.
|
2014-09-15 07:42:21 +02:00
|
|
|
"""
|
|
|
|
signature = inspect.signature(self.handler)
|
|
|
|
doc = inspect.getdoc(self.handler)
|
|
|
|
if doc is not None:
|
2014-10-14 07:59:42 +02:00
|
|
|
self.desc = doc.splitlines()[0].strip()
|
2014-09-15 07:42:21 +02:00
|
|
|
else:
|
2014-10-14 07:59:42 +02:00
|
|
|
self.desc = ""
|
2014-09-15 07:42:21 +02:00
|
|
|
if not self.ignore_args:
|
|
|
|
for param in signature.parameters.values():
|
|
|
|
annotation_info = self._parse_annotation(param)
|
2014-10-09 06:33:24 +02:00
|
|
|
if param.name == 'self':
|
|
|
|
continue
|
2014-10-14 07:59:42 +02:00
|
|
|
if self._inspect_special_param(param, annotation_info):
|
2014-10-09 06:33:24 +02:00
|
|
|
continue
|
2014-09-15 07:42:21 +02:00
|
|
|
typ = self._get_type(param, annotation_info)
|
2014-10-26 22:08:13 +01:00
|
|
|
kwargs = self._param_to_argparse_kwargs(param, annotation_info)
|
|
|
|
args = self._param_to_argparse_args(param, annotation_info)
|
2014-10-14 07:59:42 +02:00
|
|
|
self._type_conv.update(self._get_typeconv(param, typ))
|
|
|
|
self._name_conv.update(
|
|
|
|
self._get_nameconv(param, annotation_info))
|
2014-09-15 07:42:21 +02:00
|
|
|
callsig = debug.format_call(
|
|
|
|
self.parser.add_argument, args, kwargs,
|
|
|
|
full=False)
|
|
|
|
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
|
|
|
param.name, typ, callsig))
|
|
|
|
self.parser.add_argument(*args, **kwargs)
|
|
|
|
|
2014-10-26 22:08:13 +01:00
|
|
|
def _param_to_argparse_kwargs(self, param, annotation_info):
|
|
|
|
"""Get argparse keyword arguments for a parameter.
|
2014-09-15 07:42:21 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
param: The inspect.Parameter object to get the args for.
|
|
|
|
annotation_info: An AnnotationInfo tuple for the parameter.
|
|
|
|
|
2014-10-26 22:08:13 +01:00
|
|
|
Return:
|
|
|
|
A kwargs dict.
|
|
|
|
"""
|
2014-09-15 07:42:21 +02:00
|
|
|
kwargs = {}
|
|
|
|
typ = self._get_type(param, annotation_info)
|
|
|
|
|
|
|
|
try:
|
|
|
|
kwargs['help'] = self.docparser.arg_descs[param.name]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if isinstance(typ, tuple):
|
|
|
|
pass
|
|
|
|
elif utils.is_enum(typ):
|
|
|
|
kwargs['choices'] = [e.name.replace('_', '-') for e in typ]
|
|
|
|
kwargs['metavar'] = param.name
|
|
|
|
elif typ is bool:
|
|
|
|
kwargs['action'] = 'store_true'
|
|
|
|
elif typ is not None:
|
|
|
|
kwargs['type'] = typ
|
|
|
|
|
|
|
|
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
|
|
|
kwargs['nargs'] = '+'
|
|
|
|
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
|
|
|
kwargs['default'] = param.default
|
|
|
|
elif typ is not bool and param.default is not inspect.Parameter.empty:
|
|
|
|
kwargs['default'] = param.default
|
|
|
|
kwargs['nargs'] = '?'
|
2014-10-26 22:08:13 +01:00
|
|
|
kwargs.update(annotation_info.kwargs)
|
|
|
|
return kwargs
|
2014-09-15 07:42:21 +02:00
|
|
|
|
2014-10-26 22:08:13 +01:00
|
|
|
def _param_to_argparse_args(self, param, annotation_info):
|
|
|
|
"""Get argparse positional arguments for a parameter.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
param: The inspect.Parameter object to get the args for.
|
|
|
|
annotation_info: An AnnotationInfo tuple for the parameter.
|
|
|
|
|
|
|
|
Return:
|
|
|
|
A list of args.
|
|
|
|
"""
|
2014-09-15 07:42:21 +02:00
|
|
|
args = []
|
|
|
|
name = annotation_info.name or param.name
|
|
|
|
shortname = annotation_info.flag or param.name[0]
|
2014-10-20 20:27:19 +02:00
|
|
|
if len(shortname) != 1:
|
|
|
|
raise ValueError("Flag '{}' of parameter {} (command {}) must be "
|
|
|
|
"exactly 1 char!".format(shortname, name,
|
|
|
|
self.name))
|
2014-10-26 22:08:13 +01:00
|
|
|
typ = self._get_type(param, annotation_info)
|
|
|
|
if typ is bool or param.kind == inspect.Parameter.KEYWORD_ONLY:
|
2014-09-15 07:42:21 +02:00
|
|
|
long_flag = '--{}'.format(name)
|
|
|
|
short_flag = '-{}'.format(shortname)
|
|
|
|
args.append(long_flag)
|
|
|
|
args.append(short_flag)
|
|
|
|
self.opt_args[param.name] = long_flag, short_flag
|
2014-12-11 21:17:43 +01:00
|
|
|
if param.kind == inspect.Parameter.KEYWORD_ONLY:
|
|
|
|
self.flags_with_args.append(param.name)
|
2014-10-26 22:08:13 +01:00
|
|
|
else:
|
2014-09-15 07:42:21 +02:00
|
|
|
args.append(name)
|
|
|
|
self.pos_args.append((param.name, name))
|
2014-10-26 22:08:13 +01:00
|
|
|
return args
|
2014-09-15 07:42:21 +02:00
|
|
|
|
|
|
|
def _parse_annotation(self, param):
|
|
|
|
"""Get argparse arguments and type from a parameter annotation.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
param: A inspect.Parameter instance.
|
|
|
|
|
|
|
|
Return:
|
|
|
|
An AnnotationInfo namedtuple.
|
|
|
|
kwargs: A dict of keyword args to add to the
|
2014-10-09 06:33:24 +02:00
|
|
|
argparse.ArgumentParser.add_argument call.
|
2014-09-15 07:42:21 +02:00
|
|
|
typ: The type to use for this argument.
|
|
|
|
flag: The short name/flag if overridden.
|
|
|
|
name: The long name if overridden.
|
|
|
|
"""
|
2014-10-20 17:20:39 +02:00
|
|
|
info = {'kwargs': {}, 'type': None, 'flag': None, 'name': None,
|
2014-10-09 06:33:24 +02:00
|
|
|
'special': None}
|
2014-09-15 07:42:21 +02:00
|
|
|
if param.annotation is not inspect.Parameter.empty:
|
|
|
|
log.commands.vdebug("Parsing annotation {}".format(
|
|
|
|
param.annotation))
|
2014-10-20 17:20:39 +02:00
|
|
|
for field in ('type', 'flag', 'name', 'special'):
|
|
|
|
if field in param.annotation:
|
|
|
|
info[field] = param.annotation[field]
|
2014-10-20 20:27:33 +02:00
|
|
|
if 'nargs' in param.annotation:
|
|
|
|
info['kwargs'] = {'nargs': param.annotation['nargs']}
|
2014-09-15 07:42:21 +02:00
|
|
|
return self.AnnotationInfo(**info)
|
|
|
|
|
|
|
|
def _get_type(self, param, annotation_info):
|
|
|
|
"""Get the type of an argument from its default value or annotation.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
param: The inspect.Parameter to look at.
|
|
|
|
annotation_info: An AnnotationInfo tuple which overrides the type.
|
|
|
|
"""
|
2014-10-20 17:20:39 +02:00
|
|
|
if annotation_info.type is not None:
|
|
|
|
return annotation_info.type
|
2014-09-15 07:42:21 +02:00
|
|
|
elif param.default is None or param.default is inspect.Parameter.empty:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return type(param.default)
|
|
|
|
|
2014-09-28 22:13:14 +02:00
|
|
|
def _get_self_arg(self, win_id, param, args):
|
2014-09-15 08:16:19 +02:00
|
|
|
"""Get the self argument for a function call.
|
2014-05-14 18:00:40 +02:00
|
|
|
|
2014-09-15 08:16:19 +02:00
|
|
|
Arguments:
|
2014-09-28 22:13:14 +02:00
|
|
|
win_id: The window id this command should be executed in.
|
2014-09-15 08:16:19 +02:00
|
|
|
param: The count parameter.
|
|
|
|
args: The positional argument list. Gets modified directly.
|
|
|
|
"""
|
|
|
|
assert param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
2014-09-28 22:13:14 +02:00
|
|
|
if self._scope is not 'window':
|
|
|
|
win_id = None
|
|
|
|
obj = objreg.get(self._instance, scope=self._scope,
|
|
|
|
window=win_id)
|
2014-09-15 08:16:19 +02:00
|
|
|
args.append(obj)
|
|
|
|
|
|
|
|
def _get_count_arg(self, param, args, kwargs):
|
|
|
|
"""Add the count argument to a function call.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
param: The count parameter.
|
|
|
|
args: The positional argument list. Gets modified directly.
|
|
|
|
kwargs: The keyword argument dict. Gets modified directly.
|
|
|
|
"""
|
|
|
|
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
2014-09-24 22:17:53 +02:00
|
|
|
if self._count is not None:
|
|
|
|
args.append(self._count)
|
2014-09-15 08:16:19 +02:00
|
|
|
else:
|
|
|
|
args.append(param.default)
|
|
|
|
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
2014-09-24 22:17:53 +02:00
|
|
|
if self._count is not None:
|
2014-10-09 06:33:24 +02:00
|
|
|
kwargs[param.name] = self._count
|
2014-09-15 08:16:19 +02:00
|
|
|
else:
|
|
|
|
raise TypeError("{}: invalid parameter type {} for argument "
|
2014-10-09 06:33:24 +02:00
|
|
|
"{!r}!".format(self.name, param.kind, param.name))
|
2014-09-15 08:16:19 +02:00
|
|
|
|
2014-09-29 19:14:11 +02:00
|
|
|
def _get_win_id_arg(self, win_id, param, args, kwargs):
|
|
|
|
"""Add the win_id argument to a function call.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
win_id: The window ID to add.
|
|
|
|
param: The count parameter.
|
|
|
|
args: The positional argument list. Gets modified directly.
|
|
|
|
kwargs: The keyword argument dict. Gets modified directly.
|
|
|
|
"""
|
|
|
|
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
|
|
|
args.append(win_id)
|
|
|
|
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
2014-10-09 06:33:24 +02:00
|
|
|
kwargs[param.name] = win_id
|
2014-09-29 19:14:11 +02:00
|
|
|
else:
|
|
|
|
raise TypeError("{}: invalid parameter type {} for argument "
|
2014-10-09 06:33:24 +02:00
|
|
|
"{!r}!".format(self.name, param.kind, param.name))
|
2014-09-29 19:14:11 +02:00
|
|
|
|
2014-09-15 08:16:19 +02:00
|
|
|
def _get_param_name_and_value(self, param):
|
|
|
|
"""Get the converted name and value for an inspect.Parameter."""
|
2014-09-24 22:17:53 +02:00
|
|
|
name = self._name_conv.get(param.name, param.name)
|
2014-09-15 08:16:19 +02:00
|
|
|
value = getattr(self.namespace, name)
|
2014-09-24 22:17:53 +02:00
|
|
|
if param.name in self._type_conv:
|
2014-09-15 08:16:19 +02:00
|
|
|
# We convert enum types after getting the values from
|
|
|
|
# argparse, because argparse's choices argument is
|
|
|
|
# processed after type conversation, which is not what we
|
|
|
|
# want.
|
2014-09-24 22:17:53 +02:00
|
|
|
value = self._type_conv[param.name](value)
|
2014-09-15 08:16:19 +02:00
|
|
|
return name, value
|
|
|
|
|
2014-10-06 19:53:50 +02:00
|
|
|
def _get_call_args(self, win_id): # noqa
|
2014-09-15 08:16:19 +02:00
|
|
|
"""Get arguments for a function call.
|
2014-09-14 23:56:19 +02:00
|
|
|
|
2014-09-28 22:13:14 +02:00
|
|
|
Args:
|
|
|
|
win_id: The window id this command should be executed in.
|
|
|
|
|
2014-09-14 23:56:19 +02:00
|
|
|
Return:
|
|
|
|
An (args, kwargs) tuple.
|
2014-01-29 06:28:21 +01:00
|
|
|
"""
|
|
|
|
|
2014-09-14 23:56:19 +02:00
|
|
|
args = []
|
2014-05-22 16:14:43 +02:00
|
|
|
kwargs = {}
|
2014-09-15 08:16:19 +02:00
|
|
|
signature = inspect.signature(self.handler)
|
2014-09-14 22:48:25 +02:00
|
|
|
|
2014-09-22 22:46:03 +02:00
|
|
|
if self.ignore_args:
|
2014-09-24 22:17:53 +02:00
|
|
|
if self._instance is not None:
|
2014-09-22 22:46:03 +02:00
|
|
|
param = list(signature.parameters.values())[0]
|
2014-09-28 22:13:14 +02:00
|
|
|
self._get_self_arg(win_id, param, args)
|
2014-09-22 22:46:03 +02:00
|
|
|
return args, kwargs
|
|
|
|
|
2014-09-14 22:48:25 +02:00
|
|
|
for i, param in enumerate(signature.parameters.values()):
|
2014-09-24 22:17:53 +02:00
|
|
|
if i == 0 and self._instance is not None:
|
2014-09-14 22:48:25 +02:00
|
|
|
# Special case for 'self'.
|
2014-09-28 22:13:14 +02:00
|
|
|
self._get_self_arg(win_id, param, args)
|
2014-09-14 22:48:25 +02:00
|
|
|
continue
|
2014-10-14 07:59:42 +02:00
|
|
|
elif param.name == self.special_params['count']:
|
2014-10-09 06:33:24 +02:00
|
|
|
# Special case for count parameter.
|
2014-09-15 08:16:19 +02:00
|
|
|
self._get_count_arg(param, args, kwargs)
|
2014-09-14 22:48:25 +02:00
|
|
|
continue
|
2014-10-14 07:59:42 +02:00
|
|
|
elif param.name == self.special_params['win_id']:
|
2014-10-09 06:33:24 +02:00
|
|
|
# Special case for win_id parameter.
|
2014-09-29 19:14:11 +02:00
|
|
|
self._get_win_id_arg(win_id, param, args, kwargs)
|
|
|
|
continue
|
2014-09-15 08:16:19 +02:00
|
|
|
name, value = self._get_param_name_and_value(param)
|
2014-09-14 22:48:25 +02:00
|
|
|
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
2014-09-14 23:56:19 +02:00
|
|
|
args.append(value)
|
2014-09-14 22:48:25 +02:00
|
|
|
elif param.kind == inspect.Parameter.VAR_POSITIONAL:
|
2014-09-14 23:16:54 +02:00
|
|
|
if value is not None:
|
2014-09-14 23:56:19 +02:00
|
|
|
args += value
|
2014-09-14 22:48:25 +02:00
|
|
|
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
2014-09-15 06:20:33 +02:00
|
|
|
kwargs[name] = value
|
2014-05-22 16:14:43 +02:00
|
|
|
else:
|
2014-09-14 22:48:25 +02:00
|
|
|
raise TypeError("{}: Invalid parameter type {} for argument "
|
|
|
|
"'{}'!".format(
|
|
|
|
self.name, param.kind, param.name))
|
2014-09-14 23:56:19 +02:00
|
|
|
return args, kwargs
|
|
|
|
|
2014-09-28 22:13:14 +02:00
|
|
|
def run(self, win_id, args=None, count=None):
|
2014-09-14 23:56:19 +02:00
|
|
|
"""Run the command.
|
|
|
|
|
|
|
|
Note we don't catch CommandError here as it might happen async.
|
|
|
|
|
|
|
|
Args:
|
2014-09-28 22:13:14 +02:00
|
|
|
win_id: The window ID the command is run in.
|
2014-09-14 23:56:19 +02:00
|
|
|
args: Arguments to the command.
|
|
|
|
count: Command repetition count.
|
|
|
|
"""
|
|
|
|
dbgout = ["command called:", self.name]
|
|
|
|
if args:
|
|
|
|
dbgout.append(str(args))
|
|
|
|
if count is not None:
|
|
|
|
dbgout.append("(count={})".format(count))
|
|
|
|
log.commands.debug(' '.join(dbgout))
|
|
|
|
try:
|
2014-09-15 08:16:19 +02:00
|
|
|
self.namespace = self.parser.parse_args(args)
|
2014-09-14 23:56:19 +02:00
|
|
|
except argparser.ArgumentParserError as e:
|
2014-09-28 22:13:14 +02:00
|
|
|
message.error(win_id, '{}: {}'.format(self.name, e))
|
2014-09-14 23:56:19 +02:00
|
|
|
return
|
|
|
|
except argparser.ArgumentParserExit as e:
|
|
|
|
log.commands.debug("argparser exited with status {}: {}".format(
|
|
|
|
e.status, e))
|
|
|
|
return
|
2014-09-24 22:17:53 +02:00
|
|
|
self._count = count
|
2014-09-28 22:13:14 +02:00
|
|
|
posargs, kwargs = self._get_call_args(win_id)
|
|
|
|
self._check_prerequisites(win_id)
|
2014-09-03 08:57:21 +02:00
|
|
|
log.commands.debug('Calling {}'.format(
|
|
|
|
debug.format_call(self.handler, posargs, kwargs)))
|
2014-09-02 21:54:07 +02:00
|
|
|
self.handler(*posargs, **kwargs)
|