cmdutils.register: annotation -> arg for flags

Instead of using a 'flag' key in the annotation dict, we now use a flags
argument to @cmdutils.register which is a {argument: flag} dict.

See #637.
This commit is contained in:
Florian Bruhin 2016-05-10 07:35:41 +02:00
parent 49fb981e7f
commit 5eff35ba30
4 changed files with 33 additions and 20 deletions

View File

@ -439,8 +439,6 @@ then automatically checked. Possible values:
- A python enum type: All members of the enum are possible values. - A python enum type: All members of the enum are possible values.
- A tuple of multiple types above: Any of these types are valid values, - A tuple of multiple types above: Any of these types are valid values,
e.g. `('foo', 'bar')` or `(int, 'foo')`. e.g. `('foo', 'bar')` or `(int, 'foo')`.
* `flag`: The flag to be used, as 1-char string (default: First char of the
long name).
The name of an argument will always be the parameter name, with any trailing The name of an argument will always be the parameter name, with any trailing
underscores stripped. underscores stripped.

View File

@ -575,9 +575,10 @@ class CommandDispatcher:
widget.keyReleaseEvent(release_evt) widget.keyReleaseEvent(release_evt)
@cmdutils.register(instance='command-dispatcher', hide=True, @cmdutils.register(instance='command-dispatcher', hide=True,
scope='window', count='count') scope='window', count='count',
def scroll_perc(self, perc: {'type': float}=None, flags={'horizontal': 'x'})
horizontal: {'flag': 'x'}=False, count=None): def scroll_perc(self, perc: {'type': float}=None, horizontal=False,
count=None):
"""Scroll to a specific percentage of the page. """Scroll to a specific percentage of the page.
The percentage can be given either as argument or as count. The percentage can be given either as argument or as count.

View File

