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 tuple of multiple types above: Any of these types are valid values,
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
underscores stripped.

View File

@ -575,9 +575,10 @@ class CommandDispatcher:
widget.keyReleaseEvent(release_evt)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window', count='count')
def scroll_perc(self, perc: {'type': float}=None,
horizontal: {'flag': 'x'}=False, count=None):
scope='window', count='count',
flags={'horizontal': 'x'})
def scroll_perc(self, perc: {'type': float}=None, horizontal=False,
count=None):
"""Scroll to a specific percentage of the page.
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.
flags_with_args: A list of flags which take an argument.
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.
_needs_js: Whether the command needs javascript enabled
_modes: The modes the command can be executed in.
@ -66,12 +67,12 @@ class Command:
"""
AnnotationInfo = collections.namedtuple(
'AnnotationInfo', ['type', 'flag', 'hide', 'metavar'])
'AnnotationInfo', ['type', 'hide', 'metavar'])
def __init__(self, *, handler, name, instance=None, maxsplit=None,
hide=False, completion=None, modes=None, not_modes=None,
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,
win_id=None):
# 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._needs_js = needs_js
self._star_args_optional = star_args_optional
self._flags = flags or {}
self.debug = debug
self.ignore_args = ignore_args
self.handler = handler
@ -121,10 +123,18 @@ class Command:
self.desc = None
self.flags_with_args = []
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 {} "
"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):
"""Check if the command is permitted to run currently.
@ -211,7 +221,6 @@ class Command:
"""
signature = inspect.signature(self.handler)
doc = inspect.getdoc(self.handler)
arg_count = 0
if doc is not None:
self.desc = doc.splitlines()[0].strip()
else:
@ -233,7 +242,6 @@ class Command:
continue
if self._inspect_special_param(param):
continue
arg_count += 1
typ = self._get_type(param, annotation_info)
kwargs = self._param_to_argparse_kwargs(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(
param.name, typ, callsig))
self.parser.add_argument(*args, **kwargs)
return arg_count
return signature.parameters.keys()
def _param_to_argparse_kwargs(self, param, annotation_info):
"""Get argparse keyword arguments for a parameter.
@ -297,7 +305,7 @@ class Command:
"""
args = []
name = arg_name(param.name)
shortname = annotation_info.flag or name[0]
shortname = self._flags.get(param.name, name[0])
if len(shortname) != 1:
raise ValueError("Flag '{}' of parameter {} (command {}) must be "
"exactly 1 char!".format(shortname, name,
@ -325,14 +333,13 @@ class Command:
Return:
An AnnotationInfo namedtuple.
typ: The type to use for this argument.
flag: The short name/flag 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:
log.commands.vdebug("Parsing annotation {}".format(
param.annotation))
for field in ('type', 'flag', 'name', 'hide', 'metavar'):
for field in ('type', 'name', 'hide', 'metavar'):
if field in param.annotation:
info[field] = param.annotation[field]
return self.AnnotationInfo(**info)

View File

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