Coverage for src/robotide/validators/__init__.py: 72%
178 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
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.
16import builtins 1ab
17import os 1ab
18import wx 1ab
20from .. import robotapi, utils 1ab
21from ..widgets import RIDEDialog 1ab
23_ = wx.GetTranslation # To keep linter/code analyser happy 1ab
24builtins.__dict__['_'] = wx.GetTranslation 1ab
27class _AbstractValidator(wx.Validator): 1ab
28 """Implements methods to keep wxPython happy and some helper methods."""
30 def Clone(self): 1ab
31 return self.__class__() 1qprstuvwxy
33 def TransferFromWindow(self): 1ab
34 return True
36 def TransferToWindow(self): 1ab
37 return True 1zp
39 def Validate(self, win): 1ab
40 value = self.Window.Value
41 error = self._validate(value)
42 if error:
43 self._show_error(error)
44 return False
45 return True
47 def _validate(self, value): 1ab
48 return NotImplemented
50 def _show_error(self, message, title="Validation Error"): 1ab
51 message_box = RIDEDialog(title=title, message=message, style=wx.ICON_ERROR)
52 ret = message_box.execute()
53 self._set_focus_to_text_control(self.Window)
54 return ret
56 @staticmethod 1ab
57 def _set_focus_to_text_control(ctrl): 1ab
58 ctrl.SetFocus()
59 ctrl.SelectAll()
62class TimeoutValidator(_AbstractValidator): 1ab
64 def _validate(self, value): 1ab
65 time_tokens = utils.split_value(value) 1ohi
66 if not time_tokens: 66 ↛ 67line 66 didn't jump to line 67 because the condition on line 66 was never true1ohi
67 return None
68 timestr = time_tokens[0] 1ohi
69 try: 1ohi
70 secs = utils.timestr_to_secs(timestr) 1ohi
71 if secs <= 0: 1hi
72 raise ValueError("Timestring must be over zero") 1h
73 time_tokens[0] = utils.secs_to_timestr(secs) 1i
74 except ValueError as err: 1oh
75 if '${' not in timestr: 75 ↛ 77line 75 didn't jump to line 77 because the condition on line 75 was always true1oh
76 return str(err) 1oh
77 self._set_window_value(utils.join_value(time_tokens)) 1i
78 return None 1i
80 def _set_window_value(self, value): 1ab
81 self.Window.SetValue(value)
84class ArgumentTypes(object): 1ab
85 NAMED = 1 1ab
86 LIST = 2 1ab
87 DICT = 3 1ab
88 SCALAR = 4 1ab
89 DEFAULT = 5 1ab
92class ArgumentsValidator(_AbstractValidator): 1ab
94 def _validate(self, args_str): 1ab
95 try: 1Agdefc
96 types = [self._get_type(arg) 1Agdefc
97 for arg in utils.split_value(args_str)]
98 except ValueError as e: 1g
99 return "Invalid argument syntax '%s'" % str(e) # DEBUG was arg 1g
100 return self._validate_argument_order(types) 1Adefc
102 @staticmethod 1ab
103 def _get_type(arg): 1ab
104 if '=' in arg and len(arg.split("=")) > 1: 1gdefc
105 default_arg = arg.split("=")[0] 1gfc
106 else:
107 default_arg = False 1gdefc
108 if robotapi.is_scalar_var(default_arg): 1gdefc
109 return ArgumentTypes.DEFAULT 1fc
110 elif robotapi.is_scalar_var(arg): 1gdefc
111 return ArgumentTypes.SCALAR 1gdfc
112 elif arg == '@{}': 1gdec
113 return ArgumentTypes.NAMED 1dc
114 elif robotapi.is_list_var(arg): 1gdec
115 return ArgumentTypes.LIST 1ec
116 elif robotapi.is_dict_var(arg): 1gdec
117 return ArgumentTypes.DICT 1dec
118 else:
119 raise ValueError(arg) 1g
121 @staticmethod 1ab
122 def _validate_argument_order(types): 1ab
123 if types: 1Adefc
124 prev = types[0] 1defc
125 active_named_only = False 1defc
126 dict_in_list = False 1defc
127 for idx, t in enumerate(types): 1defc
128 if prev == ArgumentTypes.DICT: 1defc
129 dict_in_list = True 1ec
130 if t == ArgumentTypes.NAMED: 1defc
131 active_named_only = True 1dc
132 prev = ArgumentTypes.DEFAULT # Force max value 1dc
133 continue 1dc
134 if idx == len(types)-1: 1defc
135 if t in [ArgumentTypes.LIST, ArgumentTypes.DICT] and not dict_in_list: 1efc
136 return None 1c
137 elif t == ArgumentTypes.LIST and dict_in_list: 1efc
138 return "Only last argument can be kwargs (dictionary argument)." 1e
139 if t < prev: 1defc
140 if ((not active_named_only and t not in [ArgumentTypes.LIST, ArgumentTypes.DICT]) 1dfc
141 or (active_named_only and t in [ArgumentTypes.LIST, ArgumentTypes.DICT])):
142 return ("List and scalar arguments must be before named and " 1df
143 "dictionary arguments")
144 prev = t 1defc
145 return None 1Ac
148class NonEmptyValidator(_AbstractValidator): 1ab
150 def __init__(self, field_name): 1ab
151 _AbstractValidator.__init__(self)
152 self._field_name = field_name
154 def Clone(self): 1ab
155 return self.__class__(self._field_name)
157 def _validate(self, value): 1ab
158 if not value:
159 return _("%s cannot be empty") % self._field_name
160 return None
163class SuiteFileNameValidator(NonEmptyValidator): 1ab
165 def __init__(self, field_name, is_dir_type): 1ab
166 NonEmptyValidator.__init__(self, field_name)
167 self._is_dir_type = is_dir_type
169 def Clone(self): 1ab
170 return self.__class__(self._field_name, self._is_dir_type)
172 def _validate(self, value): 1ab
173 validity = NonEmptyValidator._validate(self, value)
174 if not self._is_dir_type() and not validity:
175 if value.lower() == '__init__':
176 return "Invalid suite file name \"%s\"" % value
177 return validity
180class DirectoryExistsValidator(_AbstractValidator): 1ab
182 def _validate(self, value): 1ab
183 if not os.path.isdir(value):
184 return "Chosen directory must exist"
185 return None
188class NewSuitePathValidator(_AbstractValidator): 1ab
190 def _validate(self, value): 1ab
191 path = os.path.normpath(value)
192 if os.path.exists(path):
193 return "Target file or directory must not exist"
194 parentdir, filename = os.path.split(path)
195 if "__init__" in filename:
196 parentdir = os.path.dirname(parentdir)
197 if not os.path.exists(parentdir):
198 try:
199 os.makedirs(parentdir)
200 except OSError:
201 return f"Failed to create directory: {parentdir}"
202 return None
205class _NameValidator(_AbstractValidator): 1ab
207 def __init__(self, controller, orig_name=None): 1ab
208 _AbstractValidator.__init__(self) 1CzDEFGqprstuvwxyjklmnB
209 self._controller = controller 1CzDEFGqprstuvwxyjklmnB
210 self._orig_name = orig_name 1CzDEFGqprstuvwxyjklmnB
212 def Clone(self): 1ab
213 return self.__class__(self._controller, self._orig_name) 1CzDEFGqprstuvwxy
215 def _validate(self, name): 1ab
216 if self._orig_name is not None and utils.eq( 1jklmnB
217 name, self._orig_name, ignore=['_']):
218 return '' 1B
219 return self._validation_method(name).error_message 1jklmn
221 @property 1ab
222 def _validation_method(self): 1ab
223 return NotImplemented
226class TestCaseNameValidator(_NameValidator): 1ab
227 __test__ = False 1ab
229 @property 1ab
230 def _validation_method(self): 1ab
231 return self._controller.validate_test_name 1jklmn
234class UserKeywordNameValidator(_NameValidator): 1ab
235 @property 1ab
236 def _validation_method(self): 1ab
237 return self._controller.validate_keyword_name 1jklmn
240class ScalarVariableNameValidator(_NameValidator): 1ab
241 @property 1ab
242 def _validation_method(self): 1ab
243 return self._controller.validate_scalar_variable_name 1jklmn
246class ListVariableNameValidator(_NameValidator): 1ab
247 @property 1ab
248 def _validation_method(self): 1ab
249 return self._controller.validate_list_variable_name 1jklmn
252class DictionaryVariableNameValidator(_NameValidator): 1ab
253 @property 1ab
254 def _validation_method(self): 1ab
255 return self._controller.validate_dict_variable_name