1 # -*- coding: utf-8 -*-
2 "option types and option description"
3 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # The original `Config` design model is unproudly borrowed from
19 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
20 # the whole pypy projet is under MIT licence
21 # ____________________________________________________________
25 from types import FunctionType
26 from tiramisu.setting import log, undefined
28 from tiramisu.error import ConfigError, ContextError
29 from tiramisu.i18n import _
30 from .baseoption import Option, validate_callback
31 from tiramisu.autolib import carry_out_calculation
34 class ChoiceOption(Option):
35 """represents a choice out of several objects.
37 The option can also have the value ``None``
41 def __init__(self, name, doc, values, default=None,
42 values_params=None, default_multi=None, requires=None,
43 multi=False, callback=None, callback_params=None,
44 validator=None, validator_params=None,
45 properties=None, warnings_only=False):
47 :param values: is a list of values the option can possibly take
49 if isinstance(values, FunctionType):
50 validate_callback(values, values_params, 'values')
52 if values_params is not None:
53 raise ValueError(_('values is not a function, so values_params must be None'))
54 if not isinstance(values, tuple): # pragma: optional cover
55 raise TypeError(_('values must be a tuple or a function for {0}'
57 self._choice_values = values
58 self._choice_values_params = values_params
59 super(ChoiceOption, self).__init__(name, doc, default=default,
60 default_multi=default_multi,
62 callback_params=callback_params,
66 validator_params=validator_params,
67 properties=properties,
68 warnings_only=warnings_only)
70 def impl_get_values(self, context):
71 #FIXME cache? but in context...
72 values = self._choice_values
73 if isinstance(values, FunctionType):
74 values_params = self._choice_values_params
75 if values_params is None:
77 values = carry_out_calculation(self, config=context,
79 callback_params=values_params)
80 if not isinstance(values, list): # pragma: optional cover
81 raise ConfigError(_('calculated values for {0} is not a list'
82 '').format(self.impl_getname()))
85 def _validate(self, value, context=undefined):
87 values = self.impl_get_values(context)
88 if not value in values: # pragma: optional cover
89 raise ValueError(_('value {0} is not permitted, '
93 except ContextError: # pragma: optional cover
94 log.debug('ChoiceOption validation, disabled because no context')
97 class BoolOption(Option):
98 "represents a choice between ``True`` and ``False``"
101 def _validate(self, value, context=undefined):
102 if not isinstance(value, bool):
103 raise ValueError(_('invalid boolean')) # pragma: optional cover
106 class IntOption(Option):
107 "represents a choice of an integer"
110 def _validate(self, value, context=undefined):
111 if not isinstance(value, int):
112 raise ValueError(_('invalid integer')) # pragma: optional cover
115 class FloatOption(Option):
116 "represents a choice of a floating point number"
119 def _validate(self, value, context=undefined):
120 if not isinstance(value, float):
121 raise ValueError(_('invalid float')) # pragma: optional cover
124 class StrOption(Option):
125 "represents the choice of a string"
128 def _validate(self, value, context=undefined):
129 if not isinstance(value, str):
130 raise ValueError(_('invalid string')) # pragma: optional cover
133 if sys.version_info[0] >= 3: # pragma: optional cover
134 #UnicodeOption is same as StrOption in python 3+
135 class UnicodeOption(StrOption):
139 class UnicodeOption(Option):
140 "represents the choice of a unicode string"
144 def _validate(self, value, context=undefined):
145 if not isinstance(value, unicode):
146 raise ValueError(_('invalid unicode')) # pragma: optional cover
149 class IPOption(Option):
150 "represents the choice of an ip"
153 def __init__(self, name, doc, default=None, default_multi=None,
154 requires=None, multi=False, callback=None,
155 callback_params=None, validator=None, validator_params=None,
156 properties=None, private_only=False, allow_reserved=False,
157 warnings_only=False):
158 extra = {'_private_only': private_only,
159 '_allow_reserved': allow_reserved}
160 super(IPOption, self).__init__(name, doc, default=default,
161 default_multi=default_multi,
163 callback_params=callback_params,
167 validator_params=validator_params,
168 properties=properties,
169 warnings_only=warnings_only,
172 def _validate(self, value, context=undefined):
173 # sometimes an ip term starts with a zero
174 # but this does not fit in some case, for example bind does not like it
176 for val in value.split('.'):
177 if val.startswith("0") and len(val) > 1:
178 raise ValueError(_('invalid IP')) # pragma: optional cover
179 except AttributeError: # pragma: optional cover
180 #if integer for example
181 raise ValueError(_('invalid IP'))
182 # 'standard' validation
184 IP('{0}/32'.format(value))
185 except ValueError: # pragma: optional cover
186 raise ValueError(_('invalid IP'))
188 def _second_level_validation(self, value, warnings_only):
189 ip = IP('{0}/32'.format(value))
190 if not self._get_extra('_allow_reserved') and ip.iptype() == 'RESERVED': # pragma: optional cover
192 msg = _("IP is in reserved class")
194 msg = _("invalid IP, mustn't be in reserved class")
195 raise ValueError(msg)
196 if self._get_extra('_private_only') and not ip.iptype() == 'PRIVATE': # pragma: optional cover
198 msg = _("IP is not in private class")
200 msg = _("invalid IP, must be in private class")
201 raise ValueError(msg)
203 def _cons_in_network(self, opts, vals, warnings_only):
205 raise ConfigError(_('invalid len for vals')) # pragma: optional cover
208 ip, network, netmask = vals
209 if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): # pragma: optional cover
211 msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}'
214 msg = _('invalid IP {0} ({1}) not in network {2} ({3}) with '
216 raise ValueError(msg.format(ip, opts[0].impl_getname(), network,
217 opts[1].impl_getname(), netmask, opts[2].impl_getname()))
220 class PortOption(Option):
221 """represents the choice of a port
222 The port numbers are divided into three ranges:
223 the well-known ports,
224 the registered ports,
225 and the dynamic or private ports.
226 You can actived this three range.
227 Port number 0 is reserved and can't be used.
228 see: http://en.wikipedia.org/wiki/Port_numbers
232 def __init__(self, name, doc, default=None, default_multi=None,
233 requires=None, multi=False, callback=None,
234 callback_params=None, validator=None, validator_params=None,
235 properties=None, allow_range=False, allow_zero=False,
236 allow_wellknown=True, allow_registred=True,
237 allow_private=False, warnings_only=False):
238 extra = {'_allow_range': allow_range,
241 ports_min = [0, 1, 1024, 49152]
242 ports_max = [0, 1023, 49151, 65535]
244 for index, allowed in enumerate([allow_zero,
248 if extra['_min_value'] is None:
250 extra['_min_value'] = ports_min[index]
253 elif allowed and is_finally:
254 raise ValueError(_('inconsistency in allowed range')) # pragma: optional cover
256 extra['_max_value'] = ports_max[index]
258 if extra['_max_value'] is None:
259 raise ValueError(_('max value is empty')) # pragma: optional cover
261 super(PortOption, self).__init__(name, doc, default=default,
262 default_multi=default_multi,
264 callback_params=callback_params,
268 validator_params=validator_params,
269 properties=properties,
270 warnings_only=warnings_only,
273 def _validate(self, value, context=undefined):
274 if self._get_extra('_allow_range') and ":" in str(value): # pragma: optional cover
275 value = str(value).split(':')
277 raise ValueError(_('invalid port, range must have two values '
279 if not value[0] < value[1]:
280 raise ValueError(_('invalid port, first port in range must be'
281 ' smaller than the second one'))
288 except ValueError: # pragma: optional cover
289 raise ValueError(_('invalid port'))
290 if not self._get_extra('_min_value') <= val <= self._get_extra('_max_value'): # pragma: optional cover
291 raise ValueError(_('invalid port, must be an between {0} '
292 'and {1}').format(self._get_extra('_min_value'),
293 self._get_extra('_max_value')))
296 class NetworkOption(Option):
297 "represents the choice of a network"
300 def _validate(self, value, context=undefined):
303 except ValueError: # pragma: optional cover
304 raise ValueError(_('invalid network address'))
306 def _second_level_validation(self, value, warnings_only):
308 if ip.iptype() == 'RESERVED': # pragma: optional cover
310 msg = _("network address is in reserved class")
312 msg = _("invalid network address, mustn't be in reserved class")
313 raise ValueError(msg)
316 class NetmaskOption(Option):
317 "represents the choice of a netmask"
320 def _validate(self, value, context=undefined):
322 IP('0.0.0.0/{0}'.format(value))
323 except ValueError: # pragma: optional cover
324 raise ValueError(_('invalid netmask address'))
326 def _cons_network_netmask(self, opts, vals, warnings_only):
327 #opts must be (netmask, network) options
330 self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only)
332 def _cons_ip_netmask(self, opts, vals, warnings_only):
333 #opts must be (netmask, ip) options
336 self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only)
338 def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net,
341 raise ConfigError(_('invalid len for opts')) # pragma: optional cover
344 ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
346 #if cidr == 32, ip same has network
347 if ip.prefixlen() != 32:
349 IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
350 make_net=not make_net)
354 if make_net: # pragma: optional cover
355 msg = _("invalid IP {0} ({1}) with netmask {2},"
356 " this IP is a network")
358 except ValueError: # pragma: optional cover
360 msg = _('invalid network {0} ({1}) with netmask {2}')
361 if msg is not None: # pragma: optional cover
362 raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
366 class BroadcastOption(Option):
369 def _validate(self, value, context=undefined):
371 IP('{0}/32'.format(value))
372 except ValueError: # pragma: optional cover
373 raise ValueError(_('invalid broadcast address'))
375 def _cons_broadcast(self, opts, vals, warnings_only):
377 raise ConfigError(_('invalid len for vals')) # pragma: optional cover
380 broadcast, network, netmask = vals
381 if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
382 raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
383 '({3}) and netmask {4} ({5})').format(
384 broadcast, opts[0].impl_getname(), network,
385 opts[1].impl_getname(), netmask, opts[2].impl_getname())) # pragma: optional cover
388 class DomainnameOption(Option):
389 """represents the choice of a domain name
390 netbios: for MS domain
391 hostname: to identify the device
393 fqdn: with tld, not supported yet
397 def __init__(self, name, doc, default=None, default_multi=None,
398 requires=None, multi=False, callback=None,
399 callback_params=None, validator=None, validator_params=None,
400 properties=None, allow_ip=False, type_='domainname',
401 warnings_only=False, allow_without_dot=False):
402 if type_ not in ['netbios', 'hostname', 'domainname']:
403 raise ValueError(_('unknown type_ {0} for hostname').format(type_)) # pragma: optional cover
404 extra = {'_dom_type': type_}
405 if allow_ip not in [True, False]:
406 raise ValueError(_('allow_ip must be a boolean')) # pragma: optional cover
407 if allow_without_dot not in [True, False]:
408 raise ValueError(_('allow_without_dot must be a boolean')) # pragma: optional cover
409 extra['_allow_ip'] = allow_ip
410 extra['_allow_without_dot'] = allow_without_dot
413 extrachar_mandatory = ''
414 if extra['_dom_type'] == 'netbios':
415 length = 14 # pragma: optional cover
416 elif extra['_dom_type'] == 'hostname':
417 length = 62 # pragma: optional cover
418 elif extra['_dom_type'] == 'domainname':
420 if allow_without_dot is False:
421 extrachar_mandatory = '\.'
423 extrachar = '\.' # pragma: optional cover
425 extra['_domain_re'] = re.compile(r'^(?:[a-z\d][a-z\d\-{0}]{{,{1}}}{2}){3}$'
426 ''.format(extrachar, length,
427 extrachar_mandatory, end))
428 super(DomainnameOption, self).__init__(name, doc, default=default,
429 default_multi=default_multi,
431 callback_params=callback_params,
435 validator_params=validator_params,
436 properties=properties,
437 warnings_only=warnings_only,
440 def _validate(self, value, context=undefined):
441 if self._get_extra('_allow_ip') is True: # pragma: optional cover
443 IP('{0}/32'.format(value))
447 if self._get_extra('_dom_type') == 'domainname' and \
448 not self._get_extra('_allow_without_dot') and \
449 '.' not in value: # pragma: optional cover
450 raise ValueError(_("invalid domainname, must have dot"))
452 raise ValueError(_("invalid domainname's length (max 255)")) # pragma: optional cover
454 raise ValueError(_("invalid domainname's length (min 2)")) # pragma: optional cover
455 if not self._get_extra('_domain_re').search(value):
456 raise ValueError(_('invalid domainname')) # pragma: optional cover
459 class EmailOption(DomainnameOption):
461 username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
463 def _validate(self, value, context=undefined):
464 splitted = value.split('@', 1)
466 username, domain = splitted
467 except ValueError: # pragma: optional cover
468 raise ValueError(_('invalid email address, must contains one @'
470 if not self.username_re.search(username):
471 raise ValueError(_('invalid username in email address')) # pragma: optional cover
472 super(EmailOption, self)._validate(domain)
475 class URLOption(DomainnameOption):
477 proto_re = re.compile(r'(http|https)://')
478 path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
480 def _validate(self, value, context=undefined):
481 match = self.proto_re.search(value)
482 if not match: # pragma: optional cover
483 raise ValueError(_('invalid url, must start with http:// or '
485 value = value[len(match.group(0)):]
487 splitted = value.split('/', 1)
489 domain, files = splitted
494 splitted = domain.split(':', 1)
496 domain, port = splitted
498 except ValueError: # pragma: optional cover
501 if not 0 <= int(port) <= 65535:
502 raise ValueError(_('invalid url, port must be an between 0 and '
503 '65536')) # pragma: optional cover
504 # validate domainname
505 super(URLOption, self)._validate(domain)
507 if files is not None and files != '' and not self.path_re.search(files):
508 raise ValueError(_('invalid url, must ends with filename')) # pragma: optional cover
511 class UsernameOption(Option):
513 #regexp build with 'man 8 adduser' informations
514 username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
516 def _validate(self, value, context=undefined):
517 match = self.username_re.search(value)
519 raise ValueError(_('invalid username')) # pragma: optional cover
522 class FilenameOption(Option):
524 path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
526 def _validate(self, value, context=undefined):
527 match = self.path_re.search(value)
529 raise ValueError(_('invalid filename')) # pragma: optional cover