@ -53,6 +53,7 @@ class Command:
win_id_arg: The name of the win_id parameter, or None. win_id_arg: The name of the win_id parameter, or None.
flags_with_args: A list of flags which take an argument. flags_with_args: A list of flags which take an argument.
no_cmd_split: If true, ';;' to split sub-commands is ignored. no_cmd_split: If true, ';;' to split sub-commands is ignored.
_flags: A mapping of argument names to alternative flags
_type_conv: A mapping of conversion functions for arguments. _type_conv: A mapping of conversion functions for arguments.
_needs_js: Whether the command needs javascript enabled _needs_js: Whether the command needs javascript enabled
_modes: The modes the command can be executed in. _modes: The modes the command can be executed in.
@ -66,12 +67,12 @@ class Command:
""" """
AnnotationInfo = collections.namedtuple( AnnotationInfo = collections.namedtuple(
'AnnotationInfo', ['type', 'flag', 'hide', 'metavar']) 'AnnotationInfo', ['type', 'hide', 'metavar'])
def __init__(self, *, handler, name, instance=None, maxsplit=None, def __init__(self, *, handler, name, instance=None, maxsplit=None,
hide=False, completion=None, modes=None, not_modes=None, hide=False, completion=None, modes=None, not_modes=None,
needs_js=False, debug=False, ignore_args=False, needs_js=False, debug=False, ignore_args=False,
deprecated=False, no_cmd_split=False, deprecated=False, no_cmd_split=False, flags=None,
star_args_optional=False, scope='global', count=None, star_args_optional=False, scope='global', count=None,
win_id=None): win_id=None):
# I really don't know how to solve this in a better way, I tried. # I really don't know how to solve this in a better way, I tried.
@ -100,6 +101,7 @@ class Command:
self._scope = scope self._scope = scope
self._needs_js = needs_js self._needs_js = needs_js
self._star_args_optional = star_args_optional self._star_args_optional = star_args_optional
self._flags = flags or {}
self.debug = debug self.debug = debug
self.ignore_args = ignore_args self.ignore_args = ignore_args
self.handler = handler self.handler = handler
@ -121,10 +123,18 @@ class Command:
self.desc = None self.desc = None
self.flags_with_args = [] self.flags_with_args = []
self._type_conv = {} self._type_conv = {}
count = self._inspect_func()
if self.completion is not None and len(self.completion) > count: args = self._inspect_func()
if self.completion is not None and len(self.completion) > len(args):
raise ValueError("Got {} completions, but only {} " raise ValueError("Got {} completions, but only {} "
"arguments!".format(len(self.completion), count)) "arguments!".format(len(self.completion),
len(args)))
for argname in self._flags:
if argname not in args:
raise ValueError("Got argument {} in flags param, but no such "
"argument exists for {}".format(
argname, self.name))
def _check_prerequisites(self, win_id): def _check_prerequisites(self, win_id):
"""Check if the command is permitted to run currently. """Check if the command is permitted to run currently.
@ -211,7 +221,6 @@ class Command:
""" """
signature = inspect.signature(self.handler) signature = inspect.signature(self.handler)
doc = inspect.getdoc(self.handler) doc = inspect.getdoc(self.handler)
arg_count = 0
if doc is not None: if doc is not None:
self.desc = doc.splitlines()[0].strip() self.desc = doc.splitlines()[0].strip()
else: else:
@ -233,7 +242,6 @@ class Command:
continue continue
if self._inspect_special_param(param): if self._inspect_special_param(param):
continue continue
arg_count += 1
typ = self._get_type(param, annotation_info) typ = self._get_type(param, annotation_info)
kwargs = self._param_to_argparse_kwargs(param, annotation_info) kwargs = self._param_to_argparse_kwargs(param, annotation_info)
args = self._param_to_argparse_args(param, annotation_info) args = self._param_to_argparse_args(param, annotation_info)
@ -244,7 +252,7 @@ class Command:
log.commands.vdebug('Adding arg {} of type {} -> {}'.format( log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
param.name, typ, callsig)) param.name, typ, callsig))
self.parser.add_argument(*args, **kwargs) self.parser.add_argument(*args, **kwargs)
return arg_count return signature.parameters.keys()
def _param_to_argparse_kwargs(self, param, annotation_info): def _param_to_argparse_kwargs(self, param, annotation_info):
"""Get argparse keyword arguments for a parameter. """Get argparse keyword arguments for a parameter.
@ -297,7 +305,7 @@ class Command:
""" """
args = [] args = []
name = arg_name(param.name) name = arg_name(param.name)
shortname = annotation_info.flag or name[0] shortname = self._flags.get(param.name, name[0])
if len(shortname) != 1: if len(shortname) != 1:
raise ValueError("Flag '{}' of parameter {} (command {}) must be " raise ValueError("Flag '{}' of parameter {} (command {}) must be "
"exactly 1 char!".format(shortname, name, "exactly 1 char!".format(shortname, name,
@ -325,14 +333,13 @@ class Command:
Return: Return:
An AnnotationInfo namedtuple. An AnnotationInfo namedtuple.
typ: The type to use for this argument. typ: The type to use for this argument.
flag: The short name/flag if overridden.
name: The long name if overridden. name: The long name if overridden.
""" """
info = {'type': None, 'flag': None, 'hide': False, 'metavar': None} info = {'type': None, 'hide': False, 'metavar': None}
if param.annotation is not inspect.Parameter.empty: if param.annotation is not inspect.Parameter.empty:
log.commands.vdebug("Parsing annotation {}".format( log.commands.vdebug("Parsing annotation {}".format(
param.annotation)) param.annotation))
for field in ('type', 'flag', 'name', 'hide', 'metavar'): for field in ('type', 'name', 'hide', 'metavar'):
if field in param.annotation: if field in param.annotation:
info[field] = param.annotation[field] info[field] = param.annotation[field]
return self.AnnotationInfo(**info) return self.AnnotationInfo(**info)

View File

@ -207,9 +207,9 @@ class TestRegister:
assert parser.parse_args(['-a']).arg assert parser.parse_args(['-a']).arg
assert not parser.parse_args([]).arg assert not parser.parse_args([]).arg
def test_flag_annotation(self): def test_flag_argument(self):
@cmdutils.register() @cmdutils.register(flags={'arg': 'b'})
def fun(arg: {'flag': 'b'}=False): def fun(arg=False):
"""Blah.""" """Blah."""
pass pass
parser = cmdutils.cmd_dict['fun'].parser parser = cmdutils.cmd_dict['fun'].parser
@ -217,3 +217,10 @@ class TestRegister:
assert parser.parse_args(['-b']).arg assert parser.parse_args(['-b']).arg
with pytest.raises(argparser.ArgumentParserError): with pytest.raises(argparser.ArgumentParserError):
parser.parse_args(['-a']) parser.parse_args(['-a'])
def test_unknown_argument_in_flags(self):
with pytest.raises(ValueError):
@cmdutils.register(flags={'foobar': 'f'})
def fun():
"""Blah."""
pass