1#  Copyright 2008-2015 Nokia Networks
2#  Copyright 2016-     Robot Framework Foundation
3#
4#  Licensed under the Apache License, Version 2.0 (the "License");
5#  you may not use this file except in compliance with the License.
6#  You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10#  Unless required by applicable law or agreed to in writing, software
11#  distributed under the License is distributed on an "AS IS" BASIS,
12#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13#  See the License for the specific language governing permissions and
14#  limitations under the License.
15
16from robot.errors import DataError
17from robot.utils import plural_or_not, seq2str
18from robot.variables import is_list_var
19
20
21class ArgumentValidator(object):
22
23    def __init__(self, argspec):
24        """:type argspec: :py:class:`robot.running.arguments.ArgumentSpec`"""
25        self._argspec = argspec
26
27    def validate(self, positional, named, dryrun=False):
28        if dryrun and any(is_list_var(arg) for arg in positional):
29            return
30        named = set(name for name, value in named)
31        self._validate_no_multiple_values(positional, named, self._argspec)
32        self._validate_positional_limits(positional, named, self._argspec)
33        self._validate_no_mandatory_missing(positional, named, self._argspec)
34        self._validate_no_named_only_missing(named, self._argspec)
35        self._validate_no_extra_named(named, self._argspec)
36
37    def _validate_positional_limits(self, positional, named, spec):
38        count = len(positional) + self._named_positionals(named, spec)
39        if not spec.minargs <= count <= spec.maxargs:
40            self._raise_wrong_count(count, spec)
41
42    def _named_positionals(self, named, spec):
43        if not spec.supports_named:
44            return 0
45        return sum(1 for n in named if n in spec.positional)
46
47    def _raise_wrong_count(self, count, spec):
48        minend = plural_or_not(spec.minargs)
49        if spec.minargs == spec.maxargs:
50            expected = '%d argument%s' % (spec.minargs, minend)
51        elif not spec.varargs:
52            expected = '%d to %d arguments' % (spec.minargs, spec.maxargs)
53        else:
54            expected = 'at least %d argument%s' % (spec.minargs, minend)
55        if spec.kwargs or spec.kwonlyargs:
56            expected = expected.replace('argument', 'non-named argument')
57        raise DataError("%s '%s' expected %s, got %d."
58                        % (spec.type, spec.name, expected, count))
59
60    def _validate_no_multiple_values(self, positional, named, spec):
61        if named and spec.supports_named:
62            for name in spec.positional[:len(positional)]:
63                if name in named:
64                    raise DataError("%s '%s' got multiple values for argument "
65                                    "'%s'." % (spec.type, spec.name, name))
66
67    def _validate_no_mandatory_missing(self, positional, named, spec):
68        for name in spec.positional[len(positional):spec.minargs]:
69            if name not in named:
70                raise DataError("%s '%s' missing value for argument '%s'."
71                                % (spec.type, spec.name, name))
72
73    def _validate_no_named_only_missing(self, named, spec):
74        defined = set(named) | set(spec.defaults)
75        missing = [arg for arg in spec.kwonlyargs if arg not in defined]
76        if missing:
77            raise DataError("%s '%s' missing named-only argument%s %s."
78                            % (spec.type, spec.name, plural_or_not(missing),
79                               seq2str(sorted(missing))))
80
81    def _validate_no_extra_named(self, named, spec):
82        if not spec.kwargs:
83            extra = set(named) - set(spec.positional) - set(spec.kwonlyargs)
84            if extra:
85                raise DataError("%s '%s' got unexpected named argument%s %s."
86                                % (spec.type, spec.name, plural_or_not(extra),
87                                   seq2str(sorted(extra))))
88