From 74e604478ed3ca00439cd2f2ab7ef32b6fa26bc4 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 19 Nov 2019 08:26:42 +0100 Subject: [PATCH] _validation, _second_level_validation => validation, second_level_validation --- tiramisu/option/booloption.py | 6 +- tiramisu/option/broadcastoption.py | 6 +- tiramisu/option/choiceoption.py | 38 +++--- tiramisu/option/dateoption.py | 13 +-- tiramisu/option/domainnameoption.py | 173 +++++++++++++++++++--------- tiramisu/option/emailoption.py | 1 - tiramisu/option/floatoption.py | 6 +- tiramisu/option/intoption.py | 26 +++-- tiramisu/option/ipoption.py | 81 +++++-------- tiramisu/option/netmaskoption.py | 11 +- tiramisu/option/networkoption.py | 44 +++---- tiramisu/option/passwordoption.py | 7 -- tiramisu/option/portoption.py | 58 ++++------ tiramisu/option/stroption.py | 14 +-- tiramisu/option/urloption.py | 101 ++++++++++++---- 15 files changed, 316 insertions(+), 269 deletions(-) diff --git a/tiramisu/option/booloption.py b/tiramisu/option/booloption.py index 49244f0..2a8cb64 100644 --- a/tiramisu/option/booloption.py +++ b/tiramisu/option/booloption.py @@ -30,9 +30,7 @@ class BoolOption(Option): _type = 'boolean' _display_name = _('boolean') - def _validate(self, - value: bool, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: + def validate(self, + value: bool) -> None: if not isinstance(value, bool): raise ValueError() diff --git a/tiramisu/option/broadcastoption.py b/tiramisu/option/broadcastoption.py index ed2900c..f071542 100644 --- a/tiramisu/option/broadcastoption.py +++ b/tiramisu/option/broadcastoption.py @@ -31,10 +31,8 @@ class BroadcastOption(Option): _type = 'broadcast_address' _display_name = _('broadcast address') - def _validate(self, - value: str, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: + def validate(self, + value: str) -> None: if not isinstance(value, str): raise ValueError(_('invalid string')) if value.count('.') != 3: diff --git a/tiramisu/option/choiceoption.py b/tiramisu/option/choiceoption.py index 115edf9..f3c60c2 100644 --- a/tiramisu/option/choiceoption.py +++ b/tiramisu/option/choiceoption.py @@ -18,12 +18,12 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -from types import FunctionType +from typing import Any -from ..setting import undefined +from ..setting import undefined, OptionBag from ..i18n import _ from .option import Option -from ..autolib import carry_out_calculation, Calculation +from ..autolib import Calculation from ..error import ConfigError, display_list @@ -40,12 +40,8 @@ class ChoiceOption(Option): name, doc, values, - default=None, - default_multi=None, - multi=False, - validators=None, - properties=None, - warnings_only=False): + *args, + **kwargs): """ :param values: is a list of values the option can possibly take @@ -56,12 +52,8 @@ class ChoiceOption(Option): self._choice_values = values super(ChoiceOption, self).__init__(name, doc, - default=default, - default_multi=default_multi, - multi=multi, - validators=validators, - properties=properties, - warnings_only=warnings_only) + *args, + **kwargs) def impl_get_values(self, option_bag): @@ -74,15 +66,17 @@ class ChoiceOption(Option): values = self._choice_values return values - def _validate(self, - value, - option_bag, - current_opt=undefined): + def validate(self, + value: Any) -> None: + pass + + def validate_with_option(self, + value: Any, + option_bag: OptionBag) -> None: values = self.impl_get_values(option_bag) if values is not undefined and value not in values: if len(values) == 1: raise ValueError(_('only "{0}" is allowed' '').format(values[0])) - else: - raise ValueError(_('only {0} are allowed' - '').format(display_list(values, add_quote=True))) + raise ValueError(_('only {0} are allowed' + '').format(display_list(values, add_quote=True))) diff --git a/tiramisu/option/dateoption.py b/tiramisu/option/dateoption.py index ad46427..64b21e6 100644 --- a/tiramisu/option/dateoption.py +++ b/tiramisu/option/dateoption.py @@ -22,20 +22,17 @@ from datetime import datetime from ..setting import undefined, Undefined, OptionBag from ..i18n import _ -from .option import Option +from .stroption import StrOption -class DateOption(Option): +class DateOption(StrOption): __slots__ = tuple() _type = 'date' _display_name = _('date') - def _validate(self, - value: str, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: - if not isinstance(value, str): - raise ValueError(_('invalid string')) + def validate(self, + value: str) -> None: + super().validate(value) try: datetime.strptime(value, "%Y-%m-%d") except ValueError: diff --git a/tiramisu/option/domainnameoption.py b/tiramisu/option/domainnameoption.py index 9c65192..c7b7e47 100644 --- a/tiramisu/option/domainnameoption.py +++ b/tiramisu/option/domainnameoption.py @@ -19,14 +19,18 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ import re -from ipaddress import ip_address -from ..setting import undefined, Undefined, OptionBag +from ipaddress import ip_interface +from typing import Any, Optional, List + from ..i18n import _ -from .option import Option +from ..setting import undefined from .ipoption import IPOption +from .stroption import StrOption +from .networkoption import NetworkOption +from .option import Calculation -class DomainnameOption(IPOption): +class DomainnameOption(StrOption): """represents the choice of a domain name netbios: for MS domain hostname: to identify the device @@ -38,53 +42,63 @@ class DomainnameOption(IPOption): _display_name = _('domain name') def __init__(self, - name, - doc, - default=None, - default_multi=None, + name: str, + doc: str, + default: Any=undefined, + default_multi: Any=None, multi: bool=False, - validators=None, - properties=None, - allow_ip: bool=False, - cidr: bool=False, - type_: str='domainname', + validators: Optional[List[Calculation]]=None, + properties: Optional[List[str]]=None, warnings_only: bool=False, - allow_without_dot=False) -> None: + allow_ip: bool=False, + allow_network: bool=False, + network_cidr: bool=False, + type: str='domainname', + allow_without_dot: bool=False, + allow_startswith_dot: bool=False) -> None: - if type_ not in ['netbios', 'hostname', 'domainname']: - raise ValueError(_('unknown type_ {0} for hostname').format(type_)) - extra = {'_dom_type': type_} - if allow_ip not in [True, False]: + if type not in ['netbios', 'hostname', 'domainname']: + raise ValueError(_('unknown type {0} for hostname').format(type)) + extra = {'_dom_type': type} + if not isinstance(allow_ip, bool): raise ValueError(_('allow_ip must be a boolean')) - if allow_without_dot not in [True, False]: + if not isinstance(allow_network, bool): + raise ValueError(_('allow_network must be a boolean')) + if not isinstance(allow_without_dot, bool): raise ValueError(_('allow_without_dot must be a boolean')) - extra['_allow_ip'] = allow_ip + if not isinstance(allow_startswith_dot, bool): + raise ValueError(_('allow_startswith_dot must be a boolean')) extra['_allow_without_dot'] = allow_without_dot - if type_ == 'domainname': + if type == 'domainname': if allow_without_dot: min_time = 0 else: min_time = 1 - regexp = r'((?!-)[a-z0-9-]{{{1},{0}}}\.){{{1},}}[a-z0-9-]{{1,{0}}}'.format(self._get_len(type_), min_time) - msg = _('only lowercase, number, "-" and "." are characters are allowed') - msg_warning = _('only lowercase, number, "-" and "." are characters are recommanded') + regexp = r'((?!-)[a-z0-9-]{{{1},{0}}}\.){{{1},}}[a-z0-9-]{{1,{0}}}'.format(self._get_len(type), min_time) + msg = _('only lowercase, number, "-" and "." characters are allowed') + msg_warning = _('only lowercase, number, "-" and "." characters are recommanded') else: - regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type_)) - msg = _('only lowercase, number and - are characters are allowed') - msg_warning = _('only lowercase, number and "-" are characters are recommanded') + regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type)) + msg = _('only lowercase, number and "-" characters are allowed') + msg_warning = _('only lowercase, number and "-" characters are recommanded') if allow_ip: msg = _('could be a IP, otherwise {}').format(msg) msg_warning = _('could be a IP, otherwise {}').format(msg_warning) - if not cidr: - regexp = r'^(?:{0}|(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){{3}}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))$'.format(regexp) - else: - regexp = r'^(?:{0}|(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){{3}}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/[0-9][0-9]))$'.format(regexp) - else: - regexp = r'^{0}$'.format(regexp) + regexp = r'^{0}$'.format(regexp) extra['_domain_re'] = re.compile(regexp) extra['_domain_re_message'] = msg extra['_domain_re_message_warning'] = msg_warning extra['_has_upper'] = re.compile('[A-Z]') + if allow_ip: + extra['_ip'] = IPOption(name, + doc) + extra['_allow_ip'] = allow_ip + if allow_network: + extra['_network'] = NetworkOption(name, + doc, + cidr=network_cidr) + extra['_allow_network'] = allow_network + extra['_allow_startswith_dot'] = allow_startswith_dot super().__init__(name, doc, @@ -94,19 +108,16 @@ class DomainnameOption(IPOption): validators=validators, properties=properties, warnings_only=warnings_only, - cidr=cidr, - _extra=extra) + extra=extra) - def _get_len(self, type_): - if type_ == 'netbios': + def _get_len(self, type): + if type == 'netbios': return 15 else: return 63 - def _validate(self, - value: str, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: + def _validate_domain(self, + value: str) -> None: def _valid_length(val): if len(val) < 1: raise ValueError(_("invalid length (min 1)")) @@ -114,33 +125,83 @@ class DomainnameOption(IPOption): raise ValueError(_("invalid length (max {0})" "").format(part_name_length)) - if not isinstance(value, str): - raise ValueError(_('invalid string')) - try: - ip_address(value) - except ValueError: - pass - else: - if self.impl_get_extra('_allow_ip') is False: - raise ValueError(_('must not be an IP')) - # it's an IP so validate with IPOption - return super()._validate(value, option_bag, current_opt) part_name_length = self._get_len(self.impl_get_extra('_dom_type')) if self.impl_get_extra('_dom_type') == 'domainname': if not self.impl_get_extra('_allow_without_dot') and not "." in value: raise ValueError(_("must have dot")) if len(value) > 255: raise ValueError(_("invalid length (max 255)")) - for dom in value.split('.'): + if self.impl_get_extra('_allow_startswith_dot') and value.startswith('.'): + val = value[1:] + else: + val = value + for dom in val.split('.'): _valid_length(dom) else: _valid_length(value) - def _second_level_validation(self, value, warnings_only): + def _validate_ip_network(self, + value: str) -> None: + allow_ip = self.impl_get_extra('_allow_ip') + allow_network = self.impl_get_extra('_allow_network') + if allow_ip is False and allow_network is False: + raise ValueError(_('must not be an IP')) + if allow_ip is True: + try: + self.impl_get_extra('_ip').validate(value) + return + except ValueError as err: + if allow_network is False: + raise err + if allow_network is True: + self.impl_get_extra('_network').validate(value) + + def validate(self, + value: str) -> None: + super().validate(value) + try: + # check if it's an IP or network + ip_interface(value) + except ValueError: + self._validate_domain(value) + else: + self._validate_ip_network(value) + + def _second_level_validation_domain(self, + value: str, + warnings_only: bool) -> None: if self.impl_get_extra('_has_upper').search(value): raise ValueError(_('some characters are uppercase')) if not self.impl_get_extra('_domain_re').search(value): if warnings_only: raise ValueError(self.impl_get_extra('_domain_re_message_warning')) - else: - raise ValueError(self.impl_get_extra('_domain_re_message')) + raise ValueError(self.impl_get_extra('_domain_re_message')) + + def _second_level_validation_ip_network(self, + value: str, + warnings_only: bool) -> None: + allow_ip = self.impl_get_extra('_allow_ip') + allow_network = self.impl_get_extra('_allow_network') + # it's an IP so validate with IPOption + if allow_ip is False and allow_network is False: + raise ValueError(_('must not be an IP')) + if allow_ip is True: + try: + self.impl_get_extra('_ip').second_level_validation(value, warnings_only) + return + except ValueError as err: + if allow_network is False: + raise err + if allow_network is True: + self.impl_get_extra('_network').second_level_validation(value, warnings_only) + + def second_level_validation(self, + value: str, + warnings_only: bool) -> None: + try: + # check if it's an IP or network + ip_interface(value) + except ValueError: + self._second_level_validation_domain(value, warnings_only) + else: + self._second_level_validation_ip_network(value, warnings_only) diff --git a/tiramisu/option/emailoption.py b/tiramisu/option/emailoption.py index 6d70b95..5fd3e14 100644 --- a/tiramisu/option/emailoption.py +++ b/tiramisu/option/emailoption.py @@ -26,7 +26,6 @@ from .stroption import RegexpOption class EmailOption(RegexpOption): __slots__ = tuple() - #https://www.w3.org/TR/html-markup/input.email.html#input.email.attrs.value.single. _regexp = re.compile(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$") _type = 'email' _display_name = _('email address') diff --git a/tiramisu/option/floatoption.py b/tiramisu/option/floatoption.py index b64b152..43faef8 100644 --- a/tiramisu/option/floatoption.py +++ b/tiramisu/option/floatoption.py @@ -30,9 +30,7 @@ class FloatOption(Option): _type = 'float' _display_name = _('float') - def _validate(self, - value: float, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: + def validate(self, + value: float) -> None: if not isinstance(value, float): raise ValueError() diff --git a/tiramisu/option/intoption.py b/tiramisu/option/intoption.py index 65d5bb5..22e96de 100644 --- a/tiramisu/option/intoption.py +++ b/tiramisu/option/intoption.py @@ -31,7 +31,6 @@ class IntOption(Option): _display_name = _('integer') def __init__(self, - name, *args, min_number=None, max_number=None, @@ -41,18 +40,27 @@ class IntOption(Option): extra['min_number'] = min_number if max_number is not None: extra['max_number'] = max_number - super().__init__(name, extra=extra, *args, **kwargs) + super().__init__(*args, extra=extra, **kwargs) - - def _validate(self, - value: int, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: + def validate(self, + value: int) -> None: if not isinstance(value, int): raise ValueError() + + def second_level_validation(self, + value, + warnings_only): min_number = self.impl_get_extra('min_number') if min_number is not None and value < min_number: - raise ValueError(_('value must be greater than "{0}"').format(min_number)) + if warnings_only: + msg = 'value should be greater than "{0}"' + else: + msg = 'value must be greater than "{0}"' + raise ValueError(_(msg).format(min_number)) max_number = self.impl_get_extra('max_number') if max_number is not None and value > max_number: - raise ValueError(_('value must be less than "{0}"').format(max_number)) + if warnings_only: + msg = 'value should be less than "{0}"' + else: + msg = 'value must be less than "{0}"' + raise ValueError(_(msg).format(max_number)) diff --git a/tiramisu/option/ipoption.py b/tiramisu/option/ipoption.py index 69f0414..0741e31 100644 --- a/tiramisu/option/ipoption.py +++ b/tiramisu/option/ipoption.py @@ -35,69 +35,48 @@ class IPOption(StrOption): _display_name = _('IP') def __init__(self, - name, - doc, - default=None, - default_multi=None, - multi=False, - validators=None, - properties=None, + *args, private_only=False, allow_reserved=False, - warnings_only=False, cidr=False, - _extra=None): - if _extra is None: + extra=None, + **kwargs): + if extra is None: extra = {} - else: - extra = _extra extra['_private_only'] = private_only extra['_allow_reserved'] = allow_reserved extra['_cidr'] = cidr - super().__init__(name, - doc, - default=default, - default_multi=default_multi, - multi=multi, - validators=validators, - properties=properties, - warnings_only=warnings_only, - extra=extra) + super().__init__(*args, + extra=extra, + **kwargs) - def _validate(self, - value: str, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: - # sometimes an ip term starts with a zero - # but this does not fit in some case, for example bind does not like it - if not isinstance(value, str): - raise ValueError(_('invalid string')) - if value.count('.') != 3: - raise ValueError() - cidr = self.impl_get_extra('_cidr') - if cidr: - if '/' not in value: - raise ValueError(_('must use CIDR notation')) - value_ = value.split('/')[0] - else: - value_ = value - for val in value_.split('.'): - if val.startswith("0") and len(val) > 1: - raise ValueError() - # 'standard' validation + def _validate_cidr(self, value): try: - if not cidr: - ip_address(value) - else: - ip = ip_interface(value) + ip = ip_interface(value) except ValueError: raise ValueError() - if cidr: - valid_ip_netmask(str(ip.ip), str(ip.netmask)) + if ip.ip == ip.network.network_address: + raise ValueError(_("it's in fact a network address")) + elif ip.ip == ip.network.broadcast_address: + raise ValueError(_("it's in fact a broacast address")) - def _second_level_validation(self, - value, - warnings_only): + def _validate_ip(self, value): + try: + ip_address(value) + except ValueError: + raise ValueError() + + def validate(self, + value: str) -> None: + super().validate(value) + if self.impl_get_extra('_cidr'): + self._validate_cidr(value) + else: + self._validate_ip(value) + + def second_level_validation(self, + value: str, + warnings_only: bool) -> None: ip = ip_interface(value) if not self.impl_get_extra('_allow_reserved') and ip.is_reserved: if warnings_only: diff --git a/tiramisu/option/netmaskoption.py b/tiramisu/option/netmaskoption.py index 0a0cc58..a61c7ad 100644 --- a/tiramisu/option/netmaskoption.py +++ b/tiramisu/option/netmaskoption.py @@ -34,14 +34,9 @@ class NetmaskOption(StrOption): _type = 'netmask' _display_name = _('netmask address') - def _validate(self, - value: str, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: - if not isinstance(value, str): - raise ValueError(_('invalid string')) - if value.count('.') != 3: - raise ValueError() + def validate(self, + value: str) -> None: + super().validate(value) for val in value.split('.'): if val.startswith("0") and len(val) > 1: raise ValueError() diff --git a/tiramisu/option/networkoption.py b/tiramisu/option/networkoption.py index 61b995d..54d3da4 100644 --- a/tiramisu/option/networkoption.py +++ b/tiramisu/option/networkoption.py @@ -20,44 +20,28 @@ # ____________________________________________________________ from ipaddress import ip_address, ip_network -from ..setting import undefined from ..i18n import _ -from .option import Option +from .stroption import StrOption -class NetworkOption(Option): +class NetworkOption(StrOption): "represents the choice of a network" __slots__ = tuple() _type = 'network' _display_name = _('network address') def __init__(self, - name, - doc, - default=None, - default_multi=None, - multi=False, - validators=None, - properties=None, - warnings_only=False, - cidr=False): + *args, + cidr=False, + **kwargs): extra = {'_cidr': cidr} - super().__init__(name, - doc, - default=default, - default_multi=default_multi, - multi=multi, - validators=validators, - properties=properties, - warnings_only=warnings_only, - extra=extra) + super().__init__(*args, + extra=extra, + **kwargs) - def _validate(self, - value, - *args, - **kwargs): - if not isinstance(value, str): - raise ValueError(_('invalid string')) + def validate(self, + value: str) -> None: + super().validate(value) if value.count('.') != 3: raise ValueError() cidr = self.impl_get_extra('_cidr') @@ -75,9 +59,9 @@ class NetworkOption(Option): except ValueError: raise ValueError() - def _second_level_validation(self, - value, - warnings_only): + def second_level_validation(self, + value: str, + warnings_only: bool) -> None: if ip_network(value).network_address.is_reserved: if warnings_only: msg = _("shouldn't be reserved network") diff --git a/tiramisu/option/passwordoption.py b/tiramisu/option/passwordoption.py index 78509b5..62d4004 100644 --- a/tiramisu/option/passwordoption.py +++ b/tiramisu/option/passwordoption.py @@ -30,10 +30,3 @@ class PasswordOption(StrOption): __slots__ = tuple() _type = 'password' _display_name = _('password') - - def _validate(self, - value: str, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: - if not isinstance(value, str): - raise ValueError(_('invalid string')) diff --git a/tiramisu/option/portoption.py b/tiramisu/option/portoption.py index 2089b82..b8c7927 100644 --- a/tiramisu/option/portoption.py +++ b/tiramisu/option/portoption.py @@ -20,7 +20,6 @@ # ____________________________________________________________ import re import sys -from typing import Union from ..setting import undefined, Undefined, OptionBag from ..i18n import _ @@ -44,19 +43,13 @@ class PortOption(StrOption): _display_name = _('port') def __init__(self, - name, - doc, - default=None, - default_multi=None, - multi=False, - validators=None, - properties=None, - allow_range=False, - allow_zero=False, - allow_wellknown=True, - allow_registred=True, - allow_private=False, - warnings_only=False): + *args, + allow_range: bool=False, + allow_zero: bool=False, + allow_wellknown: bool=True, + allow_registred: bool=True, + allow_private: bool=False, + **kwargs) -> None: extra = {'_allow_range': allow_range, '_min_value': None, @@ -81,24 +74,15 @@ class PortOption(StrOption): if extra['_max_value'] is None: raise ValueError(_('max value is empty')) - super(PortOption, self).__init__(name, - doc, - default=default, - default_multi=default_multi, - multi=multi, - validators=validators, - properties=properties, - warnings_only=warnings_only, - extra=extra) + super().__init__(*args, + extra=extra, + **kwargs) - def _validate(self, - value: Union[int,str], - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: - if not isinstance(value, str): - raise ValueError(_('invalid string')) + def validate(self, + value: str) -> None: + super().validate(value) if self.impl_get_extra('_allow_range') and ":" in str(value): - value = str(value).split(':') + value = value.split(':') if len(value) != 2: raise ValueError(_('range must have two values only')) if not value[0] < value[1]: @@ -110,8 +94,16 @@ class PortOption(StrOption): for val in value: if not self.port_re.search(val): raise ValueError() + + def second_level_validation(self, + value: str, + warnings_only: bool) -> None: + for val in value.split(':'): val = int(val) if not self.impl_get_extra('_min_value') <= val <= self.impl_get_extra('_max_value'): - raise ValueError(_('must be an integer between {0} ' - 'and {1}').format(self.impl_get_extra('_min_value'), - self.impl_get_extra('_max_value'))) + if warnings_only: + msg = 'should be between {0} and {1}' + else: + msg = 'must be between {0} and {1}' + raise ValueError(_(msg).format(self.impl_get_extra('_min_value'), + self.impl_get_extra('_max_value'))) diff --git a/tiramisu/option/stroption.py b/tiramisu/option/stroption.py index dfd678c..bd83b10 100644 --- a/tiramisu/option/stroption.py +++ b/tiramisu/option/stroption.py @@ -32,10 +32,8 @@ class StrOption(Option): _type = 'string' _display_name = _('string') - def _validate(self, - value: str, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: + def validate(self, + value: str) -> None: if not isinstance(value, str): raise ValueError() @@ -47,11 +45,9 @@ UnicodeOption = StrOption class RegexpOption(StrOption): __slots__ = tuple() - def _validate(self, - value: Any, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: - super()._validate(value, option_bag, current_opt) + def validate(self, + value: Any) -> None: + super().validate(value) match = self._regexp.search(value) if not match: raise ValueError() diff --git a/tiramisu/option/urloption.py b/tiramisu/option/urloption.py index 00bb24d..2b0c298 100644 --- a/tiramisu/option/urloption.py +++ b/tiramisu/option/urloption.py @@ -19,31 +19,75 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ import re +from typing import Any, Optional, List, Dict from ..setting import undefined, Undefined, OptionBag from ..i18n import _ -from .option import Option +from .option import Option, Calculation +from .stroption import StrOption from .domainnameoption import DomainnameOption +from .portoption import PortOption -class URLOption(DomainnameOption): +class URLOption(StrOption): __slots__ = tuple() - proto_re = re.compile(r'(http|https)://') path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") _type = 'url' _display_name = _('URL') - def _validate(self, - value: str, - option_bag: OptionBag, - current_opt: Option=Undefined) -> None: - if not isinstance(value, str): - raise ValueError(_('invalid string')) - match = self.proto_re.search(value) - if not match: + def __init__(self, + name: str, + doc: str, + default: Any=undefined, + default_multi: Any=None, + multi: bool=False, + validators: Optional[List[Calculation]]=None, + properties: Optional[List[str]]=None, + warnings_only: bool=False, + extra: Optional[Dict]=None, + allow_ip: bool=False, + type: str='domainname', + allow_without_dot=False, + allow_range: bool=False, + allow_zero: bool=False, + allow_wellknown: bool=True, + allow_registred: bool=True, + allow_private: bool=False) -> None: + + + extra = {'_domainname': DomainnameOption(name, + doc, + allow_ip=allow_ip, + type=type, + allow_without_dot=allow_without_dot), + '_port': PortOption(name, + doc, + allow_range=allow_range, + allow_zero=allow_zero, + allow_wellknown=allow_wellknown, + allow_registred=allow_registred, + allow_private=allow_private)} + super().__init__(name, + doc, + default=default, + default_multi=default_multi, + multi=multi, + validators=validators, + properties=properties, + warnings_only=warnings_only, + extra=extra) + + def _get_domain_port_files(self, + value: str) -> (str, str): + if value.startswith('http://'): + type = 'http' + value = value[7:] + elif value.startswith('https://'): + type = 'https' + value = value[8:] + else: raise ValueError(_('must start with http:// or ' 'https://')) - value = value[len(match.group(0)):] # get domain/files splitted = value.split('/', 1) if len(splitted) == 1: @@ -55,20 +99,31 @@ class URLOption(DomainnameOption): splitted = domain.split(':', 1) if len(splitted) == 1: domain = splitted[0] - port = 0 + port = {'http': '80', + 'https': '443'}[type] else: domain, port = splitted - if not 0 <= int(port) <= 65535: - raise ValueError(_('port must be an between 0 and ' - '65536')) + return domain, port, files + + def validate(self, + value: str) -> None: + super().validate(value) + domain, port, files = self._get_domain_port_files(value) + # validate port + portoption = self.impl_get_extra('_port') + portoption.validate(port) # validate domainname - super(URLOption, self)._validate(domain, - option_bag, - current_opt) - super(URLOption, self)._second_level_validation(domain, False) - # validate file + domainnameoption = self.impl_get_extra('_domainname') + domainnameoption.validate(domain) + # validate files if files is not None and files != '' and not self.path_re.search(files): raise ValueError(_('must ends with a valid resource name')) - def _second_level_validation(self, value, warnings_only): - pass + def second_level_validation(self, value, warnings_only): + domain, port, files = self._get_domain_port_files(value) + # validate port + portoption = self.impl_get_extra('_port') + portoption.second_level_validation(port, warnings_only) + # validate domainname + domainnameoption = self.impl_get_extra('_domainname') + domainnameoption.second_level_validation(domain, warnings_only)