From 5eff35ba30878b070a9c6d73009f769545a5c114 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 10 May 2016 07:35:41 +0200 Subject: [PATCH] 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. --- CONTRIBUTING.asciidoc | 2 -- qutebrowser/browser/commands.py | 7 ++++--- qutebrowser/commands/command.py | 31 +++++++++++++++++----------- tests/unit/commands/test_cmdutils.py | 13 +++++++++--- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc index 1c3831300..ef1c3e85f 100644 --- a/CONTRIBUTING.asciidoc +++ b/CONTRIBUTING.asciidoc @@ -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. diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 84955191d..865990693 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -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. diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index 4fdcfc9e6..847a7310f 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -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) diff --git a/tests/unit/commands/test_cmdutils.py b/tests/unit/commands/test_cmdutils.py index 5fa0c8c8f..1adb8904b 100644 --- a/tests/unit/commands/test_cmdutils.py +++ b/tests/unit/commands/test_cmdutils.py @@ -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